common/Elasticseatch.php
1 <?php
2
3 namespace app\common;
4
5 use yii;
6 use yii\elasticsearch\ActiveRecord;
7 use yii\data\Pagination;
8
9
10 class Elasticsearch extends ActiveRecord{
11
12 /**
13 * 使用的数据库连接
14 */
15 public static function getDb(){
16 return \Yii::$app->get('elasticsearch');
17 }
18
19 /**
20 * [mapConfig mapping配置]
21 * 返回这个模型的映射
22 *
23 * properties配置说明
24 *
25 * 属性
26 * 数据类型 string(已废弃) byte short integer long float double boolean date binary(二进制) ip(以字符换格式存IPv4地址) token_count(存储索引的字数信息)
27 *
28 * 公共属性
29 * index -> analyzed(被分析,编入索引,产生的token能被搜索到) not_analyzed(不被分析,使用原始值编入索引,在索引中作为单个词) no(不编入索引,无法搜索该字段)
30 * store -> 指定是否将字段的原始值写入索引,默认值是no,字段值被分析,能够被搜索,但是,字段值不会存储,这意味着,该字段能够被查询,但是不会存储字段的原始值 通常情况字段值已经是_source字段的一部分,可以只检索需要的
31 * boost -> 字段级别的助推,默认值是1,定义了字段在文档中的重要性/权重
32 * include_in_all -> 该属性指定当前字段是否包括在_all字段中,默认值是true
33 * copy_to -> 该属性指定一个字段名称,ElasticSearch引擎将当前字段的值复制到该属性指定的字段中
34 * doc_values -> 文档值是存储在硬盘上的索引时(indexing time)数据结构,对于not_analyzed字段,默认值是true,analyzed string字段不支持文档值;如果您确定不需要对字段进行排序或聚合,或者从脚本访问字段值,则可以禁用doc值以节省磁盘空间
35 * fielddata -> 字段数据是存储在内存中的查询时(querying time)数据结构,只支持analyzed string字段
36 * null_value -> 该属性指定一个值,当字段的值为NULL时,该字段使用null_value代替NULL值;在ElasticSearch中,NULL 值不能被索引和搜索,当一个字段设置为NULL值,ElasticSearch引擎认为该字段没有任何值,使用该属性为NULL字段设置一个指定的值,使该字段能够被索引和搜索。
37 *
38 * 常用其他属性
39 * analyzer -> 该属性定义用于建立索引和搜索的分析器名称,默认值是全局定义的分析器名称,该属性可以引用在配置结点(settings)中自定义的分析器
40 * search_analyzer -> 该属性定义的分析器,用于处理发送到特定字段的查询字符串
41 * ignore_above -> 该属性指定一个整数值,当字符串字段(analyzed string field)的字节数量大于该数值之后,超过长度的部分字符数据将不能被analyzer处理,不能被编入索引;对于 not analyzed string字段,超过长度的部分字符将被忽略,不会被编入索引。默认值是0,禁用该属性;
42 * position_increment_gap -> 该属性指定在相同词的位置上增加的gap,默认值是100;
43 * index_options -> 索引选项控制添加到倒排索引(Inverted Index)的信息
44 * [
45 * docs -> 只索引文档编号(Doc Number)
46 * freqs -> 索引文档编号和词频率(term frequency)
47 * positions -> 索引文档编号,词频率和词位置(序号)
48 * offsets -> 索引文档编号,词频率,词偏移量(开始和结束位置)和词位置(序号)
49 * 默认情况下,被分析的字符串(analyzed string)字段使用positions,其他字段使用docs
50 * ]
51 *
52 * 数值类型的其他属性
53 * precision_step -> 该属性指定为数值字段每个值生成的term数量,值越低,产生的term数量越高,范围查询越快,索引越大,默认值是4
54 * ignore_malformed -> 忽略格式错误的数值,默认值是false,不忽略错误格式,对整个文档不处理,并且抛出异常
55 * coerce -> 默认值是true,尝试将字符串转换为数值,如果字段类型是整数,那么将小数取整
56 *
57 * 日期类型的其他属性
58 * format -> 指定日期的格式,例如:“yyyy-MM-dd hh:mm:ss”
59 * ignore_malformed -> 忽略错误格式,默认值是false,不忽略错误格式
60 *
61 * fields -> 在fields属性中定义一个或多个字段,该字段的值和当前字段值相同,可以设置一个字段用于搜索,一个字段用于排序等
62 * "properties":
63 * {
64 * "id":{
65 * "type":"long",
66 * "fields":{
67 * "id2":{"type":"long","index":"not_analyzed"}
68 * }
69 * }
70 * }
71 * _all -> 表示存储其他字段的数据以便搜索,默认情况下,_all字段是启用的,包含了索引中所有字段的数据,然而这一字段使索引变大,如果不需要,请禁用该字段,或排除某些字段
72 * "_all":{"enabled":false}
73 * _source -> 表示在生成索引的过程中,存储发送到ElasticSearch的原始JSON文档,默认情况下,该字段会被启用,因为索引的局部更新功能依赖该字段。
74 * _routing -> 路由字段 公式:shard_num = hash(_routing) % num_primary_shards 使用的默认字段是_id,设置required为true,表示路由字段在进行索引的CRUD操作时必需显式赋值。
75 *
76 * 不可配置的元字段
77 * _index -> 返回文档所属的索引
78 * _uid -> 返回文档的type和id
79 * _type -> 返回文档类型(type)
80 * _id -> 返回文档的ID;
81 * _size -> 返回文档的_source字段中函数的字节数量;
82 * _field_names -> 返回文档中不包含null值的字段名称;
83 *
84 */
85 public static function mapConfig(){
86 return [
87 // 根据type来获取model->attributes
88 'member' => [
89 'properties' => [
90 'user_id' => ['type' => 'integer'],
91 'user_name' => ['type' => 'text'],
92 'email' => ['type' => 'keyword'],
93 'qq' => ['type' => 'keyword'],
94 'edit_time' => ['type' => 'date', 'format' => 'yyyy-MM-dd HH:mm:ss||yyyy-MM-dd']
95 ]
96 ],
97 // 根据type来获取model->attributes
98 'member2' => [
99 'properties' => [
100 'user_id' => ['type' => 'integer'],
101 'user_name' => ['type' => 'text'],
'edit_time' => ['type' => 'date', 'format' => 'yyyy-MM-dd HH:mm:ss||yyyy-MM-dd']
102 ]
103 ]
104 ];
105 }
106
107 /**
108 * 获取群集中所有索引映射
109 */
110 public static function getMapping(){
111 $db = self::getDb();
112 $command = $db->createCommand();
113 return $command->getMapping();
114 }
115
116 /**
117 * 根据索引主键获取相关信息
118 * 支持单个或多个
119 */
120 public function getByKey($id){
121 if(is_array($id)){
122 $res = self::mget($id);
123 }else{
124 $res = self::get($id);
125 }
126 return $res;
127 }
128
129 /**
130 * 单个
131 * 默认返回object对象 返回数组 添加->asArray()
132 */
133 public function getOne($query = []){
134 $es_query = self::find();
135
136 // 匹配查询
137 if($query && !empty($query)){
138 $es_query->query($query);
139 }
140 // 分组
141 $res = $es_query->one();
142
143 return $res;
144 }
145
146 /**
147 * 列表
148 * 默认返回object对象 返回数组 添加->asArray()
149 * search 与 all 区别在于 all是在search基础上处理再拿出结果
150 */
151 public function getList($query = [], $order = [], $offset = 0, $limit = 20){
152 $es_query = self::find();
153
154 // 匹配查询
155 if($query && !empty($query)){
156 $es_query->query($query);
157 }
158 // 排序
159 if($order && !empty($order)){
160 $es_query->orderby($order);
161 }
162 // 分组
163 $res = $es_query->offset($offset)->limit($limit)->asArray()->all();
164 $list = array_column($res, '_source');
165
166 return $list;
167 }
168
169 /**
170 * 分页列表
171 * 默认返回object对象 返回数组 添加->asArray()
172 * search 与 all 区别在于 all是在search基础上处理再拿出结果
173 */
174 public function getPageList($query = [], $order = []){
175 $es_query = self::find();
176
177 // 匹配查询
178 if($query && !empty($query)){
179 $es_query->query($query);
180 }
181 // 排序
182 if($order && !empty($order)){
183 $es_query->orderby($order);
184 }
185
186 // 分组
187 $count = $es_query->search();
188 // 分页
189 $pages = new Pagination(['totalCount' => $count['hits']['total']]);
190 // 分组
191 $res = $es_query->offset($pages->offset)->limit($pages->limit)->asArray()->all();
192 $list = array_column($res, '_source');
193
194 return ['list' => $list, 'pages' => $pages];
195 }
196
197 /**
198 * 列表
199 * 默认返回object对象 返回数组 添加->asArray()
200 * search 与 all 区别在于 all是在search基础上处理再拿出结果
201 */
202 public function getFilterList($filter_arr = [], $query = [], $order = [], $offset = 0, $limit = 20){
203 $es_query = self::find();
204
205 // 过滤器
206 if($filter_arr && !empty($filter_arr)){
207 $es_query->postFilter($filter_arr);
208 }
209 // 匹配查询
210 if($query && !empty($query)){
211 $es_query->query($query);
212 }
213 // 排序
214 if($order && !empty($order)){
215 $es_query->orderby($order);
216 }
217 // 分组
218 $res = $es_query->offset($offset)->limit($limit)->all();
219
220 return $res;
221 }
222
223 /**
224 * 获取高亮列表
225 * 默认返回object对象 返回数组 添加->asArray()
226 * search 与 all 区别在于 all是在search基础上处理再拿出结果
227 * 循环使用$v->highlight获取高亮列表
228 */
229 public function getHighLightList($highlight_arr = [], $query = [], $offset = 0, $limit = 20){
230 $es_query = self::find();
231
232 // 高亮
233 if($highlight_arr && !empty($highlight_arr)){
234 $es_query->highlight($highlight_arr);
235 }
236 // 匹配查询
237 if($query && !empty($query)){
238 $es_query->query($query);
239 }
240 // 分组
241 $res = $es_query->offset($offset)->limit($limit)->all();
242
243 return $res;
244 }
245
246 /**
247 * 获取聚合列表
248 * 默认返回object对象 返回数组 添加->asArray()
249 * search 与 all 区别在于 all是在search基础上处理再拿出结果
250 */
251 public function getAggList($aggregate_name, $addAggregate_arr = [], $query = [], $offset = 0, $limit = 20){
252 $es_query = self::find();
253
254 // 聚合
255 if($addAggregate_arr && !empty($addAggregate_arr)){
256 $es_query->addAggregate($aggregate_name, $addAggregate_arr);
257 }
258 // 匹配查询
259 if($query && !empty($query)){
260 $es_query->query($query);
261 }
262 // 分组
263 $res = $es_query->offset($offset)->limit($limit)->search();
264
265 return ['list' => $res['hits']['hits'], $aggregate_name => $res['aggregations'][$aggregate_name]];
266 }
267 }
model/EsModel
1 <?php
2
3 namespace app\models;
4
5 use app\common\Elasticsearch;
6
7 class EsModel extends Elasticsearch{
8
9 // _index _type _id 定义
10 // https://www.elastic.co/guide/cn/elasticsearch/guide/current/_Document_Metadata.html
11 public static $index_db_name = '';
12 public static $type_tb_name = '';
13
14 // 索引名相当于库名
15 public static function index(){
16 return self::$index_db_name;
17 }
18
19 // 类别名相当于表名
20 public static function type(){
21 return self::$type_tb_name;
22 }
23
24 // 属性
25 public function attributes(){
26 $mapConfig = self::mapConfig();
27 return array_keys($mapConfig[self::$type_tb_name]['properties']);
28 }
29
30 // 映射
31 public static function mapping(){
32 $mapConfig = self::mapConfig();
33 return [
34 static::type() => $mapConfig[self::$type_tb_name]
35 ];
36 }
37
38 // 更新映射
39 public static function updateMapping(){
40 $db = self::getDb();
41 $command = $db->createCommand();
42 if(!$command->indexExists(self::index())){
43 $command->createIndex(self::index());
44 }
45 $res = $command->setMapping(self::index(), self::type(), self::mapping());
46 return $res;
47 }
48
49 // 创建索引
50 public static function createIndex(){
51 $db = static::getDb();
52 $command = $db->createCommand();
53 $command->createIndex(static::index(), [
54 'settings' => [
55 'index' => [
56 // 指定新索引的时候不会立即生效,1s之后刷新生效 默认是1s
57 'refresh_interval' => '1s',
58 // 索引分片个数 默认5片
59 'number_of_shards' => 5,
60 // 每个分片副本个数 默认1个
61 'number_of_replicas' => 1
62 ]
63 ],
64 'mappings' => static::mapping(),
65 //'warmers' => [ /* ... */ ],
66 //'aliases' => [ /* ... */ ],
67 //'creation_date' => '...'
68 ]);
69 }
70
71 // 删除索引
72 public static function deleteIndex(){
73 $db = static::getDb();
74 $command = $db->createCommand();
75 $res = $command->deleteIndex(static::index(), static::type());
76 return $res;
77 }
78 }
controller调用
1 // 调用ES 2 $model = new \app\models\EsModel(); 3 $model::$index_db_name = 'index_name'; 4 $model::$type_tb_name = 'type_name';
