Elasticsearch入门

简介

https://www.elastic.co/cn/what-is/elasticsearch
全文搜索属于最常见的需求, 开源的 Elasticsearch 是目前全文搜索引擎的首选。
它可以快速地储存、 搜索和分析海量数据。 维基百科、 Stack Overflow、 Github 都采用它
Elastic 的底层是开源库 Lucene。 但是, 你没法直接用 Lucene, 必须自己写代码去调用它的接口。 Elastic 是 Lucene 的封装, 提供了 REST API 的操作接口, 开箱即用。
REST API: 天然的跨平台。
官方文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
官方中文: https://www.elastic.co/guide/cn/elasticsearch/guide/current/foreword_id.html
社区中文:
https://es.xiaoleilu.com/index.html
http://doc.codingdict.com/elasticsearch/0/

一、 基本概念

1.1、 Index(索引)

动词, 相当于 MySQL 中的 insert;
名词, 相当于 MySQL 中的 Database

1.2、 Type(类型)

在 Index(索引) 中, 可以定义一个或多个类型。
类似于 MySQL 中的 Table; 每一种类型的数据放在一起;

1.3、 Document(文档)

保存在某个索引(Index) 下, 某种类型(Type) 的一个数据(Document) , 文档是 JSON 格式的, Document 就像是 MySQL 中的某个 Table 里面的内容;

1.4、 倒排索引机制

可以参考这一篇博客:https://blog.csdn.net/fjxcsdn/article/details/103108690

二、 Docker 安装 Es

2.1、 下载镜像文件

  1. 存储和检索数据
    docker pull elasticsearch:7.4.2
  2. 可视化检索数据
    docker pull kibana:7.4.2

2.2、 创建实例

2.2.1、 ElasticSearch

  1. 创建文件
    mkdir -p /mydata/elasticsearch/config
    mkdir -p /mydata/elasticsearch/data
  2. 写配置文件
    echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
  3. 设置权限
    chmod -R 777 /mydata/elasticsearch/ 保证权限
  4. 创建并启动docker容器
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

以后再外面装好插件重启即可;
特别注意:
-e ES_JAVA_OPTS=“-Xms64m -Xmx256m” \ 测试环境下, 设置 ES 的初始内存和最大内存, 否则导致过大启动不了 ES
2、 Kibana

  • 编辑配置文件
vim /data/kibana/config/kibana.yml


#Kibana的映射端口
server.port: 5601 
#网关地址
server.host: "0.0.0.0"  
#Kibana实例对外展示的名称
server.name: "kibana-172.19.0.2"   
#Elasticsearch的集群地址,也就是说所有的集群IP,之前查到的ip
elasticsearch.hosts: ["http://172.19.0.2:9200"]    
#设置页面语言,中文使用zh-CN,英文使用en
i18n.locale: "zh-CN"        
xpack.monitoring.ui.container.elasticsearch.enabled: true


启动
docker run -d -p 5601:5601 -v /data/kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml --network es-net --name kibana kibana:7.4.2
一定改为自己虚拟机的

三、 初步检索

3.1、 _cat

  1. 查看所有节点
    GET /_cat/nodes
  2. 查看es健康状况
    GET /_cat/health
  3. 查看主节点
    GET /_cat/master
  4. 查看索引
    GET /_cat/indices

3. 2、 索引一个文档(保存)

保存一个数据, 保存在哪个索引的哪个类型下, 指定用哪个唯一标识
PUT customer/external/1
在 customer 索引下的 external 类型下保存 1 号数据为

PUT customer/external/1{
    "name":"hollow word"
} 

PUT 和 POST 都可以,

  • POST 新增。 如果不指定 id, 会自动生成 id。 指定 id 就会修改这个数据, 并新增版本号
  • PUT 可以新增可以修改。 PUT 必须指定 id; 由于 PUT 需要指定 id, 我们一般都用来做修改操作, 不指定 id 会报错。
    第一次发put请求新增
    在这里插入图片描述
    第二次发put请求就是修改
    在这里插入图片描述
    post请求
    在这里插入图片描述

3.3 查询一个文档

get /索引/类型/记录id
在这里插入图片描述

3.4 更新一个文档

  1. 方式一
POST customer/external/1/_update
{
"doc":{
"name": "John Doew"
}
} 
  1. 方式二
POST customer/external/1
{
"name": "John Doe2"
} 
  1. 方式三
PUT customer/external/1
{
"name": "John Doe"
}

不同: _update 操作会对比源文档数据, 如果相同不会有什么操作, 文档 version 不增加
适用场景:

  • 对于大并发更新, 不带 update;
  • 对于大并发查询偶尔更新, 带 update; 对比更新, 重新计算分配规则。

3.5 删除操作

  1. 删除文档
DELETE customer/external/1
  1. 删除索引
DELETE customer

3.6、 bulk 批量 API

jsonPOST customer/external/_bulk
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }

语法格式:

{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n

复杂实例:

POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}{ "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }}
{ "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123"} }
{ "doc" : {"title" : "My updated blog post"} }

