前提条件本机运行了elasticsearch服务
安装Elasticsearch php插件
composer require elasticsearch/elasticsearch
基本的新增、删除索引
//加载Elasticsearch php插件
require "vendor/autoload.php";
use \Elasticsearch\ClientBuilder;
//本机host
$hosts = [
'127.0.0.1:9200'
];
//连接
$client = ClientBuilder::create()->setHosts($hosts)->build();
$params = [
//索引名
'index' => 'artlce',
'body' => [
'settings' => [
//分区数
'number_of_shards' => 5,
//副本数
'number_of_replicas' => 1
],
'mappings' => [
//文档
'_doc' => [
'_source' => [
'enabled' => true
],
'properties' => [
//文档的字段以及字段的类型
'anchor' => [
//keyword类型的字段只能通过精确值搜索到
'type' => 'keyword'
],
'title' => [
'type' => 'text',
//ik分词器的ik_max_word模式
'analyzer' => 'ik_max_word',
'search_analyzer' => 'ik_max_word'
],
'desn' => [
'type' => 'text',
//ik分词器的ik_max_word模式
'analyzer' => 'ik_max_word',
'search_analyzer' => 'ik_max_word'
]
]
]
]
]
];
// 创建索引
$response = $client->indices()->create($params);
//删除索引
$params1 = ['index' => 'artlce'];
$response1 = $client->indices()->delete($params1);
ik分词器
1、ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民, 中华,华人,人民共和国,人民,人,民,共和国,共和,和国,国歌”,会穷尽各种可能的组合;
2、ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民,共和国,国歌”。 索引时,为了提供索引的覆盖范围,通常会采用ik_max_word分析器,会以最细粒度分词索引,搜索时为了提高搜索准确度,会采用ik_smart分析器,会以粗粒度分词
创建索引成功
基本的文档的操作
//加载Elasticsearch php插件
require "vendor/autoload.php";
use \Elasticsearch\ClientBuilder;
//本机host
$hosts = [
'127.0.0.1:9200'
];
//连接
$client = ClientBuilder::create()->setHosts($hosts)->build();
//新增文档到索引中
$params2 = [
//索引
'index' => 'artlce',
//类型 es6之后一个索引对应一个type
'type' => '_doc',
//文档id
'id' => 1,
//字段值
'body' => [
'anchor' => 'wyq',
'title' => '这是一篇文章',
'desn' => '这是一篇文章的简介',
],
];
$response = $client->index($params2);
//修改指定索引指定文档的内容
$params3 = [
//索引
'index' => 'artlce',
//类型 es6之后一个索引对应一个type
'type' => '_doc',
//文档id
'id' => 1,
//字段值
'body' => [
'anchor' => 'wyq123',
],
];
$response = $client->update($params3);
//删除指定索引的文档
$params3 = [
//索引
'index' => 'artlce',
//文档id
'id' => 1,
];
$response = $client->delete($params3);
//查询索引文档的内容
$params4 = [
'index' => "artcle",
'body' => [
'query' => [
'match' => [
'title' => "这是一篇"
]
]
]
];
$response = $this->client->search($params4);
集成到CI框架中
和之前的redis一样封装一个类,里面编写一些经常用到的Elasticsearch方法,之前在thinkphp5框架中封装过。
//libraries目录下的es类
<?php
/**
* Created by PhpStorm.
* User: wyq
* Date: 2021/12/17
* Time: 14:29
*/
require "vendor/autoload.php";
use \Elasticsearch\ClientBuilder;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\Missing404Exception;
//封装es工具类
class ES
{
private $client = [];
//构造函数中连接Es
public function __construct()
{
$this->client = ClientBuilder::create()->setHosts(config_item('esHost'))->build();
}
public function getEs()
{
return $this->client;
}
/**
* 创建索引
* @param string $indexName 索引名
* @param int $number_of_shards 分区数
* @param int $number_of_replicas 副本数
* @param array $data 字段数组
* @return array
*/
public function createIndex($indexName = "", $number_of_shards = 5, $number_of_replicas = 1, $data = [])
{
// 创建索引
$params = [
'index' => $indexName,
'body' => [
'settings' => [
'number_of_shards' => $number_of_shards,
'number_of_replicas' => $number_of_replicas
],
'mappings' => [
'_doc' => [
'_source' => [
'enabled' => true
],
'properties' => $data
]
]
]
];
try {
$response = $this->client->indices()->create($params);
$res = [
'code' => 200,
'message' => "success",
'data' => $response
];
} catch (BadRequest400Exception $exception) {
$res = [
'code' => $exception->getCode(),
'message' => json_decode($exception->getMessage(), true),
'data' => [],
];
}
return $res;
}
/**
* 删除索引
* @param string $indexName 索引名
*/
public function delIndex($indexName = "")
{
$data = ['index' => $indexName];
try {
$response = $this->client->indices()->delete($data);
$res = [
'code' => 200,
'message' => "success",
'data' => $response
];
} catch (Missing404Exception $exception) {
$res = [
'code' => $exception->getCode(),
'message' => json_decode($exception->getMessage(), true),
'data' => [],
];
}
return $res;
}
/**
* 保存doc
* @param string $indexName 索引名
* @param int $id 新增、修改的文档id
* @param array $data 新增、修改的文档数据
*/
public function SaveDoc($indexName = "", int $id, $data = [])
{
// 写文档
$params = [
'index' => $indexName,
'type' => '_doc',
'id' => $id,
'body' => $data,
];
try {
$response = $this->client->index($params);
$res = [
'code' => 200,
'message' => "success",
'data' => $response
];
} catch (Missing404Exception $exception) {
$res = [
'code' => $exception->getCode(),
'message' => json_decode($exception->getMessage(), true),
'data' => [],
];
}
return $res;
}
/**
* 删除doc
* @param string $indexName 索引名
* @param int $id 文档id
* @return array
*/
public function deleteDoc($indexName = "", int $id)
{
$data = [
'index' => $indexName,
'id' => $id,
];
try {
$response = $this->client->delete($data);
$res = [
'code' => 200,
'message' => "success",
'data' => $response
];
} catch (Missing404Exception $exception) {
$res = [
'code' => $exception->getCode(),
'message' => json_decode($exception->getMessage(), true),
'data' => [],
];
}
return $res;
}
/**
* 修改doc
* @param string $indexName
* @param int $id
* @param array $data
* @return array
*/
public function updateDoc($indexName = "", int $id, $data = [])
{
//修改指定索引指定文档的内容
$params = [
//索引
'index' => $indexName,
//类型 es6之后一个索引对应一个type
'type' => '_doc',
//文档id
'id' => $id,
//字段值
'body' => [
'doc' => [
$data
]
],
];
try {
$response = $this->client->update($params);
$res = [
'code' => 200,
'message' => "success",
'data' => $response
];
} catch (Missing404Exception $exception) {
$res = [
'code' => $exception->getCode(),
'message' => json_decode($exception->getMessage(), true),
'data' => [],
];
}
return $res;
}
/**
* @param string $indexName
* @param array $body
* @return array
*/
public function searchDoc($indexName = "", $body =[])
{
$params = [
'index' => $indexName,
'body' => $body,
];
try {
$response = $this->client->search($params);
$res = [
'code' => 200,
'message' => "success",
'data' => $response
];
} catch (Missing404Exception $exception) {
$res = [
'code' => $exception->getCode(),
'message' => json_decode($exception->getMessage(), true),
'data' => [],
];
}
return $res;
}
}
创建索引
//控制器Api下面的代码
public function createIndex()
{
//获取上面的es功能类
$es = getEs();
$data = [
//文档的字段以及字段的类型
'uses_id' => [
'type' => 'integer'
],
'nickname' => [
//keyword类型的字段只能通过精确值搜索到
'type'=> 'keyword'
],
'pageViews' => [
'type' => 'integer'
],
'likes' => [
'type' => 'integer'
],
'title' => [
'type' => 'text',
//ik分词器的ik_max_word模式
'analyzer' => 'ik_max_word',
'search_analyzer' => 'ik_max_word'
],
'content' => [
'type' => 'text',
//ik分词器的ik_max_word模式
'analyzer' => 'ik_max_word',
'search_analyzer' => 'ik_max_word'
],
'create_time' => [
'type' => 'integer'
],
'update_time' => [
'type' => 'date',
//date的格式
"format" => "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
],
];
//创建索引
$res = $es->createIndex('articles',5,1,$data);
if ($res) {
success($res);
}
fail($res);
}
结果
将全部的文章放入es中
这里面的字段都是检索之后需要展示的字段,其他不需要展示的字段为放入es中
//控制器 Job/job下的代码
public function articleToes()
{
$article = $this->article_model->findAll();
$es = getEs();
foreach ($article as $v) {
$data = [
'user_id' => $v['user_id'],
'nickname' => $v['nickname'],
'desc' => $v['desc'],
'content' => $v['content'],
'title' => $v['title'],
'pageViews' => $v['pageViews'],
'likes' => $v['likes'],
'create_time' => $v['create_time'],
'update_time' => date('Y-m-d H:i:s', $v['update_time'])
];
$es->SaveDoc('articles', $v['id'], $data);
}
}
结果
对文章进行检索
//Article控制器代码
public function search()
{
$data = $this->input->post();
if (!empty($data['keyword'])){
$body = [
'query' => [
'bool' => [
//这里的should相当于or操作,还有must相当于and操作
'should' => [
['match' => ['title' => $data['keyword'] ] ],
['match' => ['desc' => $data['keyword'] ] ],
['match' => ['content' => $data['keyword'] ] ],
['match' => ['nickname' => $data['keyword'] ] ],
]
]
]
];
$res = $this->es->searchDoc('articles', $body);
success($res);
}
}
结果:
这里对es类进行了简单的封装,以及简单的操作,后续还会对代码进行优化.
新增功能
新增封装了批量插入功能函数
/**
* 批量插入数据
* @param string $indexName
* @param $data 带上id
* @param bool $isId 是否在内容中也保存id,默认不保存
* @return array
*/
public function CreateDocs($indexName = "", $data, $isId = false)
{
$params = ["body" => []];
foreach ($data as $k => $v) {
if (empty($v['id'])) {
//数组中不存在id
$params['body'][] = [
'index' => [
'_index' => $indexName,
]
];
} else {
//数组中存在id
$params['body'][] = [
'index' => [
'_index' => $indexName,
//设置id
'_id' => $v['id']
]
];
//内容中不展示id字段
if (!$isId) {
unset($v['id']);
}
}
//内容
$params['body'][] = $v;
}
try {
$response = $this->client->bulk($params);
$res = [
'code' => 200,
'message' => "success",
'data' => $response
];
} catch (Missing404Exception $exception) {
$res = [
'code' => $exception->getCode(),
'message' => json_decode($exception->getMessage(), true),
'data' => [],
];
}
return $res;
}
对查询出来的数据格式和分页做优化(放在公共助手函数中)
//测试查询 这里只测试页码分页效果
function findEs()
{
$data = $this->input->get();
$page = $data['page'] ? $data['page'] : 1; //当前页数
$pageSize = 2; //页码
$from = ($page - 1) * $pageSize;
$es = getEs();
$body = [
'query' => [
'bool' => [
"should" => [
["match" => ['title' => "Python"]],
["match" => ['desc' => "Python"]],
["match" => ['nickname' => "Python"]],
]
]
],
"from" => $from,
"size" => $pageSize,
];
$res = $es->searchDoc('articles', $body);
$result = esFormat($res,$page,$pageSize);
responseArray($result);
}
/*
* 格式化查询到的es结果
*/
if (!function_exists("esFormat")) {
function esFormat($data,$page = 0,$pageSize)
{
if (empty($data['data']) || $data['code'] != 200) {
return returnArr(400, '', '查找失败');
}
if (empty($data['data']['hits']['total'])) {
return returnArr(400, '', '暂无数据');
}
$formatData = array_column($data['data']['hits']['hits'], "_source");
$total = $data["data"]['hits']['total']['value'];
$res = [
"list" => $formatData,
];
if ($page){
$res["paginate"] = [
'page' => $page, //页码
"pageSize" => $pageSize, //一页展示个数
"total" => $total //总数
];
}
return returnArr(200, $res, "查询成功");
}
}
分页展示没有问题