elasticsearch服务类封装
1.服务类的调用
<?php /** * es数据查询 * * @return void */ // public function esSeachData($keywords, $page = 1) public function esSeachData() { $page = 1; // 分页需要是数量的倍数 $keywords = '真无线'; // 检索内容 $configId = 1; // 检索配置id 1=搜索配置,2=文章推荐 // 只有文章推荐需要筛选文章,也就是$configId = 2 $searchTypeValue= 2; // $config['search_type'] 检索配置 id为1传栏目、id为2传模型id // 检索配置:1=搜索配置,2=文章推荐 $config = AddonsElasticsearchPlugConfig::build() -> where('id', $configId)->find(); $keyName = 'content'; // 检索索引名 $fieldName = 'title'; // 检索字段 $pageSize = $config['search_article_nums']; // 文章数量 $from = ($page - 1) * $pageSize; // 分页需要是数量的倍数 // 检索范围:0=模糊搜索,1=精准匹配 if ($config['search_scope']) { $fieldName = $fieldName.'.keyword'; } // 检索排序规则:0=默认,1=发布时间倒序,1=发布时间升序 switch ($config['search_sort']) { case 1: $sort['create_time'] = ['order' => 'desc']; break; case 2: $sort['create_time'] = ['order' => 'asc']; break; default: $sort = []; break; } // 返回的字段限制 $includeFields = ['id','cid','mid','title']; // 检索类型:0=匹配即可,1=同栏目数据,1=同模型数据(只有文章推荐需要筛选文章、栏目) if ($configId == 2 && $config['search_type'] == 1) { $searchTypeKey = 'cid'; $res = ElasticsearchService::build() -> esSingleFieldWhereSearch($keyName, $fieldName, $keywords, $from, $pageSize, $sort, $includeFields, $searchTypeKey, $searchTypeValue); } elseif ($configId == 2 && $config['search_type'] == 2) { $searchTypeKey = 'mid'; $res = ElasticsearchService::build() -> esSingleFieldWhereSearch($keyName, $fieldName, $keywords, $from, $pageSize, $sort, $includeFields, $searchTypeKey, $searchTypeValue); }else{ $res = ElasticsearchService::build() -> esSingleFieldSearch($keyName, $fieldName, $keywords, $from, $pageSize, $sort, $includeFields); } $list['total'] = $res['pageNumber']; // 数据数量 $list['per_page'] = $pageSize; // 页码数量 $list['current_page'] = $page; // 页码 $list['last_page'] = ceil($res['pageNumber']/$pageSize); // 最后页码 $list['data'] = $res['list']; // 数据列表 $data['list'] = $list; return $this->json_success('es数据查询',$data); // return $data; }
2.服务类的封装
<?php
/*
* @Author: panzhide seanzhui@qq.com
* @Date: 2023-03-29 16:48:56
* @LastEditors: panzhide seanzhui@qq.com
* @LastEditTime: 2023-04-03 11:35:37
* @FilePath: \zhanzancms\addons\elasticsearchplug\model\ElasticsearchService.php
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
namespace addons\elasticsearchplug\service;
use Elasticsearch\ClientBuilder;
use think\App;
use think\facade\Db;
use addons\elasticsearchplug\model\BaseModel;
class ElasticsearchService extends BaseModel
{
// 构造函数
public function __construct()
{
$elasticsearch_hosts = ['elastic:seanzhui@198.12.116.62:9200'];
$this->client = ClientBuilder::create()->setHosts($elasticsearch_hosts)->build();
$this->index_name = 'zhanzancms'; // 索引名(项目名)
}
public static function build() {
return new self();
}
/**
* @desc 创建索引和映射,只能创建一次
* ES可以自动创建索引,不过实际项目中,通常需要预先创建索引结构,明确指定数据类型,避免出现ES自动创建的字段类型不是你想要的类型
* @paramstring $keyName 索引名
* @returnarray|mixed|string
*/
public function create_index($keyName) {
$params = [
'index' => $this->index_name .'_'. $keyName,
'body' => [
'settings' => [
'number_of_shards' => 5, // 数据分片数,默认为5,有时候设置为3
'number_of_replicas' => 0 // 数据备份数,如果只有一台机器,设置为0
],
'mappings'=>[
'_source' => [
'enabled' => true
],
'properties' => [
// 以下为字段示例
'id' => [
'type' => 'integer',
'index' => false, //index参数作用是控制当前字段是否被索引,默认为true,false表示不记录,即不可被搜索
],
'title' => [
'type' => 'text', // text 类型的字符串是可以被全文检索的,它会被分词器作用,
'index' => true,
'analyzer' => 'ik_max_word' // 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query
],
'content' => [
'type' => 'text',
'index' => true,
'analyzer' => 'ik_max_word'
],
'tags' => [ // 商品标签,字段被拆分后不具有意义,所以使用keyword 类型
'type' => 'keyword',
],
'price' => [
'type' => 'float'
],
'add_time' => [
'type' => 'integer', // 时间戳
],
'update_time' => [
'type' => 'integer', // 时间戳
]
]
]
]
];
try {
$response = $this->client->indices()->create($params);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
throw new \think\Exception($e -> getMessage());
}
return $response;
}
/**
* 删除索引
* @paramstring $keyName 索引名
* @returnvoid
*/
public function deleteIndex(string $keyName)
{
Db::startTrans();
try {
$params = ['index' => $keyName];
$response = $this->client->indices()->delete($params);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
throw new \think\Exception($e -> getMessage());
}
return $response;
}
/**
* 判断文档存在
* @paramstring $keyName 索引名
* @paramstring $id 索引id
* @returnbool
*/
public function existsDoc(string $keyName, string $id)
{
$params = [
'index' => $this->index_name .'_'. $keyName,
'id' => $id
];
return $this->client->exists($params);
}
/**
* 添加文档(单条)
* @paramstring $keyName 索引名
* @paramstring $id 索引id
* @paramarray $doc 跟创建文档结构时properties(es文档模板)的字段一致
* @returnarray|callable
*/
public function addDoc(string $keyName, string $id, array $doc)
{
$params = [
'index' => $this->index_name .'_'. $keyName,
'id' => $id,
'body' => $doc
];
return $this->client->index($params);
}
/**
* 添加文档(批量),如果id已存在则为更新
* @paramstring $keyName 索引名
* @paramstring $id 索引id
* @paramarray $list 跟创建文档结构时properties(es文档模板)的字段一致
* @returnarray|callable
*/
public function addDocAll(string $keyName, array $list)
{
$index_name = $this->index_name .'_'. $keyName;
$docs = [];
foreach ($list as $key => $value) {
$docs['body'][] = ['index'=>['_index'=>$index_name, '_id'=>$value['id']]];
$docs['body'][] = $value;
}
return $this->client->bulk($docs);
}
/**
* 删除文档
* @paramstring $keyName 索引名
* @paramstring $id 文档id
* @returnarray|callable
*/
public function deleteDoc(string $keyName, string $id)
{
// 文档是否存在
$res = $this->existsDoc($keyName, $id);
if (!$res) {
throw new \think\Exception('文档不存在');
}
Db::startTrans();
try {
$params = [
'index' => $this->index_name .'_'. $keyName,
'id' => $id
];
$response = $this->client->delete($params);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
throw new \think\Exception($e -> getMessage());
}
return $response;
}
/**
* 更新文档(字段内容)
* @paramstring $keyName 索引名
* @paramstring $id 文档id
* @paramstring $key 更新的字段
* @paramstring $value 更新的内容
* @returnarray|callable
*/
public function updateDoc(string $keyName, string $id, string $key, string $value)
{
// 文档是否存在
$res = $this->existsDoc($keyName, $id);
if (!$res) {
throw new \think\Exception('文档不存在');
}
Db::startTrans();
try {
// 可以灵活添加新字段,最好不要乱添加
$params = [
'index' => $this->index_name .'_'. $keyName,
'id' => $id,
'body' => [
'doc' => [
$key => $value
]
]
];
$response = $this->client->update($params);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
throw new \think\Exception($e -> getMessage());
}
return $response;
}
/**
* 更新文档(数组)
* @paramstring $keyName 索引名
* @paramstring $id 文档id
* @paramarray $doc 更新的字段
* @returnarray|callable
*/
public function updateDocArray(string $keyName, string $id, array $doc)
{
// 文档是否存在
$res = $this->existsDoc($keyName, $id);
if (!$res) {
throw new \think\Exception('文档不存在');
}
Db::startTrans();
try {
// 可以灵活添加新字段,最好不要乱添加
$params = [
'index' => $this->index_name .'_'. $keyName,
'id' => $id,
'body' => ['doc'=>$doc]
];
$response = $this->client->update($params);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
throw new \think\Exception($e -> getMessage());
}
return $response;
}
/**
* 获取文档
* @paramstring $keyName 索引名
* @paramstring $id 文档id
* @returnarray
*/
public function getDoc(string $keyName, string $id){
$params = [
'index' => $this->index_name .'_'. $keyName,
'id' => $id
];
$response = $this->client->get($params);
return $response['_source'];
}
/**
* es按条件批量删除数据
* @paramstring $keyName 索引名
* @paramstring $fieldName 字段名
* @paramarray $where 排序
* @returnvoid
*/
public function esSingleFieldDel($keyName, $fieldName, $value)
{
$params = [
'index' => $this->index_name .'_'. $keyName,
// 如果出现版本冲突,如何处理?proceed表示继续更新,abort表示停止更新
'conflicts' => 'proceed',
'body' => [ // ES请求体内容
'query' => [ // 设置查询条件,跟es查询语法一致
"term" => [
$fieldName => $value
]
],
],
];
$res = $this->client->deleteByQuery($params);
return $res;
}
/**
* es单字段复杂搜索
$sort 3维数组 例:'sort' => [
['time' => ['order' => 'desc']],
['popularity' => ['order' => 'desc']]
]
* @paramstring $keyName 索引名
* @paramstring $fieldName.keyword 字段名 全文精确检索字段用 match加.keyword,其他非text字段匹配用term
* @paramstring $keywords 关键词
* @paramint $from 起始位置
* @paramint $size 文档数量
* @paramarray $sort 排序
* @paramint $mid 复杂搜索
* @paramarray $includeFields 返回的字段
* @returnvoid
*/
public function esSingleFieldWhereSearch($keyName, $fieldName, $keywords, $from=0, $size=0, $sort=[], $includeFields=[], $searchTypeKey, $searchTypeValue)
{
$searchTypeValueMin = $searchTypeValue - 1;
$searchTypeValueMax = $searchTypeValue + 1;
$params = [
'index' => $this->index_name .'_'. $keyName,
'body' => [
'query' => [
'bool' => [
// 必须匹配
'must' => [
'match' => [
$fieldName => $keywords
],
],
// // 应该匹配
// 'should' => [
// ['match' => [
// 'mid' => [
// 'query' => '1',
// 'boost' => 10, // 权重
// ]
// ]],
// // ['match' => [
// // $fieldName => [
// // 'query' => $keywords,
// // 'boost' => 2,
// // ]
// // ]],
// ],
// 复杂的搜索,筛选出mid等于x的
'filter' => [
'range' => [
$searchTypeKey => [
'gt'=> $searchTypeValueMin,
'lt'=> $searchTypeValueMax
]
]
],
],
]
]
];
// 获取页面数量
$resList = $this->client->search($params);
$data['pageNumber'] = count($resList);
if(!empty($sort)){
$params['body']['sort'] = $sort;
}
if($size != 0){
$params['body']['from'] = $from;
$params['body']['size'] = $size;
}
if(!empty($includeFields)){
$params['body']["_source"] = $includeFields;
}
$res = $this->client->search($params);
$data['list'] = array_column($res['hits']['hits'], '_source');
return $data;
}
/**
* es单字段模糊匹配
$sort 3维数组 例:'sort' => [
['time' => ['order' => 'desc']],
['popularity' => ['order' => 'desc']]
]
* @paramstring $keyName 索引名
* @paramstring $fieldName 字段名
* @paramstring $keywords 关键词
* @paramint $from 起始位置
* @paramint $size 文档数量
* @paramarray $sort 排序
* @paramarray $includeFields 返回的字段
* @returnvoid
*/
public function esSingleFieldSearch($keyName, $fieldName, $keywords, $from=0, $size=0, $sort=[], $includeFields=[])
{
$params = [
'index' => $this->index_name .'_'. $keyName,
'body' => [
'query' => [
'match' => [
$fieldName => $keywords
]
]
]
];
// 获取页面数量
$resList = $this->client->search($params);
$data['pageNumber'] = count($resList);
if(!empty($sort)){
$params['body']['sort'] = $sort;
}
if($size != 0){
$params['body']['from'] = $from;
$params['body']['size'] = $size;
}
if(!empty($includeFields)){
$params['body']["_source"] = $includeFields;
}
$res = $this->client->search($params);
$data['list'] = array_column($res['hits']['hits'], '_source');
return $data;
}
/**
* es多字段模糊匹配同一数据
* param $fieldName 1维数组
param $includeFields 1维数组
param $sort 3维数组 例:
[
['time' => ['order' => 'desc']],
['popularity' => ['order' => 'desc']]
]
* @paramstring $keyName 索引名
* @paramarray $fieldName 字段名(多字段)
* @paramstring $keywords 关键词
* @paramint $from 起始位置
* @paramint $size 文档数量
* @paramarray $sort 排序
* @paramarray $includeFields 返回的字段
* @returnvoid
*/
public function esMultiFieldSearch($keyName, $fieldName=[], $keywords, $from=0, $size=0, $sort=[], $includeFields=[])
{
$params = [
'index' => $this->index_name .'_'. $keyName,
'body' => [
"query"=>[
"multi_match"=> [
"query" => $keywords,
"fields" => $fieldName
]
]
]
];
// 获取页面数量
$resList = $this->client->search($params);
$data['pageNumber'] = count($resList);
if(!empty($sort)){
$params['body']['sort'] = $sort;
}
if($size!=0){
$params['body']['from'] = $from;
$params['body']['size'] = $size;
}
if(!empty($includeFields)){
$params['body']["_source"] = $includeFields;
}
$res = $this->client->search($params);
$data['list'] = array_column($res['hits']['hits'], '_source');
return $data;
}
/**
* 获取索引内所有数据
* @param [type] $keyName 索引名
* @paraminteger $from 起始位置
* @paraminteger $size 文档数量
* @paramarray $sort 排序
* @returnvoid
*/
public function getEsIndexAllData($keyName, $from=0, $size=0, $sort=[])
{
$params = [
'index' => $this->index_name .'_'. $keyName,
'body' => [
'query' => [
'match_all' => new \stdClass()
]
]
];
if($size!=0){
$params['body']['from'] = $from;
$params['body']['size'] = $size;
}
if(!empty($sort)){
$params['body']['sort'] = $sort;
}
$res = $this->client->search($params);
$res = array_column($res['hits']['hits'], '_source');
return $res;
}
}