bulk API 以此按顺序执行所有的 action(动作) 。 如果一个单个的动作因任何原因而失败,
它将继续处理它后面剩余的动作。 当 bulk API 返回时, 它将提供每个动作的状态(与发送的顺序相同) , 所以您可以检查是否一个指定的动作是不是失败了

3.7、 样本测试数据

我准备了一份顾客银行账户信息的虚构的 JSON 文档样本。 每个文档都有下列的

schema
(模式) :
{
"account_number": 0,
"balance": 16623,
"firstname": "Bradshaw",
"lastname": "Mckenzie",
"age": 29,
"gender": "F",
"address": "244 Columbus Place",
"employer": "Euron",
"email": "bradshawmckenzie@euron.com",
"city": "Hobucken",
"state": "CO"
}

https://raw.githubusercontent.com/elastic/elasticsearch/7.4/docs/src/test/resources/accounts.json
导入测试数据
POST bank/account/_bulk

四、 进阶检索

4.1、 SearchAPI

ES 支持两种基本方式检索 :

  1. 一个是通过使用 REST request URI 发送搜索参数(uri+检索参数)
  2. 另一个是通过使用 REST request body 来发送它们(uri+请求体)1) 、 检索信息

4.1.1、 检索信息

  1. 一切检索从_search 开始
  • GET bank/_search: 检索 bank 下所有信息, 包括 type 和 docs
  • GET bank/_search?q=*&sort=account_number:asc 请求参数方式检索
    响应结果解释:
字段解释
tookElasticsearch 执行搜索的时间( 毫秒)
time_out告诉我们搜索是否超时
shards告诉我们多少个分片被搜索了, 以及统计了成功/失败的搜索分片
hits搜索结果
hits.total搜索结果
hits.hits实际的搜索结果数组( 默认为前 10 的文档)
sort -结果的排序 key( 键) ( 没有则按 score 排序)
score相关性得分(全文检索用)
max_score最高得分( 全文检索用)
  1. url+请求体的形式进行检索
GET bank/_search
{
	"query": {
		"match_all": {}
	},
	"sort": [
		{
			"account_number": {
				"order": "desc"
			}
		}
	]
}

HTTP 客户端工具( POSTMAN) , get 请求不能携带请求体, 我们变为 post 也是一样的我们 POST 一个 JSON 风格的查询请求体到 _search API。
需要了解, 一旦搜索的结果被返回, Elasticsearch 就完成了这次请求, 并且不会维护任何服务端的资源或者结果的 cursor( 游标)

4.2、 Query DSL

4.2.1 、 基本语法格式

Elasticsearch 提供了一个可以执行查询的 Json 风格的 DSL( domain-specific language 领域特定语言) 。 这个被称为 Query DSL。 该查询语言非常全面, 并且刚开始的时候感觉有点复杂,
真正学好它的方法是从一些基础的示例开始的。
一个查询语句的典型结构

{
	QUERY_NAME: {
		ARGUMENT: VALUE,
		ARGUMENT: VALUE,...
	}
}

如果是针对某个字段, 那么它的结构如下:

{
	QUERY_NAME: {
	FIELD_NAME: {
		ARGUMENT: VALUE,
		ARGUMENT: VALUE,...
	}
	}
} 

示例模板

GET bank/_search
{
	"query": {
		"match_all": {}
	},
	"from": 0,
	"size": 5,
	"sort": [
	{
		"account_number": {
		"order": "desc"
		}
	}
	]
} 

解释

  1. query 定义如何查询,
  2. match_all 查询类型【代表查询所有的所有】 , es 中可以在 query 中组合非常多的查询类型完成复杂查询
  3. 除了 query 参数之外, 我们也可以传递其它的参数以改变查询结果。 如 sort, size
  4. from+size 限定, 完成分页功能
  5. sort 排序, 多字段排序, 会在前序字段相等时后续字段内部排序, 否则以前序为准

4.2.2 返回部分字段

GET bank/_search
{
	"query": {
	"match_all": {}
	},
	"from": 0,
	"size": 5,
	"_source": ["age","balance"]
}

4.2.3 match【 匹配查询】

  1. 基本类型( 非字符串) , 精确匹配
GET bank/_search
{
	"query": {
	"match": {
		"account_number": "20"
	}
	}
}

match 返回 account_number=20 的

  1. 字符串, 全文检索
GET bank/_search
{
	"query": {
	"match": {
		"address": "mill"
	}
	}
} 

最终查询出 address 中包含 mill 单词的所有记录,match 当搜索字符串类型的时候, 会进行全文检索, 并且每条记录有相关性得分。

  1. 字符串, 多个单词( 分词+全文检索)
GET bank/_search
{
	"query": {
	"match": {
		"address": "mill road"
	}
	}
} 

最终查询出 address 中包含 mill 或者 road 或者 mill road 的所有记录, 并给出相关性得分

4.2.4 、 match_phrase【 短语匹配】

