基于https://github.com/ErickTamayo/laravel-scout-elastic 改造
安装前需要提前安装官方scout扩展https://learnku.com/docs/laravel/8.x/scout/9422和elasticsearch的php扩展,具体的scout配置说明见官方文档
首先安装php elasticsearch扩展包(用7的包)
composer require elasticsearch/elasticsearch:v7.*
然后,安装laravel官方 Scout扩展:
composer require laravel/scout
Scout 安装完成后,使用 vendor:publish Artisan 命令来生成 Scout 配置文件。这个命令将在你的 config 目录下生成一个 scout.php 配置文件。
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
然后安装:
composer require tbryan24/laravel-scout-elastic
扩展包里有一个服务提供者,使用包的时候需要在config/app.php的providers中注册服务提供者
Tbryan24\LaravelScoutElastic\ElasticScoutProvider::class
在搜索的模型中添加Tbryan24\LaravelScoutElastic\EsSearchable
该trait继承的是Laravel\Scout\Searchable
。这个 trait 会注册一个模型观察者来保持模型和所有驱动的同步:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model;
use Tbryan24\LaravelScoutElastic\EsSearchable;
class LifeMomentsPost extends Model
{
use EsSearchable;
}
在模型中引入Tbryan24\LaravelScoutElastic\EsSearchable,可以过重写模型上的 searchableAs
方法来自定义模型的索引
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model;
use Tbryan24\LaravelScoutElastic\EsSearchable;
class LifeMomentsPost extends Model
{
use EsSearchable;
/**
* 重写模型上的 searchableAs 方法来自定义模型的索引
* @return string
*/
public function searchableAs()
{
return 'posts_index';
}
}
默认情况下,模型以完整的 toArray
格式持久化到搜索索引。如果要自定义同步到搜索索引的数据,可以覆盖模型上的 toSearchableArray
方法:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model;
use Tbryan24\LaravelScoutElastic\EsSearchable;
class LifeMomentsPost extends Model
{
use EsSearchable;
/**
* 重写模型上的 searchableAs 方法来自定义模型的索引
* @return string
*/
public function searchableAs()
{
return 'posts_index';
}
/**
* 获取模型的可搜索数据。
*
* @return array
*/
public function toSearchableArray()
{
/*$data=$this->toArray();
$data['id']=$data["_id"];
unset($data["_id"]);
return $data;*/
//return $this->toArray();//如果你用的是mongodb作为主库,这里直接返回会有问题,mongodb的_id与es的_id冲突
// 自定义数组...
return [
'id' => $this->_id,
'title' => $this->title,
'content' => $this->content,
'type' => $this->type,
'status' => $this->status,
'fabulous_count' => $this->fabulous_count,
'views' => $this->views,
'account_id' => $this->account_id,
'suggest' => $this->title,
// 需要注意 时间字段 需要转义,否则会失败
'created_at' => $this->created_at,
'deleted_at' => $this->deleted_at,
];
}
}
scout在模型插入数据时会调用searchable中的update方法向es中同步数据,如果索引不存在则会创建相应索引,但是系统创建索引都是一些基本类型,有时候并不能满足我们的需求,比如我们要设置分词器或者特殊过滤规则,此时我们就要指定索引创建时的setting或者mapping
如下:在模型里加入esBody方法,设置你需要es为你特殊设置的一些配置
public function esBody()
{
$body = [
"settings" => [
'number_of_shards' => 5,//主分片
'number_of_replicas' => 2,//副本分片
"analysis" => [
"analyzer" => [//构造自己的拼音分析器
"pinyin_analyzer" => [
"tokenizer" => "my_pinyin"
]
],
"tokenizer" => [
"my_pinyin" => [
"type" => "pinyin",
"keep_first_letter" => true,
"keep_separate_first_letter" => true,
"keep_full_pinyin" => true,
"keep_original" => true,
"limit_first_letter_length" => 16,
"lowercase" => true
]
]
]
],
"mappings" => [
'_source' => [
'enabled' => true
],
"properties" => [
"nickname" => [
"type" => "text",
"analyzer" => "ik_max_word",
"fields" => [
"s-pinyin" => [
"type" => "text",
"analyzer" => "pinyin_analyzer"//nickname.s-pinyin字段使用上面定义的拼音分析器
]
]
]
]
]
];
return $body;
}
另外,你也可以自己手动逻辑实现索引创建
$res = LifeMomentsAccount::search()->createIndex($body);
正常情况下search方法传入的是你的搜索关键词(字符串),为了实现更多功能你可以采用该方式传入构造好的body数组,该方式比较灵活,你可以基于es原生构造所有你希望实现的查询,比如你要嵌套多层bool来实现复杂的过滤需求,模型中新增方法getSearchBody用于构造查询的body(暂定方式)
/**
* Notes: 构造es搜索查询的body
* Author: wangchengfei
* DataTime: 2022/4/8 13:30
* @param $content
* @param int $page
* @param int $pageSize
* @return array
*/
public function getSearchBody($content, $page = 1, $pageSize = 10)
{
$body = [
'query' => [
'bool' => [
'must' => [
[
"bool" => [
"should" => [
[
"term" => [
"status" => [
"value" => "0"
]
]
],
[
"term" => [
"status" => [
"value" => "2"
]
]
]
]
]
],
[
'match' => [
'title' => [
'query' => $content,
]
]
],
],
]
],
"highlight" => [
"fields" => [
'title' => new \stdClass(),
],
'pre_tags' => ["<em style='color:red'>"],
'post_tags' => ["</em>"]
],
"from" => ($page - 1) * $pageSize,
"size" => $pageSize
];
return $body;
}
如果你采用了以上方式,你就不需要链式调用高亮,排序等方法了,全都可以在body里实现,如果你调用了高亮等那就会重写该部分内容,比如你在Repository中使用自定义body,你就可以像如下方式使用:
$body = $this->lifeMomentsPostModel->getSearchBody($content, $page, $pageSize);
$esData = LifeMomentsPost::search($body)->raw();
获取分词解析后的最终数据(对单个汉字或字母进行了过滤和对分词结果进行了排重)
LifeMomentsSearchKeywords::search()->analyze($keywords)->getTokens()
获取分词解析的原始数据(es返回的结构)
LifeMomentsSearchKeywords::search()->analyze($keywords)->raw();
必须在模型中配置suggestAs,不然会提示==模型内未配置搜索建议==
public function suggestAs()
{
return ["field" => "keywords.s-pinyin", "size" => 10,'suggest_name'=>'keywords-suggest'];
}
查询使用
LifeMomentsSearchKeywords::search()->suggest($keywords)->raw();
LifeMomentsPost::search($keywords)->highLight(["title","content"],"<p style='color:red'>","</p>")->raw();