Client 操作索引库和文档(PHP版)
一、安装 PHP Client
官方文档:https://www.elastic.co/guide/en/elasticsearch/client/php-api/7.17/connecting.html
二、创建 hotel 表,并导入数据
CREATE TABLE `hotel` (
`id` bigint(20) NOT NULL COMMENT '酒店id',
`name` varchar(255) NOT NULL COMMENT '酒店名称',
`address` varchar(255) NOT NULL COMMENT '酒店地址',
`price` int(10) NOT NULL COMMENT '酒店价格',
`score` int(2) NOT NULL COMMENT '酒店评分',
`brand` varchar(32) NOT NULL COMMENT '酒店品牌',
`city` varchar(32) NOT NULL COMMENT '所在城市',
`star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,1星到5星,1钻到5钻',
`business` varchar(255) DEFAULT NULL COMMENT '商圈',
`latitude` varchar(32) NOT NULL COMMENT '纬度',
`longitude` varchar(32) NOT NULL COMMENT '经度',
`pic` varchar(255) DEFAULT NULL COMMENT '酒店图片',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;
三、分析数据结构
mapping 映射要考虑的信息包括:
- 字段名
- 数据类型
- 是否参与搜索
- 是否分词,如果分词,分词器选择什么?
说明:
- 字段名、字段数据类型,可以参考数据表结构的名称和类型
- 是否参与搜索要根据业务来判断,例如:图片地址就无需参与搜索
- 是否分词看文本内容,内容如果是一个整体就无需分词,反之则要分词
- 分词器的选择由分词粒度决定
四、使用 Kibana 工具,创建索引库
PUT /hote
{
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
说明:
- location:地理坐标,里面包含精度、纬度,定义成 geo_point 类型
- all:一个组合字段,数据库不存在该字段,其目的是将多字段的值,利用 copy_to 合并,提供给用户搜索
五、基于 PHP Client 操作 ES
5.1 初始化客户端
<?php
use Elasticsearch\ClientBuilder;
class Index
{
private $client;
public function __construct(App $app)
{
$this->initClient();
}
private function initClient()
{
if (!$this->client) {
$host = sprintf('http://%s:%s@%s:%s', env('es.username'), env('es.password'), env('es.host'), env('es.port'));
$hosts = [
$host
];
$this->client = ClientBuilder::create()->setHosts($hosts)->build();
}
return $this->client;
}
}
5.2 创建索引库
public function createIndex()
{
$result = $this->client->indices()->create([
'index' => 'hotel',
'body' => [
'mappings' => [
'properties' => [
'id' => [
'type' => 'keyword',
],
'name' => [
'type' => 'text',
'analyzer' => 'ik_max_word',
'copy_to' => 'all'
],
'address' => [
'type' => 'keyword',
'index' => 'false'
],
'price' => [
'type' => 'integer'
],
'score' => [
'type' => 'integer'
],
'brand' => [
'type' => 'keyword',
'copy_to' => 'all'
],
'city' => [
'type' => 'keyword',
'copy_to' => 'all'
],
'starName' => [
'type' => 'keyword',
],
'business' => [
'type' => 'keyword',
],
'location' => [
'type' => 'geo_point',
],
'pic' => [
'type' => 'keyword',
'index' => 'false'
],
'all' => [
'type' => 'text',
'analyzer' => 'ik_max_word',
]
]
]
]
]);
return json($result);
}
# 响应结果:{"acknowledged":true,"shards_acknowledged":true,"index":"hotel"}
5.3 获取索引库
public function getIndex()
{
$result = $this->client->indices()->get(['index' => 'hotel']);
return json($result);
}
# 响应结果:
{"hotel":{"aliases":[],"mappings":{"properties":{"address":{"type":"keyword","index":false},"all":{"type":"text","analyzer":"ik_max_word"},"brand":{"type":"keyword","copy_to":["all"]},"business":{"type":"keyword"},"city":{"type":"keyword","copy_to":["all"]},"id":{"type":"keyword"},"location":{"type":"geo_point"},"name":{"type":"text","copy_to":["all"],"analyzer":"ik_max_word"},"pic":{"type":"keyword","index":false},"price":{"type":"integer"},"score":{"type":"integer"},"starName":{"type":"keyword"}}},"settings":{"index":{"routing":{"allocation":{"include":{"_tier_preference":"data_content"}}},"number_of_shards":"1","provided_name":"hotel","creation_date":"1732073635610","number_of_replicas":"1","uuid":"HBkKJctjTQaqoBpmuMQbUg","version":{"created":"8518000"}}}}}
5.3 删除索引库
public function deleteIndex()
{
$result = $this->client->indices()->delete(['index' => 'hotel']);
return json($result);
}
# 响应结果:
{"acknowledged":true}
5.4 新增文档
新增酒店数据:添加单条记录
public function addDoc()
{
$log = Hotel::find(36934);
$result = $this->client->index([
'index' => 'hotel',
'id' => $log->id,
'body' => [
'id' => $log->id,
'name' => $log->name,
'address' => $log->address,
'price' => $log->price,
'score' => $log->score,
'brand' => $log->brand,
'city' => $log->city,
'starName' => $log->star_name,
'business' => $log->business,
'location' => $log->latitude . ',' . $log->longitude, // 纬度,经度
'pic' => $log->pic,
]
]);
return json($result);
}
# 响应结果:
{"_index":"hotel","_id":"36934","_version":3,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":2,"_primary_term":1}
5.5 查询文档
根据ID查询文档:
public function getDocById()
{
$id = 36934;
$result = $this->client->get(['index' => 'hotel', 'id' => $id]);
return json($result);
}
# 响应结果:
{"_index":"hotel","_id":"36934","_version":3,"_seq_no":2,"_primary_term":1,"found":true,"_source":{"id":36934,"name":"7天连锁酒店(上海宝山路地铁站店)","address":"静安交通路40号","price":336,"score":37,"brand":"7天酒店","city":"上海","starName":"二钻","business":"四川北路商业区","location":"31.251433,121.47522","pic":"https:\/\/m.tuniucdn.com\/fb2\/t1\/G1\/M00\/3E\/40\/Cii9EVkyLrKIXo1vAAHgrxo_pUcAALcKQLD688AAeDH564_w200_h200_c1_t0.jpg"}}
5.6 修改文档
增量修改:
public function updateDocById()
{
$id = 36934;
$result = $this->client->update([
'index' => 'hotel',
'id' => $id,
'body' => [
// 需更新的字段
'doc' => [
'address' => '静安交通路41号'
]
]
]);
return json($result);
}
# 响应结果:
{"_index":"hotel","_id":"36934","_version":4,"result":"updated","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":3,"_primary_term":1}
5.7 删除文档
根据ID删除指定记录:
public function deleteDocById()
{
$id = 36934;
$result = $this->client->delete(['index' => 'hotel', 'id' => $id]);
return json($result);
}
# 响应结果:
{"_index":"hotel","_id":"36934","_version":5,"result":"deleted","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":4,"_primary_term":1}
5.8 批量导入文档
public function batchAddDoc()
{
$hotels = Hotel::order(['id' => 'asc'])->limit(2)->select();
$params = [];
foreach ($hotels as $hotel) {
$params['body'][] = [
'index' => [
'_index' => 'hotel',
'_id' => $hotel->id,
]
];
$params['body'][] = [
'id' => $hotel->id,
'name' => $hotel->name,
'address' => $hotel->address,
'price' => $hotel->price,
'score' => $hotel->score,
'brand' => $hotel->brand,
'city' => $hotel->city,
'starName' => $hotel->star_name,
'business' => $hotel->business,
'location' => $hotel->latitude . ',' . $hotel->longitude,
'pic' => $hotel->pic,
];
}
$result = $this->client->bulk($params);
return json($result);
}
# 响应结果:
{"errors":false,"took":0,"items":[{"index":{"_index":"hotel","_id":"36934","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":5,"_primary_term":1,"status":201}},{"index":{"_index":"hotel","_id":"38609","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":6,"_primary_term":1,"status":201}}]}
5.9 ES 服务类
<?php
namespace app\pkg;
use Elasticsearch\ClientBuilder;
use Elasticsearch\Client;
class ES
{
private Client $client;
public function __construct(array $hosts)
{
$this->client = ClientBuilder::create()->setHosts($hosts)->build();
}
// ================================= 索引相关操作 =================================================
/**
* 创建索引
*
* @param string $index 索引名称
* @param array $settings 索引设置
* @param array $mappings 索引映射
* @return array
*/
public function creatIndex(string $index, array $settings = [], array $mappings = []): array
{
$params = [
'index' => $index,
'body' => [
'settings' => $settings,
'mappings' => $mappings,
]
];
return $this->client->indices()->create($params);
}
/**
* 删除索引
*
* @param $indexName string 索引名称
* @return array
*/
public function deleteIndex(string $indexName): array
{
$params = [
'index' => $indexName,
];
return $this->client->indices()->delete($params);
}
/**
* 检查索引是否存在
*
* @param string $indexName
* @return bool
*/
public function existsIndex(string $indexName)
{
return $this->client->indices()->exists(['index' => $indexName]);
}
/**
* 获取索引设置
*
* @param string $indexName 索引名称
* @return array
*/
public function getIndexSettings(string $indexName): array
{
$params = [
'index' => $indexName
];
return $this->client->indices()->getSettings($params);
}
/**
* 关闭索引
*
* @param $indexName string 索引名称
* @return array
*/
public function closeIndex(string $indexName): array
{
$params = ['index' => $indexName];
return $this->client->indices()->close($params);
}
/**
* 打开索引
*
* @param $indexName string 索引名称
* @return array
*/
public function openIndex(string $indexName): array
{
$params = ['index' => $indexName];
return $this->client->indices()->open($params);
}
/**
* 更新索引设置
*
* @param string $indexName 索引名称
* @param array $settings 新的索引设置
* @return array 更新索引设置的响应
*/
public function updateIndexSettings(string $indexName, array $settings): array
{
$params = [
'index' => $indexName,
'body' => ['settings' => $settings]
];
return $this->client->indices()->putSettings($params);
}
/**
* 更新非动态索引设置
*
* @param $indexName string 索引名称
* @param array $settings 索引设置
* @return array
*/
public function updateNonDynamicIndexSettings(string $indexName, array $settings)
{
// 关闭索引
$this->closeIndex($indexName);
// 更新索引设置
$response = $this->updateIndexSettings($indexName, $settings);
// 重新打开索引
$this->openIndex($indexName);
return $response;
}
/**
* 获取索引映射
*
* @param string $indexName 索引名称
* @return array 获取索引映射的响应
*/
public function getIndexMappings($indexName)
{
$params = [
'index' => $indexName
];
return $this->client->indices()->getMapping($params);
}
/**
* 更新索引映射
*
* @param string $indexName 索引名称
* @param array $mappings 新的索引映射
* @return array 更新索引映射的响应
*/
public function updateIndexMappings(string $indexName, array $mappings): array
{
$params = [
'index' => $indexName,
'body' => ['properties' => $mappings]
];
return $this->client->indices()->putMapping($params);
}
/**
* 刷新索引
*
* @param $indexName string 索引名称
* @return array
*/
public function refreshIndex(string $indexName): array
{
$params = [
'index' => $indexName
];
return $this->client->indices()->refresh($params);
}
// ===================================== 文档相关操作 ===============================================
/**
* 新增文档
*
* @param string $indexName 索引名称
* @param string $id 文档ID
* @param array $body 文档内容
* @return array
*/
public function addDocument($indexName, $id, array $body)
{
$params = [
'index' => $indexName,
'id' => $id, // 文档ID,可省略,默认生成随机ID; 即使传入的是正整形,查询存到的结果还是字符串类型
'body' => $body
];
return $this->client->index($params);
}
/**
* 批量新增文档
*
* @param array $params
* @return array|callable
*/
public function batchAddDocument(array $params)
{
return $this->client->bulk($params);
}
/**
* 获取文档
* 说明:查询单挑记录
*
* @param string $indexName 索引名称
* @param string $id 文档ID
* @return array|callable
*/
public function getDocument(string $indexName, $id)
{
$params = [
'index' => $indexName,
'id' => $id,
];
return $this->client->get($params);
}
/**
* 更新文档
*
* @param string $indexName 索引名称
* @param string $id 文档ID
* @param array $doc 更新内容
* @return array|callable
*/
public function updateDocument(string $indexName, $id, array $doc)
{
$params = [
'index' => $indexName,
'id' => $id,
'body' => [
'doc' => $doc, // 需要更新的内容
],
];
return $this->client->update($params);
}
/**
* 删除文档
*
* @param string $indexName
* @param $id
* @return array|callable
*/
public function deleteDocument(string $indexName, int $id)
{
$params = [
'index' => $indexName,
'id' => $id,
];
return $this->client->delete($params);
}
// ======================================= 搜索相关操作 ================================================
/**
* 基础搜索方法
*
* @param string $indexName 索引名称
* @param array $query 搜索查询
* @param array|null $sort 排序参数
* @param int|null $from 起始位置
* @param int|null $size 返回结果数
* @return array
*/
public function search(string $indexName, array $query, array $sort = null, int $from = null, int $size = null)
{
$params = [
'index' => $indexName,
'body' => [
'query' => $query
]
];
if ($sort) {
$params['body']['sort'] = $sort;
}
if (!is_null($from)) {
$params['body']['from'] = $from;
}
if (!is_null($size)) {
$params['body']['size'] = $size;
}
return $this->client->search($params);
}
}