将需要匹配的值当成一个整体单词( 不分词) 进行检索

GET bank/_search
{
	"query": {
	"match_phrase": {
		"address": "mill road"
	}
	}
} 

查出 address 中包含 mill road 的所有记录, 并给出相关性得分

4.2.5.5 、 multi_match【 多字段匹配】

GET bank/_search
{
	"query": {
	"multi_match": {
		"query": "mill",
		"fields": ["state","address"]
	}
	}
}

state 或者 address 包含 mill

4.2.5.6、 bool【 复合查询】

bool 用来做复合查询:
复合语句可以合并 任何 其它查询语句, 包括复合语句, 了解这一点是很重要的。 这就意味着, 复合语句之间可以互相嵌套, 可以表达非常复杂的逻辑。

  1. must: 必须达到 must 列举的所有条件
get bank/_search
{
  "query":{
    "bool": {
      "must": [
        {
          "match": {
            "address": "mill"
          }
        },
        {
          "match": {
            "gender": "M"
          }
        }
      ]
    }
  }
}
  

查找address=mill同时gender=M的所有结果

  1. should: 应该达到 should 列举的条件, 如果达到会增加相关文档的评分, 并不会改变询的结果。 如果 query 中只有 should 且只有一种匹配规则, 那么 should 的条件就会被作为默认匹配条件而去改变查询结果
get bank/_search
{
  "query":{
    "bool": {
      "must": [
        {
          "match": {
            "address": "mill"
          }
        },
        {
          "match": {
            "gender": "M"
          }
        }
      ],
       "should": [
        {
          "match": {
            "lastname": "wallace"
          }
        }
      ]
    }
  }
}
  1. must_not 必须不是指定的情况
GET bank/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "address": "mill"
          }
        },
        {
          "match": {
            "gender": "M"
          }
        }
      ]
    }
  }
}

查询所有address不包含mill同时gender不包含M的所有结果集

4.2.5.7 filter【结果过滤】

并不是所有的查询都需要产生分数, 特别是那些仅用于 “filtering”(过滤) 的文档。 为了不计算分数 Elasticsearch 会自动检查场景并且优化查询的执行

GET bank/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "age": {
            "gt": 10,
            "lt": 100
          }
        }
      }
    }
  }
}

查询age在10到100之间的所有结果

4.2.5.8 term

和 match 一样。 匹配某个属性的值。 全文检索字段用 match, 其他非 text 字段匹配用 term。

GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "age": {
              "value": 28
            }
          }
        }
      ]
    }
  }
}

## 精确查询
GET bank/_search
{
  "query": {
    "match": {
      "address.keyword": "789 Madison"
    }
  }
}
 
## 精确查询
GET bank/_search
{
  "query": {
    "match": {
      "address": "789 Madison"
    }
  }
}  

4.2.5.9 aggregations( 执行聚合)

聚合提供了从数据中分组和提取数据的能力。 最简单的聚合方法大致等于 SQL GROUP BY 和 SQL 聚合函数。 在 Elasticsearch 中, 您有执行搜索返回 hits( 命中结果) , 并且同时返回聚合结果, 把一个响应中的所有 hits( 命中结果) 分隔开的能力。 这是非常强大且有效的,您可以执行查询和多个聚合, 并且在一次使用中得到各自的( 任何一个的) 返回结果, 使用一次简洁和简化的 API 来避免网络往返。

  1. 搜索 address 中包含 mill 的所有人的年龄分布以及平均年龄, 但不显示这些人的详情。
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "ageAgg": {
      "terms": { 
        "field": "age",
        "size": 10
      }
    },
    "avgAge":{
      "avg": {
        "field": "age"
      }
    },
    "aveBalance":{
      "avg": {
        "field": "balance"
      }
    }
  }
}
  1. 按照年龄聚合, 并且请求这些年龄段的这些人的平均薪资
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "balanceAvg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}
  1. 复杂: 查出所有年龄分布, 并且这些年龄段中 M 的平均薪资和 F 的平均薪资以及这个年龄
    段的总体平均薪资
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "avgAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "ageBalannce": {
          "avg": {
            "field": "balance"
          }
        },
        "ageGender":{
          "terms": {
            "field": "gender.keyword",
            "size": 10
          },
          "aggs": {
            "ageGenderBalance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
        
      }
    }
  }
}

4.3 Mapping 映射

4.3.1字段类型

字段类型Elasticsearch类型说明
string,varcharkeyword这是不可标记的文本字段,例如 CODE001
string,varchar,texttext这是要标记化的文本字段,例如 a nice text
integerinteger这是一个整型(32位),例如 1、2、3
longlong这是一个长整型(64位)
floatfloat这是一个浮点数(32位),例如 1.2 或 4.5
doubledouble这是一个 double 类型浮点数(64位)
booleanboolean这是一个布尔值:true 或 false
date / datetimedate这是一个日期时间值
bytes / binarybinary这包含一些用于二进制数据的字节,例如文本或字节流

其他映射类型
在这里插入图片描述

4.3.2 映射

