2021-07-18 Laravel5.8结合MeiliSearch+Scout全文搜索

2021-07-19 15:53:19 浏览数 (1)

有人说,折腾的最终目的地就是默认,这句话是真没错….

折腾那么久之后,还是就回归misiyu了


不管怎么说,最近以来,编程能力也是有不少提高的,虽然很久没写php了,但是编程这东西很多东西真是互通的..

再加上,最近撸了几个腾讯云轻量服务器,所以把原来运行在学生机上面的博客搬移到新机器上了,这次无论是Mysql,php,nginx都是全手动搭建,只能说,还是不少坑需要踩一下。

这次打算改造搜索能力,使用的是meilisearch,这是一个新出来的家伙,还不错!

前言

Laravel官方已经有scout meilisearch 的一键扩展包

见:https://packagist.org/packages/laravel/scout

但是,肯定是不支持5.8版本的,所以需要我们自己写一个基于scout的搜索插件。

流程

先安装scout

代码语言:javascript复制
composer require laravel/scout "v7.2.1"

laravel 5.8 最多支持 scout "v7.2.1"

发布:

代码语言:javascript复制
php artisan vendor:publish --provider="LaravelScoutScoutServiceProvider"

在config/会多一个scout.php

新增:

代码语言:javascript复制
'meilisearch' => [
    'host' => env('MEILISEARCH_HOST', '127.0.0.1:7700'),
]

增加个配置:

代码语言:javascript复制
SCOUT_DRIVER=meilisearch
MEILISEARCH_MASTER_KEY=xxx
MEILISEARCH_HOST=http://xxxx:7700

首先我们写个帮助类:

代码语言:javascript复制
<?php


namespace AppHelper;

use MeiliSearchClient;

class MeiliSearch
{
    protected $client;

    /**
     * MeiliSearch constructor.
     */
    public function __construct()
    {
        $this->client = new Client(env('MEILISEARCH_HOST'), env('MEILISEARCH_MASTER_KEY'));
    }

    public function add(string $indexName, array $docs)
    {
        $index = $this->client->index($indexName);
        $index->addDocuments($docs);
    }


    public function update(string $indexName, array $docs)
    {
        $index = $this->client->index($indexName);
        $index->updateDocuments($docs);
    }


    public function delete(string $indexName, int $id)
    {
        $index = $this->client->index($indexName);
        $index->deleteDocument($id);
    }


    public function search(string $indexName, string $key, int $size, int $page)
    {
        $offset = $page * $size - $size;
        $index = $this->client->index($indexName);
        return $index->search($key, [
            'offset' => $offset,
            'limit' => $size,
        ]);
    }


}

上面就是个帮助函数,只是进行了再封装。 所以你需要安装 meilisearch 官方提供的php开发包:https://packagist.org/packages/meilisearch/meilisearch-php


下面这个就是核心,也是scout的扩展类:

AppServicesAppSearch

代码语言:javascript复制
<?php


namespace AppServices;


use AppHelperMeiliSearch;
use IlluminateDatabaseEloquentCollection;
use IlluminateDatabaseEloquentModel;
use IlluminateSupportFacadesLog;
use LaravelScoutBuilder;
use MeiliSearchSearchSearchResult;

class AppSearch
{
    protected $ml;
    protected $indexName = 'blog_article';

    /**
     * AppSearch constructor.
     * @param MeiliSearch $ml
     */
    public function __construct(MeiliSearch $ml)
    {
        $this->ml = $ml;
    }

    /**
     * 更新给定模型索引
     *
     * @param Collection $models
     * @return void
     */
    public function update(Collection $models)
    {
        // dd($models);
        if ($models->isEmpty()) {
            return;
        }

        // if ($this->usesSoftDelete($models->first()) && config('scout.soft_delete', false)) {
        //     $models->each->pushSoftDeleteMetadata();
        // }
        Log::info('Update Index');
        $docs = [];
        $models->map(function ($model) use (&$docs) {
            $array = $model->toSearchableArray();
            if (empty($array)) {
                return;
            }
            $data = [
                'id' => $model->id,
                'title' => $model->title,
                'author_id' => $model->author_id,
                'category_id' => $model->category_id,
                'tags' => $model->tags,
                'content' => $model->content,
            ];
            $docs[] = $data;
        });
        // dd($docs);
        $this->ml->update($this->indexName, $docs);
    }

    /**
     * 从索引中移除给定模型
     *
     * @param Collection $models
     * @return void
     */
    public function delete(Collection $models)
    {
        Log::info('-------------delete-----------');
        $models->map(function ($model) {
            Log::info('Deleted:' . $model->getKey());
            $this->ml->delete($this->indexName, $model->getKey());
        });
    }

    /**
     * 通过迅搜引擎执行搜索
     *
     * @param Builder $builder
     * @return mixed
     */
    public function search(Builder $builder, $size, $page)
    {
        Log::info('search');
        return $this->ml->search($this->indexName, $builder->query, $size, $page);
    }

    /**
     * 返回给定搜索结果的主键
     *
     * @param mixed $results
     * @return IlluminateSupportCollection
     */
    public function mapIds($results)
    {
        return collect($results)
            ->pluck('id')->values();
    }

    /**
     * 分页实现
     *
     * @param Builder $builder
     * @param int $size
     * @param int $page
     * @return mixed
     */
    public function paginate(Builder $builder, int $size, int $page)
    {
        Log::info('paginate');
        return $this->search($builder, $size, $page);
    }

    /**
     * 返回搜索结果总数
     *
     * @param mixed $results
     * @return int
     */
    public function getTotalCount(SearchResult $results): int
    {
        return $results->getNbHits();
    }

    /**
     * 将搜索结果和模型实例映射起来
     *
     * @param Builder $builder
     * @param Model $model
     * @param mixed $results
     * @return Collection
     */
    public function map(Builder $builder, $results, Model $model): Collection
    {
        if (count($results) === 0) {
            return Collection::make();
        }

        $keys = collect($results)
            ->pluck('id')->values()->unique()->all();
        $models = $model->getScoutModelsByIds($builder, $keys)->keyBy($model->getKeyName());
        return Collection::make($results)->map(function ($hit) use ($model, $models) {
            $key = $hit['id'];
            if (isset($models[$key])) {
                return $models[$key];
            }
            return null;
        })->filter();
    }
}

当然,其实就是调用方法,在模型进行增删改查时,也对meilisearch中索引数据同步进行增删改查。


上面完成之后,你需要:

在模型Model.php中,增加use Searchable;

如:

代码语言:javascript复制
class Article extends Model
{
    use Searchable;
    //....
}

接下来需要做的就是将其绑定到 Scout 扩展中,我们可以通过在 AppServiceProviderboot 方法中添加以下代码来实现:

代码语言:javascript复制
// 注册新的搜索引擎
resolve(EngineManager::class)->extend('meilisearch', function ($app) {
    $ml = new MeiliSearch();
    return new AppSearch($ml);
});

最后,可以使用了:

代码语言:javascript复制
$articles = Article::search($keyword)->paginate(15);

最后

怎么将以前的文章导入到meilisearch呢?

代码语言:javascript复制
php artisan scout:import "AppModelAdminArticle"

使用artisan命令就行,自带的哟!

参考

  • meilisearch/MeiliSearch: Powerful, fast, and an easy to use search engine (github.com)
  • Laravel Scout - Laravel - The PHP Framework For Web Artisans
  • Laravel Scout Xunsearch最佳实践 - 文章|迷思爱学习乐园|兴趣是最好的老师 (misiai.com)
  • meilisearch/meilisearch-php: PHP wrapper for the MeiliSearch API (github.com)

0 人点赞