有人说,折腾的最终目的地就是默认,这句话是真没错….
折腾那么久之后,还是就回归misiyu了
不管怎么说,最近以来,编程能力也是有不少提高的,虽然很久没写php了,但是编程这东西很多东西真是互通的..
再加上,最近撸了几个腾讯云轻量服务器,所以把原来运行在学生机上面的博客搬移到新机器上了,这次无论是Mysql,php,nginx都是全手动搭建,只能说,还是不少坑需要踩一下。
这次打算改造搜索能力,使用的是meilisearch,这是一个新出来的家伙,还不错!
前言
Laravel官方已经有scout meilisearch 的一键扩展包
见:https://packagist.org/packages/laravel/scout
但是,肯定是不支持5.8版本的,所以需要我们自己写一个基于scout
的搜索插件。
流程
先安装scout
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
<?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 扩展中,我们可以通过在 AppServiceProvider
的 boot
方法中添加以下代码来实现:
// 注册新的搜索引擎
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)