Mapping(映射)
Mapping 是用来定义一个文档( document) , 以及它所包含的属性( field) 是如何存储和索引的。 比如, 使用 mapping 来定义:

  1. 哪些字符串属性应该被看做全文本属性(full text fields) 。
  2. 哪些属性包含数字, 日期或者地理位置。
  3. 文档中的所有属性是否都能被索引(_all 配置) 。
  4. 日期的格式。
  5. 自定义映射规则来执行动态添加属性

查看映射信息

get 索引/_mapping
get bank/_mappinng

4.3.3 新版本改变

Es7 及以上移除了 type 的概念。

  • 关系型数据库中两个数据表示是独立的, 即使他们里面有相同名称的列也不影响使用,但 ES 中不是这样的。 elasticsearch 是基于 Lucene 开发的搜索引擎, 而 ES 中不同 type下名称相同的 filed 最终在 Lucene 中的处理方式是一样的。
    1. 两个不同 type 下的两个 user_name, 在 ES 同一个索引下其实被认为是同一个 filed,你必须在两个不同的 type 中定义相同的 filed 映射。 否则, 不同 type 中的相同字段名称就会在处理中出现冲突的情况, 导致 Lucene 处理效率下降。
    2. 去掉 type 就是为了提高 ES 处理数据的效率。
  • Elasticsearch 7.x
    URL 中的 type 参数为可选。 比如, 索引一个文档不再要求提供文档类型。
  • Elasticsearch 8.x
    不再支持 URL 中的 type 参数。
    解决:
    1. 将索引从多类型迁移到单类型, 每种类型文档一个独立索引
    2. 将已存在的索引下的类型数据, 全部迁移到指定位置即可。 详见数据迁移
4.3.3.1. 创建索引并指定映射
PUT /my_index
{
  "mappings":{
    "properties":{
      "age":{"type":"integer"},
      "name":{"type":"text"}
    }
  }
}
4.3.3.2. 添加新的映射
PUT /my_index/_mapping
{
  "properties":{
    "id":{
      "type":"integer",
      "index":"false"
    }
  }
}

解释:index:是否参与索引:true为是,false为否

4.3.3.3 更新映射

对于已经存在的映射字段, 我们不能更新。 更新必须创建新的索引进行数据迁移

先创建出 new_twitter 的正确映射。 然后使用如下方式进行数据迁移

POST _reindex [固定写法]
{
	"source": {
	"index": "twitter"
	},
	"dest": {
	"index": "new_twitter"
	}
}

实例将bank索引下account类型的数据迁移到network下面

POST _reindex
{
  "source": {
    "index": "bank",
    "type": "account"
  },
  "dest": {
    "index": "network"
  }
}

4.4 分词

一个 tokenizer( 分词器) 接收一个字符流, 将之分割为独立的 tokens( 词元, 通常是独立的单词) , 然后输出 tokens 流。
例如, whitespace tokenizer 遇到空白字符时分割文本。 它会将文本 "Quick brown fox!"分割为 [Quick, brown, fox!]。
该 tokenizer(分词器) 还负责记录各个 term(词条) 的顺序或 position 位置(用于phrase 短语和 word proximity 词近邻查询) , 以及 term(词条) 所代表的原始 word(单词) 的 start(起始) 和 end(结束) 的 character offsets(字符偏移量) (用于高亮显示搜索的内容) 。Elasticsearch 提供了很多内置的分词器, 可以用来构建 custom analyzers(自定义分词器) 。

4.4.1 、 安装 ik 分词器

注意: 不能用默认 elasticsearch-plugin install xxx.zip 进行自动安装
https://github.com/medcl/elasticsearch-analysis-ik/releases?after=v6.4.2 对应 es 版本安装

  1. 进入 es 容器内部 plugins 目录
    docker exec -it 容器 id /bin/bash
    进入plugin目录
  2. 从https://github.com/medcl/elasticsearch-analysis-ik/releases?after=v6.4.2&page=7下载对应版本的jk分词器
  3. 将jk分词器的文件解析,并放在plugin目录下面
  4. 退出docker容器
  5. 重启elasticSearch

4.4.2 使用不同的分词器进行测试

POST _analyze
{
  "text": "尚硅谷电商项目"
}

post _analyze
{
  "analyzer":"ik_smart",
  "text":"尚硅谷电商项目"
}

post _analyze
{
  "analyzer":"ik_max_word",
  "text":"尚硅谷电商项目"
}

能够看出不同的分词器, 分词有明显的区别, 所以以后定义一个索引不能再使用默认的 mapping 了, 要手工建立 mapping, 因为要选择分词器。

4.4.3 自定义词库

4.4.3.1 安装nginx
  1. 随便启动一个 nginx 实例, 只是为了复制出配置
    docker run -p 80:80 --name nginx -d nginx:1.10
  2. 将容器内的配置文件拷贝到当前目录:
    docker container cp nginx:/etc/nginx .
  3. 修改文件名称:
    mv nginx conf
  4. 把这个 conf 移动到/mydata/nginx 下
    mv conf nginx
  5. 终止原容器
    docker stop nginx
  6. 执行命令删除原容器
    docker rm $ContainerId
  7. 启动nginx
