ElasticSearch学习笔记
实操篇
使用Restful接口访问ES
- ES的接口语法
- 其中:
参数 | 解释 |
---|---|
VERB | 适当的 HTTP 方法或谓词 : GET、 POST、 PUT、 HEAD 或者 DELETE。 |
PROTOCOL | http 或 https(如果你在 Elasticsearch 前面有一个 https 代理) |
HOST | Elasticsearch 集群中任意节点的主机名,或者用 localhost 代表本地机器上的节点。 |
PORT | 运行 Elasticsearch HTTP 服务的端口号,默认是 9200 。 |
PATH | API 的终端路径(例如 _count 将返回集群中文档数量)。Path 可能包含多个组件,例如: _cluster/stats 和 _nodes/stats/jvm |
QUERY_STRING | 任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读) |
BODY | 一个 JSON 格式的请求体 (如果请求需要的话) |
- 下面我们通过使用 Kibana 的 DevTools 操作 ES
ES集群信息查看
- 查看cluster集群状态:
GET /_cat/health?v
- 查看node状态信息:
GET /_nodes/stats/process/?pretty
- 查看node的机器使用情况:
GET /_cat/nodes?v
- 查看集群索引信息:
GET /_cat/indices?v
# 只显示状态为黄色的
GET /_cat/indices?v&health=yellow
# 根据文档数量降序排列
GET /_cat/indices?v&s=docs.count:desc
# 显示每个索引占的内存
GET /_cat/indices?v&h=i,tm&s=tm:desc
- node使用的磁盘情况:
GET /_cat/allocation?v
- 查看文档数:
GET /_cat/count?v
- fieldData的大小:
GET /_cat/fielddata?v
# 查看指定的字段
GET /_cat/fielddata?v&fields=kkb
- 主节点信息:
GET /_cat/master?v
- 查看分片的恢复信息:里边包涵了很多信息,文件信息和translog信息等
GET /_cat/recovery?v
- 查看集群信息列表:返回所有的信息查询url
GET /_cat/
索引库维护
创建索引index和映射mapping
- 基本语法:
PUT /{index}
{
# settings
# mappings
}
- 例子:
PUT /blog1
{
"settings": {
"index": {
# 分片数量
"number_of_shards": "5",
# 副本集数量
"number_of_replicas": "1"
}
},
"mappings": {
"properties": {
"id": {
"type": "long",
"store": true,
"index": true
},
"title": {
"type": "text",
"store": true,
"index": true,
"analyzer": "standard"
},
"content": {
"type": "text",
"store": true,
"index": true,
"analyzer": "standard"
}
}
}
}
Field的属性
- type:数据类型。
- 数值类型:integer、long、float等
- 字符串类型:keyword、text
- keyword:不分词,可以创建索引。例如身份证号、订单号等。不需要分词,可以创建索引。就会把field中的内容作为一个完整的关键词,创建索引。
- text:分词的数据类型。可以指定分词器。(稍后会讲)
- 日期类型等
- store:是否存储。
- 取决于是否展示给用户看,或者业务需要。
- 不存储不影响分词,不影响查询,可以节约存储空间。不存储的后果就是无法展示。
- index:是否索引。
- 如果不分词也可以选择是否创建索引,不创建索引就无法查询此字段的内容。
- analyzer:定义分词器。
- 默认是标准分词器(standardAnalyzer)
创建索引后设置Mapping
- 不设置mapping也可以向索引库中添加文档,es会根据文档的数据自动识别field的类型。
- 推荐先设置好mapping然后再添加数据。因为es识别的数据类型未必是我们想要的数据类型。
- mapping一旦设置无法修改,只能新增。
- 设置mapping语法:
POST /{index}/{type}/_mapping
{
"{type}": {
"properties": {
"{field}": {
# key-value
}
}
}
}
- 例子:
POST /blog1/_mapping
{
"properties": {
"id": {
"type": "long",
"store": true,
"index": true
},
"title": {
"type": "text",
"store": true,
"index": true,
"analyzer": "standard"
},
"content": {
"type": "text",
"store": true,
"index": true,
"analyzer": "standard"
}
}
}
删除索引库
- 语法:
文档维护
创建文档
- 基本语法:
PUT /{index}/{type}/{_id}
{
# 文档内容 key-value
}
- 例子:
POST /blog1/_doc/1
{
"id":1,
"title":"ElasticSearch是一个基于Lucene的搜索服务器",
"content":"它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。 Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引 擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。"
}
- es7中type固定为“_doc”
- _id:文档的唯一编号和文档中id属性没有任何关系。通常让二者保持一致。
修改文档
- 在es中所谓的修改其实就是先删除后添加。
- 例子:
POST /blog1/_doc/1
{
"id":1,
"title":"【修改】ElasticSearch是一个基于Lucene的搜索服务器",
"content":"【修改】它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。 Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引 擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。"
}
删除文档
- 语法:
- 例子:
DELETE /blog1/_doc/1
根据id查询文档
- 语法:
- 例子:
GET /blog1/_doc/1
使用查询表达式(Query DSL)
- 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html
- 查询语法:
GET /{index}/{type}/_search
{
"query": {
"查询方法": "查询条件"
}
}
- 查询全部文档:
GET /{index}/{type}/_search
{
"query": {
"match_all": {}
}
}
根据关键词(term)查询
- 需要指定在哪个field上查询,查询哪个关键词。
- 例子:
GET /blog1/_search
{
"query": {
"term": {
"title": "搜索"
}
}
}
使用query_string查询,或者match查询
- 带分词的查询,先对查询内容进行分词处理,然后基于分词的结果查询。
- 例子:
GET /blog1/_search
{
"query": {
"query_string": {
"default_field": "title",
"query": "搜索服务器"
}
}
}
- 相关度排序:
- TF:关键词出现的频率,TF 越高相关度越高,越重要。
- DF:关键词在多个文档中出现的频率,DF越高越不重要,相关度越低。
- 可以设置boots值调节field权重。
在查询时,根据关键词计算每个文档的相关度,给每个文档打分,然后按得分降序排列。
multi_match查询
GET /blog1/_search
{
"query": {
"multi_match": {
"query": "李四",
"fields": ["name", "address"]
}
}
}
bool查询
- bool (布尔)过滤器:这是个复合过滤器(compound filter) ,它可以接受多个其他过滤器作为参数,并将这些过滤器结合成各式各样的布尔(逻辑)组合。
GET /{index}/_search
{
"query": {
"bool": {
"must": [
{}
],
"should": [
{}
],
"must_not": [
{}
],
"filter": {}
}
}
}
- should:对于一个文档,查询列表中,只要有一个查询匹配,那么这个文档就被看成是匹配的。
- must_not:对于一个文档,查询列表中的的所有查询都必须都不为真,这个文档才被认为是匹配的。
- filter:从ES5.0开始过滤条件需要在添加在bool查询中,filter条件中支持查询条件中的所有查询方 法。filter可以配置多个,从而实现多个过滤条件同时生效。
- 在不需要相关度排序的情况下,推荐使用filter查询,提高查询速度。filter不对文档打分。
其它查询
- terms:跟term有点类似,但可以同时指定多个条件,相当于union all的作用,汇聚所有查询的值
{
"query": {
"term": {"date": ["2018-09-01","2018-10-03"]}
}
}
- range:范围查询,指定一个范围查询
{
"query": {
"range":{
"age":{ //查询age字段
"gte":60, //大于等于60
"lt":70 //小于70
}
}
}
}
- exists:所有值不为空,相当于 is not null
- missing:所有值为空,相当于 is null
{
"query": {
"missing":{
"field":"age" // age字段为空的所有数据
}
}
}
- prefix:前缀搜索
{
"query": {
"prefix":{
"_id":1 // _id 以1开头的数据,不适合值为中文
}
}
}
- phrase_match:篇幅匹配,不拆词匹配,寻找邻近的几个单词,我理解为精确短语匹配,即查找的短语不会被分词查找
{
"query": {
"match_phrase":{
"content":"china reference" // content中包含china reference而不是chian 或 reference
}
}
}
- 其他方法:
search_type,match_phrase,fuzzy,and,or,not,limit,size,from,to,gt,gte,lt,lte,field,fields,aggs,count,sum,min,max,avg
高级使用
Mapping映射
- 如果不添加,生成时系统会默认指定mapping(映射)结构,检索时系统会猜测你想要的类型,如果对系统反馈的不满意,我们就可以手动设置
- 添加:
PUT /kkb1
{
"mappings": {
"properties": {
"age": {"type": "long"}
}
}
}
// 注意——在ES 7.0.0之前,mappings定义需要一个类型名,如:
{
"mappings": {
"java": {
"properties": {
"age": {"type": "long"}
}
}
}
}
- 获取:
GET /kkb1/_mapping
- 配置 mapping 是对索引字段的映射配置,可以直接对索引文档字段设置。
{
// 数据类型:一般文本使用text,表示可分词进行模糊查询;keyword表示无法被分词(不需要执行分词器),用于精确查找
"type": "text",
// 分词器:一般使用最大分词 ik_max_word
"analyzer": "ik_max_word",
// 字段标准化规则:如把所有字符转为小写
"normalizer": "normalizer_name",
// 字段权重:用于查询时评分,关键字段的权重就会高一些,默认都是1;另外查询时可临时指定权重
"boost": 1.5,
// 清理脏数据:字符串会被强制转换为整数,浮点数被强制转换为整数,默认为true
"coerce": true,
// 自定_all字段:指定某几个字段拼接成自定义
"copy_to": "field_name",
// 加快排序、聚合操作,但需要额外存储空间,默认true,对于确定不需要排序和聚合的字段可false
"doc_values": true,
// 新字段动态添加 true-无限制 false-数据可写入但该字段不保留,strict-无法写入抛异常
"dynamic": true,
// 是否会被索引,但都会存储;可以针对一整个_doc
"enabled": true,
// 针对text字段加快排序和聚合(doc_values对text无效);此项官网建议不开启,非常消耗内存
"fielddata": false,
// 是否开启全局预加载,加快查询;此参数只支持text和keyword,keyword默认可用,而text需要设置fielddata属性
"eager_global_ordinals": true,
// 格式化 此参数代表可接受的时间格式 3种都接受
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis",
// 指定字段索引和存储的长度最大值,超过最大值的会被忽略
"ignore_above": 100,
// 插入文档时是否忽略类型,默认是false,类型不一致无法插入
"ignore_malformed": false,
// 4个可选参数:分词字段默认是positions,其他的默认是docs
// docs: 索引文档号
// freqs: 文档号+词频
// positions: 文档号+词频+位置,通常用来做距离查询
// offsets: 文档号+词频+位置+偏移量,通常被使用在高亮字段
"index_options": "docs",
// 该字段是否会被索引和可查询,默认 true
"index": true,
// 可以对一个字段提供多种索引模式,使用text类型做全文检索,也可使用keyword类型做聚合和排序
"fields": {"raw": {"type": "keyword"}},
// 用于标准化文档,以便查询时计算文档的相关性,建议不开启
"norms": true,
// 可以让值为null的字段显式得可索引、可搜索
"null_value": "NULL",
// 词组查询时可以跨词查询,既可变为分词查询,默认100
"position_increment_gap": 0,
// 嵌套属性,例如该字段是音乐,音乐还有歌词,类型,歌手等属性
"properties": {},
// 查询分词器,一般情况和analyzer对应
"search_analyzer": "ik_max_word",
// 用于指定文档评分模型,参数有三个
// BM25:ES和Lucene默认的评分模型
// classic:TF/IDF评分
// boolean:布尔模型评分
"similarity": "BM25",
// 默认情况false,其实并不是真没有存储,_source字段里会保存一份原始文档
// 在某些情况下,store参数有意义,比如一个文档里面有title、date和超大的content字段,如果只想获取title和date
"store": true,
// 默认不存储向量信息
// 支持参数yES(term存储),with_positions(term + 位置),with_offsets(term + 偏移量),with_positions_offsets(term + 位置 + 偏移量)
// 对快速高亮fast vector highlighter能提升性能,但开启又会加大索引体积,不适合大数据量用
"term_vector": "no"
}
索引模板
介绍
- 当索引和文档变多后,我们不能每一个都是手动的设置,这就用到了模板。
- 模板包涵了settings和mappings信息,通过模式匹配的方式使得多个索引重用一个模板。
- settings:索引的相关配置信息,例如分片数、副本数、tranlog、refresh等,一般使用 elasticsearch.yml 中配置。
- mappings:说明信息,索引是否需要检索,字段类型等等,就是映射的内容设置。
使用
- 创建模板:
POST /_template/tkkb1?include_type_name=true
{
"index_patterns": ["kkbt*"],
"settings": {
"index.number_of_shards": 2,
"number_of_replicas": 1
},
"mappings": {
"java": {
"properties": {
"age": {
"type": "long"
},
"info": {
"search_analyzer": "ik_max_word",
"analyzer": "ik_max_word",
"type": "text"
}
}
}
}
}
- 创建索引:
PUT /kkbt1/java/1
{
"age": "18",
"info": "我是程序员"
}
- 查看索引信息:
GET /kkbt1/_settings
GET /kkbt1/_mapping
- 删除模板:
DELETE /_template/tkkb1
别名
介绍
- 类似于数据库的视图,单索引视图可以直接操作,多索引的只能查。
单索引
POST /_aliases
{
"actions": [
{
"add": {
"index": "kkb1",
"alias": "kkb"
}
}
]
}
- 别名查询:
GET /kkb1/_alias
多索引
POST /_aliases
{
"actions": [
{
"add": {
"index": "kkb1",
"alias": "kkb"
}
},
{
"add": {
"index": "kkbt1",
"alias": "kkbt"
}
}
]
}
- 别名查询:
GET /kkb1/_alias
GET /kkbt1/_alias
- 删除别名:
POST /_aliases
{
"actions": [
{
"remove": {
"index": "kkb1",
"alias": "kkb"
}
}
]
}
- 需要一个一个删除,更新的话先删除后添加。
缓存管理
- ES是先写入缓存然后再到磁盘,使用过程中会用到一些缓存类的操作。
- 强制刷新:虽然ES设置是1秒写一次内存segment,但是可能出现延时。
// 单个
POST /kkb1/_refresh
// 全量
POST /_refresh
- 清理缓存:缓存过大会引起效率低等情况。
// 单个
POST /kkb1/_cache/clear
// 全量
POST /_cache/clear
分词
- 使用:在创建mapping的时候对指定的字段添加分词器。
自带分词器
- ES 有自带的分词器,但是对中文的支持很不友好,下面简单说下自带的几个分词器,可以通过analyzer 字段来选择分词器。
- standard:默认的分词器,对词汇转换成小写切割,去掉标点和助词(a/an/the),支持中文但是是根据文字单个切分。结果是中文直接按照每个字拆分。
POST /_analyze
{
"analyzer": "standard",
"text": "我是一个程序员,咿呀咿呀哟!"
}
- simple:通过非字母字符来切割,会过滤掉数字。这个分词粒度比较大,如果不需要太细的搜索,力度大了效率会高,具体看实际场景来使用
POST /_analyze
{
"analyzer": "simple",
"text": "我是一个程序员,咿呀咿呀哟!"
}
- 更多内置的分词器,详见官方地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html
analysis-ik
- 官方地址:https://github.com/medcl/elasticsearch-analysis-ik
- 使用:ik_smart,ik_max_word是ik 的两种分词器
- ik_max_word:会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合。
- ik_smart:会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。
POST /_analyze
{
"analyzer": "ik_smart",
"text": "我是一个程序员,咿呀咿呀哟!"
}
- 创建ik的mapping 对kkb索引下的text字段设置分词器
POST /kkb/java/_mapping?include_type_name=true
{
"properties": {
"info": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
- 存放多个文档值:
POST /kkb/java/1
{
"info": "我是一个程序员,咿呀咿呀呀!"
}
POST /kkb/java/2
{
"info": "我是一个产品,咿呀咿呀呀!"
}
POST /kkb/java/3
{
"info": "程序员要揍产品!"
}
- 查询:
POST /kkb/fulltext/_search
{
"query": {
"match": {
"info": "程序"
}
}
}
- 结果会根据词库去匹配去匹配,词库配置在IKAnalyzer.cfg.xml 位置
{conf}/analysis-ik/config/IKAnalyzer.cfg.xml
或者jar里 {plugins}/elasticsearch-analysis-ik- */config/IKAnalyzer.cfg.xml
- 文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM
"http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!-- 用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
<!-- 用户可以在这里配置自己的扩展停止词字典 -->
<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
<!-- 用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">location</entry>
<!-- 用户可以在这里配置远程扩展停止词字典 -->
<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
</properties>
- 可能遇到的错误:java.net.SocketPermission 网络访问权限问题 修改jdk/lib/java.policy文件
permission java.net.SocketPermission "*", "connect,resolve";
# 重启
高亮查询highlight
- ES有三种高亮:
- plain highlight(默认)
- posting highlight(性能) 对磁盘消耗少,切割的片大
- fast vector highlight(文件) 对大文件检索快
POST /kkb/fulltext/_search
{
"query": {
"match": {
"info": "程序"
}
},
"highlight": {
"pre_tags": ["<tag1>", "<tag2>"],
"post_tags": ["</tag1>", "</tag2>"],
"fields": {
"info": {}
}
}
}
SpringBoot中使用ES
- 利用ES搜索商品信息
创建项目
- 定义一个SpringBoot工程 elasticsearch-demo。
- 引入依赖:
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-es-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
代码实现
- 启动类:
@SpringBootApplication
public class SearchServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SearchServiceApplication.class, args);
}
}
- yml配置文件:
server:
port: 8000
spring:
application:
name: es-demo
data:
# 配置ES集群地址
elasticsearch:
cluster-name: yw-es
cluster-nodes: 192.168.254.120:9300,192.168.254.102:9300,192.168.254.104:9300
- repository和pojo:
// 商品信息
@Data
@Accessors(chain = true)
@Document(indexName = "supergo", type = "goods")
public class Goods {
@Id
@Field(type = FieldType.Long, store = true)
private long id;
@JsonProperty(value = "goods_name")
@Field(type = FieldType.Text, store = true, analyzer = "ik_max_word")
private String goodsName;
@JsonProperty(value = "seller_id")
@Field(type = FieldType.Keyword, store = true)
private String sellerId;
@JsonProperty(value = "nick_name")
@Field(type = FieldType.Keyword, store = true)
private String nickName;
@JsonProperty(value = "brand_id")
@Field(type = FieldType.Long, store = true)
private long brandId;
@JsonProperty(value = "brand_name")
@Field(type = FieldType.Keyword, store = true)
private String brandName;
@JsonProperty(value = "goods_name")
@Field(type = FieldType.Long, store = true)
private long category1Id;
@JsonProperty(value = "goods_name")
@Field(type = FieldType.Keyword, store = true)
private String cname1;
@JsonProperty(value = "category2_id")
@Field(type = FieldType.Long, store = true)
private long category2Id;
@Field(type = FieldType.Keyword, store = true)
private String cname2;
@JsonProperty(value = "category3_id")
@Field(type = FieldType.Long, store = true)
private long category3Id;
@Field(type = FieldType.Keyword, store = true)
private String cname3;
@JsonProperty(value = "small_pic")
@Field(type = FieldType.Keyword, store = true, index = false)
private String smallPic;
@Field(type = FieldType.Float, store = true)
private double price;
}
// GoodsRepository
public interface GoodsRepository extends ElasticsearchCrudRepository<Goods, Long>{
}
- 控制器:
// 搜索结果
@Data
@Accessors(chain = true)
public class SearchResult {
private List<Goods> goodsList;
private List<?> aggs;
private Integer page;
private Integer totalPage;
}
// api接口
@RestController
public class SearchController {
@Resource
private SearchService searchService;
@GetMapping("/search")
public SearchResult search(@RequestParam(required = true) String keyword,
@RequestParam(required = false) String ev,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "50") int rows) {
// 将ev转换成List类型
List<Map> filters = null;
if (StringUtils.isNotBlank(ev)) {
String[] strings = ev.split("\\|");
filters = Stream.of(strings).map(e -> {
Map<String, String> fs = new HashMap<>(2);
fs.put("key", e.split("-")[0]);
fs.put("value", e.split("-")[1]);
return fs;
}).collect(Collectors.toList());
}
return searchService.search(keyword, filters, page, rows);
}
}
- service层:
@Service
public class SearchService {
@Resource
private ElasticsearchTemplate elasticsearchTemplate;
public SearchResult search(String queryString, List<Map> filters, int page, int rows) {
//创建一个查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.queryStringQuery(queryString).defaultField("goods_name"))
//设置分页条件
.withPageable(PageRequest.of(page, rows))
//设置聚合条件
.addAggregation(AggregationBuilders.terms("brand_aggs").field("brand_name"))
.addAggregation(AggregationBuilders.terms("category_aggs").field("cname3"))
//设置高亮显示
.withHighlightFields(new HighlightBuilder.Field("goods_name").preTags("<em>").postTags("</em>"));
//设置过滤条件
if (filters != null && !filters.isEmpty()) {
for (Map filer : filters) {
queryBuilder.withFilter(QueryBuilders.termQuery((String) filer.get("key"), filer.get("value")));
}
}
//创建查询对象
NativeSearchQuery query = queryBuilder.build();
//-----------------
//执行查询
AggregatedPage<Goods> aggregatedPage = elasticsearchTemplate.queryForPage(query, Goods.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> clazz, Pageable pageable) {
List<Goods> goodsList = new ArrayList<>();
SearchHits searchHits = searchResponse.getHits();
searchHits.forEach(hits -> {
Goods goodsEntity = new Goods();
goodsEntity.setId((Long) hits.getSourceAsMap().get("id"))
.setGoodsName((String) hits.getSourceAsMap().get("goods_name"))
.setSellerId((String) hits.getSourceAsMap().get("seller_id"))
.setNickName((String) hits.getSourceAsMap().get("nick_name"))
.setBrandId((Integer) hits.getSourceAsMap().get("brand_id"))
.setBrandName((String) hits.getSourceAsMap().get("brand_name"))
.setCategory1Id((Integer) hits.getSourceAsMap().get("category1_id"))
.setCname1((String) hits.getSourceAsMap().get("cname1"))
.setCategory2Id((Integer) hits.getSourceAsMap().get("category2_id"))
.setCname2((String) hits.getSourceAsMap().get("cname2"))
.setCategory3Id((Integer) hits.getSourceAsMap().get("category3_id"))
.setCname3((String) hits.getSourceAsMap().get("cname3"))
.setSmallPic((String) hits.getSourceAsMap().get("small_pic"))
.setPrice((Double) hits.getSourceAsMap().get("price"));
//取高亮结果
HighlightField highlightField = hits.getHighlightFields().get("goods_name");
if (highlightField != null) {
String hl = highlightField.getFragments()[0].string();
goodsEntity.setGoodsName(hl);
}
goodsList.add(goodsEntity);
});
return new AggregatedPageImpl<>((List<T>) goodsList, pageable, searchHits.totalHits, searchResponse.getAggregations());
}
});
//取商品列表
List<Goods> content = aggregatedPage.getContent();
//取聚合结果
Terms termBrand = (Terms) aggregatedPage.getAggregation("brand_aggs");
List<String> brandAggsList = termBrand.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
Map brandMap = new HashMap(4);
brandMap.put("name", "品牌");
brandMap.put("content", brandAggsList);
brandMap.put("filed", "brand_name");
Terms termCategory = (Terms) aggregatedPage.getAggregation("category_aggs");
List<String> categoryAggsList = termCategory.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
Map catMap = new HashMap(4);
catMap.put("name", "分类");
catMap.put("content", categoryAggsList);
catMap.put("filed", "cname3");
List<Map> aggsList = new ArrayList<>();
aggsList.add(brandMap);
aggsList.add(catMap);
//取总记录数
int totalPages = aggregatedPage.getTotalPages();
//封装成一个SearchResult对象
SearchResult searchResult = new SearchResult();
searchResult.setGoodsList(content);
searchResult.setAggs(aggsList);
searchResult.setPage(page);
searchResult.setTotalPage(totalPages);
//返回结果
return searchResult;
}
}