1. 前言
先说一下啊,首先Laravel和Xunsearch各自都有各种坑,各种达不到很完美的效果;
使用这个方法不知道怎么回事,添加索引总是有重复的,每条数据都有两条索引;
有两条索引确实比较麻烦,等我以后研究下;
2. 步骤
以下步骤基本参考自:https://xueyuanjun.com/post/9485.html
但不得不说,不知道scout版本原因,还是什么原因,原文有坑;
2.1. 安装xunsearch服务端
安装不多言,在Linux上安装就行,如果这都不会,还是真的得研究下Linux;
官方文档:http://www.xunsearch.com/doc/php/guide/start.installation
2.2. 安装各自扩展包
xunsearch扩展包:
composer require hightman/xunsearch
Scout扩展包:
composer require laravel/scout
小坑1:截止本文发布时,scout是v8.0.0
版本,这个版本仅支持Laravel^6.0|^7.0
;
如果你还是5.8.x,请安装:
composer require laravel/scout "v7.2.1"
2.3. 配置
基本配置
将配置文件 scout.php
发布到 config
目录下:
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
在 config/scout.php
中新增 xunsearch
相关配置:
'xunsearch' => [
'host' => env('XUNSEARCH_HOST', '127.0.0.1'),
]
接下来需要修改 .env 中的相关配置:
SCOUT_DRIVER=xunsearch
XUNSEARCH_HOST=迅搜服务端IP地址
SCOUT_PREFIX=misiai_
SCOUT_QUEUE=true
在模型中,使用:
use Searchable;
如:
class Disk extends Model
{
use Searchable;
//....
}
索引配置文件
在config/
目录下新建一个xs_disk.ini
的配置文件:
project.name = vlzpan_disk
project.default_charset = utf-8
server.index = xunsearch服务端IP:8383 // 不配置的话默认为127.0.0.1:8383
server.search = xunsearch服务端IP:8384 // 不配置的话默认为127.0.0.1:8384
[id]
type = id
[filename]
type = title
[share_user]
type = mixed
该配置文件和官方一致,所以更多配置请到官网查看:http://www.xunsearch.com/doc/php/guide/ini.guide
2.4. 编写迅搜 Scout 扩展类
要实现基于迅搜驱动的搜索功能,还需要为其编写 Scout 扩展 XunSearchEnginge
:
该文件位于App\Services
(没有则新建)
<?php
namespace App\Services;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
use Laravel\Scout\Builder;
class SearchEngine
{
protected $xs;
public function __construct(\XS $xs)
{
$this->xs = $xs;
}
/**
* 更新给定模型索引
*
* @param \Illuminate\Database\Eloquent\Collection $models
* @return void
* @throws \XSException
*/
public function update($models)
{
if ($models->isEmpty()) {
return;
}
// if ($this->usesSoftDelete($models->first()) && config('scout.soft_delete', false)) {
// $models->each->pushSoftDeleteMetadata();
// }
Log::info('Update Index');
$index = $this->xs->index;
$models->map(function ($model) use ($index) {
$array = $model->toSearchableArray();
if (empty($array)) {
return;
}
$doc = new \XSDocument;
$data = [
'id' => $model->id,
'filename' => $model->filename,
'share_user' => $model->share_user,
];
$doc->setFields($data);
$index->update($doc);
});
$index->flushIndex();
}
/**
* 从索引中移除给定模型
*
* @param \Illuminate\Database\Eloquent\Collection $models
* @return void
* @throws \XSException
*/
public function delete($models)
{
$index = $this->xs->index;
$models->map(function ($model) use ($index) {
Log::info('Deleted:' . $model->getKey());
$index->del($model->getKey());
});
$index->flushIndex();
}
/**
* 通过迅搜引擎执行搜索
*
* @param \Laravel\Scout\Builder $builder
* @return mixed
*/
public function search(Builder $builder)
{
return $this->performSearch($builder, array_filter(['hitsPerPage' => $builder->limit]));
}
/**
* 分页实现
*
* @param \Laravel\Scout\Builder $builder
* @param int $perPage
* @param int $page
* @return mixed
*/
public function paginate(Builder $builder, $perPage, $page)
{
return $this->performSearch($builder, [
'hitsPerPage' => $perPage,
'page' => $page - 1,
]);
}
/**
* 返回给定搜索结果的主键
*
* @param mixed $results
* @return \Illuminate\Support\Collection
*/
public function mapIds($results)
{
return collect($results)
->pluck('id')->values();
}
/**
* 将搜索结果和模型实例映射起来
*
* @param Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @param mixed $results
* @return \Illuminate\Database\Eloquent\Collection
*/
public function map(Builder $builder, $results, $model)
{
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];
}
})->filter();
}
/**
* 返回搜索结果总数
*
* @param mixed $results
* @return int
*/
public function getTotalCount($results)
{
return ceil($this->xs->search->getLastCount() / 2);
}
// protected function usesSoftDelete($model)
// {
// return in_array(SoftDeletes::class, class_uses_recursive($model));
// }
// 执行搜索功能
protected function performSearch(Builder $builder, array $options = [])
{
$search = $this->xs->search;
if ($builder->callback) {
return call_user_func(
$builder->callback,
$search,
$builder->query,
$options
);
}
$search->setFuzzy()->setQuery($builder->query);
collect($builder->wheres)->map(function ($value, $key) use ($search) {
$search->addRange($key, $value, $value);
});
$offset = 0;
$perPage = $options['hitsPerPage'];
if (!empty($options['page'])) {
$offset = $perPage * $options['page'];
}
return $search->setLimit($perPage, $offset)->search();
}
/**
* 获取中文分词
* @param $text
* @return array
*/
public function getScwsWords($text)
{
$tokenizer = new \XSTokenizerScws();
return $tokenizer->getResult($text);
}
}
说明:
你需要修改
$data = [
'id' => $model->id,
'filename' => $model->filename,
'share_user' => $model->share_user,
];
中的字段为你自己的字段,当然,这还得和你之前ini文件中的字段保持一致;
这里有个中坑:
public function map(Builder $builder, $results, $model)
{
if (count($results) === 0) {
return Collection::make();
}
$keys = collect($results)
->pluck('id')->values()->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];
}
})->filter();
}
Map方法和原文的不一致,我修改过了;你可以看一下原文,原文只传了2个参数,但实际要传3个参数!
以上代码包含搜索、索引构建、删除、分页等所有功能,接下来需要做的就是将其绑定到 Scout 扩展中,我们可以通过在 AppServiceProvider
的 boot
方法中添加以下代码来实现:
// 注册新的搜索引擎
resolve(EngineManager::class)->extend('xunsearch', function ($app) {
$xs = new \XS(config_path('xs_disk.ini'));
return new SearchEngine($xs);
});
3. 后言
目前暂时就这样,我遇到的问题是:
1、每条数据都有两条索引(搜索出来的结果每一条都有重复的一条)
2、上面写完了后,你使用:
$disks = Disk::search($query)->paginate(20);
即可搜索,返回的是模型对象的集合,也正如此,所以建议在ini配置文件中,只填写需要搜索的字段;
因为如果你仔细看SearchEngine
类,你会发现所得到的仅仅是搜索结果的id,然后Laravel(Scout)再封装成每个模型(相当于应该还要从数据库中查询)
3、还值得给小白说的是,.env
配置文件里面的
SCOUT_DRIVER=xunsearch
XUNSEARCH_HOST=迅搜服务端IP地址
SCOUT_PREFIX=misiai_
SCOUT_QUEUE=true
我们使用了SCOUT_QUEUE=true
代表使用了队列,如果你不懂队列,那么将其改为false也行;使用队列不过是想提高web的响应速度罢了;