docker run -p 80:80 --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10
4.4.3.2 自定义词库
  1. 创建词库对应的目录
    在nginx下找到html目录并在下面创建es目录
    具体路径为:/mydata/nginx/html/es
  2. 在此目录下创建词库对应的文档
    创建的词库如下图所示
    在这里插入图片描述
4.4.3.3配置远程词库的地址
  1. 进入到ik词库的目录
    cd /mydata/elasticsearch/plugins/ik/config/
  2. 编辑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"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://47.93.21.100/es/fenci.txt</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

原来的 xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
POST _analyze
{ "analyzer": "ik_max_word",
"text": "我是中国人"
}<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
  1. 重启es
  2. 测试
POST _analyze
{
  "analyzer": "ik_smart",
  "text": "尚硅谷谷粒商城"
}

结果(可以检索到自己的词库)
在这里插入图片描述

5.SpringBoot

5.1 创建module项目

新建一个gulimall-search的module

5.2 添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>gulimall-search</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall-search</name>
    <description>ElasticSearch检索服务</description>
    <properties>
        <java.version>1.8</java.version>
        <elasticsearch.version>7.4.2</elasticsearch.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.atguigu.gulimall</groupId>
            <artifactId>gulimall-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--导入es elasticsearch-rest-high-level-client -->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

5.3 yml

spring:
  application:
    name: gulimall_search
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1

5.4 配置es客户端

package com.atguigu.gulimall.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 1.导入依赖
 * 2. 编写配置 get容器中注入RestHighLevelClient
 * 3. 参考API https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.4/java-rest-high.html
 */
@Configuration
public class GulimallElasticSearchConfig {


    public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();

        COMMON_OPTIONS = builder.build();
    }


    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestClientBuilder builder = null;
        builder = RestClient.builder(new HttpHost("47.93.21.100",9200,"http"));
        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    }
}

5.5 测试

package com.atguigu.gulimall.search;

import com.alibaba.fastjson.JSON;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import com.mysql.cj.QueryBindings;
import lombok.Data;
import lombok.ToString;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.naming.directory.SearchResult;
import java.io.IOException;
import java.util.List;


@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallSearchApplicationTest {



    @Autowired
    private RestHighLevelClient client;

    @Test
    public void contestLoads(){
        System.out.println(client);
    }

    /**
     * 测试保存操作
     * 更新也可以
     * @throws IOException
     */
    @Test
    public void indexData() throws IOException {
        IndexRequest indexRequest = new IndexRequest("users");
        indexRequest.id("1");//保存数据的id
//        indexRequest.source("name","zhansgan","age",18,"gender","男");
        User user = new User();
        user.setName("zhansgan");
        user.setAge(18);
        user.setGender("男");
        String s = JSON.toJSONString(user);
        indexRequest.source(s, XContentType.JSON);
        //执行操作
        IndexResponse index = client.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        //提取有用的相应数据
        System.out.println(index);
    }

    /**
     * 获取数据
     * @throws IOException
     */
    @Test
    public void  getData() throws IOException {
        GetRequest getRequest = new GetRequest("users", "1");
        GetResponse response = client.get(getRequest, RequestOptions.DEFAULT);
        System.out.println(response);
    }

    @Test
    public void searchData() throws IOException {
        //1 创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        //1.1 指定索引
        searchRequest.indices("bank");
        //1.2 指定DSL 检索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
        //1.3 按照年龄值分布进行聚合
        TermsAggregationBuilder ageAggg = AggregationBuilders.terms("AgeAgg").field("age").size(10);
        searchSourceBuilder.aggregation(ageAggg);
        //1.4 计算平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        searchSourceBuilder.aggregation(balanceAvg);
        searchRequest.source(searchSourceBuilder);
        System.out.println(searchRequest.toString());
        System.out.println("===================");
        //2 执行检索
        SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        System.out.println(response);
        //3.1获取索引命中记录
        SearchHits hits = response.getHits();
        SearchHit[] searchHits = hits.getHits();
        for(SearchHit hit :searchHits){
            String string = hit.getSourceAsString();
            Account account = JSON.parseObject(string, Account.class);
            System.out.println(account);
        }
        System.out.println("-----------------------");
        Aggregations aggregations = response.getAggregations();
        System.out.println(aggregations);
        System.out.println("**************");
        Terms ageAgg = aggregations.get("ageAgg");
        List<? extends Terms.Bucket> bucketList = ageAgg.getBuckets();

        for(Terms.Bucket bucket:bucketList){
            Object key = bucket.getKey();
            long docCount = bucket.getDocCount();
            System.out.println(key+"年龄有"+docCount+"人");
        }

        System.out.println("############");
        Avg avgAge = aggregations.get("avgAge");
        System.out.println(avgAge.getValue());
    }

    @Data
    public class User{
        private String name;
        private String gender;
        private Integer age;
    }

    @Data
    @ToString
    public static class Account{
        private int account_number;

        private int balance;

        private String firstname;

        private String lastname;

        private int age;

        private String gender;

        private String address;

        private String employer;

        private String email;

        private String city;

        private String state;
    }

}

6.谷粒商城项目es实战

6.1 mapping设置

PUT gulimall_product
{
  "mappings": {
    "properties": {
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword"
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      },
      "brandId": {
        "type": "long"
      },
      "brandImg": {
        "type": "keyword"
      },
      "brandName": {
        "type": "keyword"
      },
      "catalogId": {
        "type": "long"
      },
      "catalogName": {
        "type": "text"
      },
      "catelogId": {
        "type": "long"
      },
      "catelogName": {
        "type": "keyword"
      },
      "hasStock": {
        "type": "boolean"
      },
      "hosStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "saleCount": {
        "type": "long"
      },
      "skuId": {
        "type": "long"
      },
      "skuImg": {
        "type": "keyword"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuTitle": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "spuId": {
        "type": "long"
      }
    }
  }
}

6.2 写搜索条件

GET gulimall_product/_search
{ 
  "from": 0,
  "size": 16,
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "Apple"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": 225
          }
        },
        {
          "term":{
            "brandId":12
          }
        },
        {
          "nested": {
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId":"15"
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "以官网信息为准"
                      ]
                    }
                  }
                ]
              }
            },
            "path": "attrs"
          }
        },
        {
          "term": {
            "hasStock":true
          }
        },
        {
          "range": {
            "skuPrice": {
              "from": null,
              "to": null
            }
          }
        }
      ]
    }
  },
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brandId"
      },
      "aggs": {
        "brand_name_agg": {
          "terms": {
            "field": "brandName"
          }
        },
        "brand_img_agg": {
          "terms": {
            "field": "brandImg"
          }
        }
      }
    },
    "catalog_agg": {
      "terms": {
        "field": "catalogId"
      },
      "aggs": {
        "catalogg_name_agg": {
          "terms": {
            "field": "catelogName"
          }
        }
      }
    },
    "attr_agg": {
      "nested": {
        "path": "attrs"
      },
      "aggs": {
        "attrs_id_agg": {
          "terms": {
            "field": "attrs.attrId"
          },
          "aggs": {
            "attr_name_agg": {
              "terms": {
                "field": "attrs.attrName"
              }
            },
            "attr_value_agg": {
              "terms": {
                "field": "attrs.attrValue"
              }
            }
          }
        }
      }
    }
  },
  "highlight": {
    "pre_tags": [
      "<b style='color:red'>"
    ],
    "post_tags": [
      "</b>"
    ],
    "fields": {
      "skuTitle": {}
    }
  }
}

6.3 配置客户端

package com.atguigu.gulimall.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 1.导入依赖
 * 2. 编写配置 get容器中注入RestHighLevelClient
 * 3. 参考API https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.4/java-rest-high.html
 */
@Configuration
public class GulimallElasticSearchConfig {


    public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();

        COMMON_OPTIONS = builder.build();
    }


    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestClientBuilder builder = null;
        builder = RestClient.builder(new HttpHost("47.93.21.100",9200,"http"));
        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    }
}

6.4 实体类

package com.atguigu.gulimall.search.vo;

import lombok.Data;

import java.util.List;

@Data
public class SearchParam {

    private String keyword;//页面传递过来的全局匹配关键字
    private Long catalogId;//三级分类id

    /**
     * sort=saleCount_asc/desc
     * sort=skuPrice_asc/desc
     * sort=hotScore_asc/desc
     */
    private String sort;//排序条件

    private Integer hasStock = 1;//是否只显示有货 0无 1有
    private String skuPrice;//价格区间查询
    private List<Long> brandId;//按照品牌进行查询,可以多选
    private List<String> attrs;//按照属性进行筛选
    private Integer pageNum = 1 ;//页码
}

package com.atguigu.gulimall.search.vo;

import com.atguigu.common.to.es.SkuEsModel;
import lombok.Data;

import java.util.List;

@Data
public class SearchResult {

    //查询到的所有商品信息
    private List<SkuEsModel> products;

    private Integer pageNum;
    private Long total;
    private Integer totalPages;

    private List<BrandVo> brands;//当前查询到的结果,所有涉及到的品牌
    private List<AttrVo> attrs;//当前查询结果所涉及到的所有属性
    private List<CatalogVo> catalogs;//当前查询结果所涉及到的所有分类

    @Data
    public static class BrandVo{
        private Long brandId;
        private String brandName;
        private String brandImg;
    }

    @Data
    public static class AttrVo{
        private Long attrId;
        private String attrName;
        private List<String> attrValue;
    }

    @Data
    public static class CatalogVo{
        private Long catalogId;
        private String catalogName;
    }

}

6.5 controller

package com.atguigu.gulimall.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 1.导入依赖
 * 2. 编写配置 get容器中注入RestHighLevelClient
 * 3. 参考API https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.4/java-rest-high.html
 */
@Configuration
public class GulimallElasticSearchConfig {


    public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();

        COMMON_OPTIONS = builder.build();
    }


    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestClientBuilder builder = null;
        builder = RestClient.builder(new HttpHost("47.93.21.100",9200,"http"));
        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    }
}

6.6 service

package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import com.atguigu.gulimall.search.constant.EsConstant;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.SearchParam;
import com.atguigu.gulimall.search.vo.SearchResult;
import com.mysql.cj.QueryBindings;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public SearchResult search(SearchParam searchParam) {
        //1.动态构建出查询需要的DSL语句
        SearchResult result = null;
        //1.准备检索请求
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            //2.执行检索请求
            SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
            //3.分析响应数据,解析成我们需要的格式
            result = buildSearchResult(response,searchParam);
        }catch (Exception e){
            e.printStackTrace();
        }

        return result;
    }


    /**
     * 构建索引请求
     * @return
     *      * 模糊匹配 过滤(按照属性、分类、品牌、价格区间、库存),排序 分页 高亮 聚合分析
     */
    private SearchRequest buildSearchRequest(SearchParam param){
        SearchSourceBuilder builder = new SearchSourceBuilder();//构建DSL语句
        //* 模糊匹配 过滤(按照属性、分类、品牌、价格区间、库存)
        //1.构建bool query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.1 must 模糊查询
        if(StringUtils.isNotBlank(param.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
        }
        //1.2 bool  filter 按照三级分类的id进行查询
        if(param.getCatalogId() != null){
            boolQuery.filter(QueryBuilders.termQuery("catalogId",param.getCatalogId()));
        }
        //1.2 bool  filter 按照品牌id进行查询
        if (param.getBrandId() != null && param.getBrandId().size() >0){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
        }
        //1.2 bool  filter 按照指定的属性进行查询
        if(param.getAttrs() != null && param.getAttrs().size()>0){

            for(String attr:param.getAttrs()){
                //attrs=1_5寸:8寸
                BoolQueryBuilder nestBoolQuery = QueryBuilders.boolQuery();
                String[] s = attr.split("_");
                String attrId = s[0];
                String[] attrValues = s[1].split(":");
                nestBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                nestBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                //每一个都必须生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }
        //1.2 bool  filter 按照是否有库存进行查询
        boolQuery.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1));

        //1.2 bool  filter 按照价格区间进行查询
        if(!StringUtils.isEmpty(param.getSkuPrice())){
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] s = param.getSkuPrice().split("-");
            if(s.length==2){
                rangeQuery.gte(s[0]).lte(s[1]);
            }else if(s.length==1){
                if(param.getSkuPrice().startsWith("-")){
                    rangeQuery.gte(s[0]);
                }
                if(param.getSkuPrice().endsWith("-")){
                    rangeQuery.lte(s[0]);
                }
            }
            boolQuery.filter(rangeQuery);
        }

        builder.query(boolQuery);
        // 排序 分页 高亮
        // 2.1 排序
        if(StringUtils.isNotBlank(param.getSort())){
            String sort = param.getSort();
            String[] split = sort.split("_");
            SortOrder sortOrder = split[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;;
            builder.sort(split[0],sortOrder);
        }
        //2.2 分页
        builder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
        builder.size(EsConstant.PRODUCT_PAGESIZE);
        //2.3高亮
        if(StringUtils.isNotBlank(param.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            builder.highlighter(highlightBuilder);
        }
        /**
         * 聚合分析
         */
        //1.品牌聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(50);
        //品牌聚合对应的子聚合
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(10));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(10));
        builder.aggregation(brand_agg);
        //2.分类聚合
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catelogName").size(20));
        builder.aggregation(catalog_agg);

        //3.属性聚合 attr_agg
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
        //聚合出当前所有的attrId
        AggregationBuilder attr_id_agg = AggregationBuilders.terms("attrs_id_agg").field("attrs.attrId").size(20);
        attr_agg.subAggregation(attr_id_agg);
        //聚合出当前所有attr_id对应的名字
        AggregationBuilder attr_name_agg = AggregationBuilders.terms("attr_name_agg").field( "attrs.attrName").size(20);
        //聚合出当前所有attr_id对应所有可能的属性值
        AggregationBuilder attr_value_agg = AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(20);
        attr_id_agg.subAggregation(attr_name_agg);
        attr_id_agg.subAggregation(attr_value_agg);
        builder.aggregation(attr_agg);
        String dsl = builder.toString();
        System.out.println(dsl);
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},builder);
        return searchRequest;
    }


    /**
     * 返回构建结果
     * @param response
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse response,SearchParam searchParam) {
        SearchResult result = new SearchResult();
        // 1. 返回所有查询的商品
        SearchHits hits = response.getHits();

        ArrayList<SkuEsModel> esModels = new ArrayList<>();
        if(hits.getHits() != null && hits.getHits().length>0){
            for(SearchHit hit : hits.getHits()){
                String source = hit.getSourceAsString();
                SkuEsModel esModel = JSON.parseObject(source, SkuEsModel.class);
                if(StringUtils.isNotBlank(searchParam.getKeyword())){
                    String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].string();
                    esModel.setSkuTitle(skuTitle);
                    esModels.add(esModel);
                }
            }
        }
        result.setProducts(esModels);

        //2, 返回所有查询到商品信息 所涉及到的属性信息
        ArrayList<SearchResult.AttrVo> attrVos = new ArrayList<>();
        ParsedNested attrAgg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attrsIdAgg = attrAgg.getAggregations().get("attrs_id_agg");
        List<? extends Terms.Bucket> attrsIdAggBuckets = attrsIdAgg.getBuckets();
        for(Terms.Bucket bucket:attrsIdAggBuckets){
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            //属性的id
            long attrId = bucket.getKeyAsNumber().longValue();
            attrVo.setAttrId(attrId);
            //属性的名字
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);
            //属性的值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> {
                String keyAsString = item.getKeyAsString();
                return keyAsString;
            }).collect(Collectors.toList());
            attrVo.setAttrValue(attrValues);
            attrVos.add(attrVo);
        }
        result.setAttrs(attrVos);
        //3. 当前所有商品所涉及到的品牌信息
        ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
        ArrayList<SearchResult.BrandVo> brandVos = new ArrayList<>();
        List<? extends Terms.Bucket> brandAggBuckets = brand_agg.getBuckets();
        if(brandAggBuckets != null && brandAggBuckets.size()>0){
            for(Terms.Bucket bucket:brandAggBuckets){
                //品牌id
                SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
                String id = bucket.getKeyAsString();
                brandVo.setBrandId(Long.parseLong(id));
                //品牌图片
                ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
                String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
                brandVo.setBrandImg(brandImg);
                //品牌的名字
                ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
                String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                brandVo.setBrandName(brandName);

                brandVos.add(brandVo);
            }
        }
        result.setBrands(brandVos);

        //4.当前所有商品所涉及到的分类信息
        ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
        ArrayList<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
        List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
        for(Terms.Bucket bucket:buckets){
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            //得到分类的id
            String keyAsString = bucket.getKeyAsString();
            catalogVo.setCatalogId(Long.parseLong(keyAsString));
            //得到分类名
            ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
            List<? extends Terms.Bucket> cataloggNameAggBuckets = catalog_name_agg.getBuckets();
            if(cataloggNameAggBuckets != null && cataloggNameAggBuckets.size()> 0){
                String catalog_name = cataloggNameAggBuckets.get(0).getKeyAsString();
                catalogVo.setCatalogName(catalog_name);
            }
            catalogVos.add(catalogVo);
        }
        result.setCatalogs(catalogVos);
//        5. 分页信息 页码 总记录数
        result.setPageNum(searchParam.getPageNum());
        long total = hits.getTotalHits().value;
        result.setTotal(total);
        int totalPages = total%EsConstant.PRODUCT_PAGESIZE == 0? (int)total%EsConstant.PRODUCT_PAGESIZE :(int) (total%EsConstant.PRODUCT_PAGESIZE)+1;
        result.setTotalPages(totalPages);
        return result;
    }
}

ElasticSearch是一个开源的分布式搜索引擎,基于Lucene库。它可以快速地存储、搜索和分析大量的数据。ElasticSearch被广泛用于日志分析、全文搜索、安全分析和商业智能等领域。 以下是ElasticSearch入门指南: 1. 安装ElasticSearch:你可以从ElasticSearch官网下载并安装ElasticSearch。根据你的操作系统选择相应的版本。 2. 启动ElasticSearch:启动ElasticSearch非常简单。只需在终端中运行elasticsearch命令即可。 3. 探索ElasticSearch:通过在浏览器中输入http://localhost:9200/,可以访问ElasticSearch的REST API,并能看到ElasticSearch的基本信息。 4. 创建索引:在ElasticSearch中,数据被存储在索引中。你可以通过发送PUT请求来创建一个新的索引。例如,你可以使用以下命令来创建一个名为“my_index”的新索引: ``` PUT /my_index { "settings": { "number_of_shards": 1, "number_of_replicas": 0 } } ``` 5. 添加文档:在ElasticSearch中,文档是指一个JSON对象。你可以使用以下命令将文档添加到“my_index”索引中: ``` PUT /my_index/_doc/1 { "title": "Elasticsearch入门", "author": "John", "content": "Elasticsearch是一个开源的分布式搜索引擎" } ``` 6. 搜索文档:你可以使用以下命令来搜索“my_index”索引中的所有文档: ``` GET /my_index/_search ``` 7. 进行查询:你可以使用查询语句来搜索“my_index”索引。例如,你可以使用以下命令来搜索标题包含“Elasticsearch”的所有文档: ``` GET /my_index/_search { "query": { "match": { "title": "Elasticsearch" } } } ``` 这就是ElasticSearch入门指南。对于更深入的学习,你可以查看ElasticSearch官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值