一、Elasticsearch 安装
- 1.1 Elasticsearch镜像下载
docker pull elasticsearch:5.6.8
查看镜像是否下载成功
- 1.2 运行 Elasticsearch
docker run -d --name=my_elasticsearch -p 9200:9200 -p 9300:9300 6c0bdf761f3b
启动失败,查看日志
docker logs 容器ID
内存不足导致启动失败,重启
docker run -d -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" --name=my_es -p 9200:9200 -p 9300:9300 6c0bdf761f3b
访问 http://192.168.3.156:9200,运行成功
二、配置跨域
- 2.1 进入容器
docker exec -it c11654c6c86b /bin/bash
- 2.2 安装vim
apt-get update
apt-get install vim
- 2.3 配置跨域
vi elasticsearch.yml
#自定义集群名称
cluster.name: "my_es"
#当前es节点绑定的ip地址,默认127.0.0.1
network.host: 0.0.0.0
#开启跨域
http.cors.enabled: true
#当设置允许跨域,默认为*,表示支持所有域名
http.cors.allow-origin: "*"
- 2.4 配置 Elasticsearch 占用内存
vi /etc/elasticsearch/jvm.options
- 2.5 重启发现异常
vi /etc/sysctl.conf
vm.max_map_count=262144
重新加载
sysctl -p
查看 vm.max_map_count
sysctl -a|grep vm.max_map_count
- 2.5 重新启动Elasticsearch,并访问
- 2.6 开机自启
docker update --restart=always 容器ID
三、IK 分词器 安装
链接:https://pan.baidu.com/s/1LsmRD6K6lTToaJesE9CfOw
提取码:4e74
3.1 下载IK分词器,注意与Elasticsearch对应的版本。
3.2 修改文件名称。Elasticsearch启动后会去plugins/ik/下找需要的配置文件
mv elasticsearch/ ik
- 3.3 将ik复制到Elasticsearch容器中
docker cp ik c11654c6c86b:/usr/share/elasticsearch/plugins
- 3.4 重启后访问
http://192.168.3.156:9200/_analyze?analyzer=ik_smart&pretty=true&text=你是什么品种
- 3.5 自定义分词
修改/usr/share/elasticsearch/plugins/ik/config/
IKAnalyzer.cfg.xml
四、Kibana 安装
- 4.1 Kibana 镜像下载
docker pull docker.io/kibana:5.6.8
- 4.2 启动Kibana
docker run -d -e ELASTICSEARCH_URL=http://192.168.3.156:9200 --name kibana -p 5601:5601 18efdc555b14
- 4.3 访问 192.168.3.156:5601
五、DSL语句
#获取所有索引库
GET _cat/indices?v
#创建索引库
PUT /user
#删除索引库
DELETE /user
#创建映射
#user - 索引库;userinfo - 类型;_mapping - 映射
#name/city/age/description - 域
PUT /user/userinfo/_mapping
{
"properties": {
"name":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
},
"city":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
},
"age":{
"type": "long",
"store": false
},
"description":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
}
}
}
#添加数据
PUT /user/userinfo/1
{
"name":"张三",
"age":"12",
"city":"大连",
"description":"张三来自大连"
}
#查询数据
GET /user/userinfo/1
#更新数据:将原始数据删除后新增数据
PUT /user/userinfo/1
{
"name":"张三",
"age":"22"
}
#更新数据:更新指定域的数据
POST /user/userinfo/1/_update
{
"doc":{
"age":"22"
}
}
#删除数据
DELETE /user/userinfo/1
#查询user索引库中type为userinfo的数据
GET /user/userinfo/_search
#排序
#根据age降序查询
#match_all查询所有
GET /user/userinfo/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
#分页
GET /user/userinfo/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 10
}
#词项查询Term
GET /user/userinfo/_search
{
"query": {
"term": {
"city": {
"value": "大连"
}
}
}
}
#多词项查询Terms
GET /user/userinfo/_search
{
"query": {
"terms": {
"city": [
"大连",
"鞍山"
]
}
}
}
#范围查询range
GET /user/userinfo/_search
{
"query": {
"range": {
"age": {
"gte": 10,
"lte": 20
}
}
}
}
#域查询exists
GET /user/userinfo/_search
{
"query": {
"exists":{
"field":"age"
}
}
}
#多个过滤条件查询bool
#must 多个查询条件完全匹配,相当于and
#must_not 多个查询条件的相反匹配,相当于not
#should 至少有一个查询条件匹配,相当于or
GET /user/userinfo/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "大连"
}
}
},{
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
]
}
}
}
#查询所有
GET /user/userinfo/_search
{
"query": {
"match_all": {}
}
}
#字符搜索
GET /user/userinfo/_search
{
"query": {
"match": {
"description": "大连"
}
}
}
#前缀匹配 prefix
GET /user/userinfo/_search
{
"query": {
"prefix": {
"name": {
"value": "张"
}
}
}
}
#多个域匹配
GET /user/userinfo/_search
{
"query": {
"multi_match": {
"query": "大连",
"fields": [
"city",
"description"
]
}
}
}
六、SpringBoot 整合 Elasticsearch
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
application.yml
spring:
application:
#----------------- Server-id ------------------
name: elasticsearch-service
data:
elasticsearch:
cluster-name: es 的 cluster-name
cluster-nodes: es ip地址:9300
启动类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableElasticsearchRepositories(basePackages = "com.gg.projects.elasticsearch.module.mapper")
public class ElasticsearchApplication {
public static void main(String[] args) {
//解决 : SpringBoot整合Elasticsearch,client初始化时出现netty冲突异常
System.setProperty("es.set.netty.runtime.available.processors","false");
SpringApplication.run(ElasticsearchApplication.class, args);
}
}
-
6.1 创建索引库对象映射
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Map;
/**
* Document- 索引库配置
* indexName - 索引库名称(必须小写)
* type - 索引库类型
*/
@Document(indexName = "goods-info", type = "goodsinfo")
public class GoodInfo{
//索引库 ID
@Id
private String goodId;
/**
* type = FieldType.text 类型,Text支持分词
* analyzer = "ik_smart" 创建索引的分词器
* index = true 添加数据时是否分词
* store = false 是否存储
* searchAnalyzer = "ik_smart" 搜索时使用的分词器
*/
@Field(type = FieldType.text, analyzer = "ik_smart", index = true, store = false, searchAnalyzer = "ik_smart")
private String goodName;
private BigDecimal price;
private int num;
private String image;
private int status;
private String spuId;
/**
* type = FieldType.keyword 不支持分词
* 举例,我们搜索分类为华为手机。如果支持分词,搜索出来的分类为华为/手机
*/
@Field(type = FieldType.keyword)
private String categoryName;
private String spec;
//下文生成动态域
private Map<String,Object> specMap;
-
6.2 实现索引库数据导入
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
//ElasticsearchRepository<索引库,id类型>
public interface GoodsInfoMapper extends ElasticsearchRepository<GoodInfo,String> {
}
获取数据时建议分页查询,此处为示例代码仅供参考
@Service
public class ElasticsearchServiceImpl implements ElasticsearchService{
@Autowired
GoodsAPI goodsAPI;
@Autowired
GoodsInfoMapper goodsInfoMapper;
@Override
public void importGoodsData() {
//调用GoodsAPI 接口获取所有商品信息
List<Good> goods = goodsAPI.getGoods();
//转化为Elasticsearch 所需的Bean类型
List<GoodInfo> goodInfos = JSON.parseArray(JSON.toJSONString(goods),GoodInfo.class);
//实现数据批量导入
goodsInfoMapper.saveAll(goodInfos);
}
}
-
6.3 生成动态域
@Service
public class ElasticsearchServiceImpl implements ElasticsearchService{
@Autowired
GoodsAPI goodsAPI;
@Autowired
GoodsInfoMapper goodsInfoMapper;
@Override
public void importGoodsData() {
//调用GoodsAPI 接口获取所有商品信息
List<Good> goods = goodsAPI.getGoods();
//转化为Elasticsearch 所需的Bean类型
List<GoodInfo> goodInfos = JSON.parseArray(JSON.toJSONString(goods),GoodInfo.class);
//获取商品规格
for (GoodInfo goodInfo : goodInfos) {
//将json字符串的规格数据转成map<String,Object>类型。
//map<String,Object>类型会自动转成动态域,其中key为域的名称
goodInfo.setSpecMap(JSON.parseObject(goodInfo.getSpec()));
}
//实现数据批量导入
goodsInfoMapper.saveAll(goodInfos);
}
}
-
6.4 关键词搜索(分词) queryStringQuery()
@Service
public class ElasticsearchServiceImpl implements ElasticsearchService {
@Autowired
GoodsInfoMapper goodsInfoMapper;
@Autowired
ElasticsearchTemplate elasticsearchTemplate;
@Override
public Map<String, Object> search(Map<String, String> searchMap) {
//条件封装
NativeSearchQueryBuilder nativeSearchQueryBuilder = getNativeSearchQueryBuilder(searchMap);
//结果集搜索
Map<String, Object> result = getResult(nativeSearchQueryBuilder);
return result;
}
private NativeSearchQueryBuilder getNativeSearchQueryBuilder(Map<String, String> searchMap) {
//搜索条件构建对象,用于封装各种搜索条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (searchMap != null && searchMap.size() > 0){
//根据关键词搜索
String keyword = searchMap.get("keyword");
//如果关键词不为空
if (!StringUtils.isEmpty(keyword)){
//queryStringQuery() 分词搜索
// field() 制定域
boolQueryBuilder.must(QueryBuilders.queryStringQuery(keyword).field("goodName"));
}
}
return nativeSearchQueryBuilder;
}
private Map<String, Object> getResult(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
/**
* param1 搜索条件封装的数据
* param2 搜索的结果集(集合数据)需要转换的类型
*/
AggregatedPage<GoodInfo> page = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), GoodInfo.class);
//封装数据
Map<String, Object> result = new HashMap<>();
//获取结果集
result.put("rows",page.getContent());
//获取数据总数
result.put("totalData",page.getTotalElements());
//获取总页数
result.put("totalPages",page.getTotalPages());
return result;
}
}
-
6.5 分类列表查询
private List<String> searchCategoryList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
/**
* 查询数据中的分组
* addAggregation()添加一个聚合操作
* terms() 取别名
* field("") 根据某个域进行分组
*/
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("categoryGroupName").field("categoryId"));
AggregatedPage<GoodInfo> groupPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), GoodInfo.class);
//获取分组;groupPage.getAggregations()获取的是集合,可以根据多个域进行分组
//get("categoryGroupName")获取指定域的集合
StringTerms stringTerms = groupPage.getAggregations().get("categoryGroupName");
List<String> categoryList = Lists.newArrayList();
for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
//获取其中一个分类名称
String categoryName = bucket.getKeyAsString();
categoryList.add(categoryName);
}
return categoryList;
}
6.6 规格列表查询
private Map<String, Set<String>> searchSpecList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
/**
* 根据规格进行分组查询
* .size(100000) 分页默认10条
*/
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("specList").field("spec.keyword").size(100000));
AggregatedPage<GoodInfo> groupPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), GoodInfo.class);
//获取分组;groupPage.getAggregations()获取的是集合,可以根据多个域进行分组
//get("categoryGroupName")获取指定域的集合
StringTerms stringTerms = groupPage.getAggregations().get("specList");
//获取所有规格
List<String> specList = Lists.newArrayList();
for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
String specInfo = bucket.getKeyAsString();
specList.add(specInfo);
}
Map<String, Set<String>> allSpec = new HashMap<String, Set<String>>();
for (String spec : specList) {
//将每个规格JSON串转成MAP
Map<String, String> specMap = JSON.parseObject(spec, Map.class);
//合并规格值
for (Map.Entry<String, String> entry : specMap.entrySet()) {
String key = entry.getKey(); //规格名称
String value = entry.getValue(); //规格值
//如果当前规格名称已存在,则为原有值后面添加新值
Set<String> specSet = allSpec.get(key);
if (specSet == null) {
specSet = new HashSet<String>();
}
specSet.add(value);
allSpec.put(key, specSet);
}
}
return allSpec;
}
6.7 分类查询(不分词查询) termQuery()
private NativeSearchQueryBuilder getNativeSearchQueryBuilder(Map<String, String> searchMap) {
//搜索条件构建对象,用于封装各种搜索条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (searchMap != null && searchMap.size() > 0) {
//根据分类搜索
String category = searchMap.get("category");
if (!StringUtils.isEmpty(category)) {
//构建条件,对good name
/**
* 构建条件2
* termQuery 对 categoryId 进行不分词查询
*/
boolQueryBuilder.must(QueryBuilders.termQuery("categoryId",category));
}
}
//将boolQueryBuilder 对象填充到 nativeSearchQueryBuilder
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
return nativeSearchQueryBuilder;
}
6.8 规格条件查询(动态域查询)
private NativeSearchQueryBuilder getNativeSearchQueryBuilder(Map<String, String> searchMap) {
//搜索条件构建对象,用于封装各种搜索条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (searchMap != null && searchMap.size() > 0) {
//规格条件查询
for (Map.Entry<String, String> entry : searchMap.entrySet()) {
String key = entry.getKey();
//获取以'spec_'为前缀的key
if (key.startsWith("spec_")){
String spec = entry.getValue();
//搜索的域为 'specMap.颜色.keyword'
boolQueryBuilder.must(QueryBuilders.termQuery("specMap."+key.substring(5)+".keyword",spec));
}
}
//将boolQueryBuilder 对象填充到 nativeSearchQueryBuilder
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
return nativeSearchQueryBuilder;
}
6.9 价格范围搜索 rangeQuery()
private NativeSearchQueryBuilder getNativeSearchQueryBuilder(Map<String, String> searchMap) {
//搜索条件构建对象,用于封装各种搜索条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (searchMap != null && searchMap.size() > 0) {
//根据价格范围搜索
String priceRage = searchMap.get("price");
if (!StringUtils.isEmpty(priceRage)) {
String[] price = priceRage.split("-");
if (price != null && price.length > 0) {
//gte >=
boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gte(Integer.parseInt(price[0])));
if (price.length == 2) {
//lte <=
boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(price[1])));
}
}
}
}
//将boolQueryBuilder 对象填充到 nativeSearchQueryBuilder
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
return nativeSearchQueryBuilder;
}
6.10 分页 withPageable()
private NativeSearchQueryBuilder getNativeSearchQueryBuilder(Map<String, String> searchMap) {
//搜索条件构建对象,用于封装各种搜索条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//分页: 默认第一页
String pageNum = searchMap.get("pageNum");
if (StringUtils.isEmpty(pageNum)) {
pageNum = "1";
}
Integer size = 3;
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum)-1,size));
return nativeSearchQueryBuilder;
}
6.11 排序 withSort()
private NativeSearchQueryBuilder getNativeSearchQueryBuilder(Map<String, String> searchMap) {
//搜索条件构建对象,用于封装各种搜索条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//排序
String sortField = searchMap.get("sortField"); //指定排序的域
String sortRule = searchMap.get("sortRule"); //指定排序的规则
if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)){
nativeSearchQueryBuilder.withSort(new FieldSortBuilder(sortField).order(SortOrder.valueOf(sortRule)));
}
return nativeSearchQueryBuilder;
}
6.12 高亮搜索
private Map<String, Object> getResult(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
//高亮配置
HighlightBuilder.Field field = new HighlightBuilder.Field("goodName"); // 指定高亮域
//前缀<em style="color:red;">
field.postTags("<em style='color:red;'>");
//后缀</em>
field.postTags("</em>");
//碎片长度
field.fragmentOffset(4);
//添加高亮
nativeSearchQueryBuilder.withHighlightFields(field);
//获取所有数据并对返回值进行处理
AggregatedPage<GoodInfo> page = elasticsearchTemplate
.queryForPage(
nativeSearchQueryBuilder.build(), //搜索条件封装
GoodInfo.class, //转换的类型
new SearchResultMapper() { //SearchResultMapper 执行搜索后,将数据结果集封装到该对象中
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//用于存储转化后的高亮数据
List<T> list = Lists.newArrayList();
//执行查询,获取所有数据
SearchHits searchHitFields = searchResponse.getHits();
for (SearchHit searchHitField : searchHitFields) {
//分析结果集数据,获取非高亮数据
GoodInfo goodInfo = JSON.parseObject(searchHitField.getSourceAsString(), GoodInfo.class);
//分级结果集数据,获取制定域的高亮数据
HighlightField highlightField = searchHitField.getHighlightFields().get("goodName");
if (highlightField != null && highlightField.getFragments() != null) {
//读取高亮数据
Text[] fragments = highlightField.getFragments();
StringBuffer buffer = new StringBuffer();
for (Text fragment : fragments) {
buffer.append(fragment.toString());
}
//高亮数据替换非高亮数据
goodInfo.setGoodName(buffer.toString());
}
//将数据添加到集合中
list.add((T)goodInfo);
}
/**
* param1. (携带高亮数据)搜索的集合数据:List<T> content
* param2. 分页对象信息, Pageable
* param3. 搜索记录的总条数 total
*/
return new AggregatedPageImpl<T>(list,pageable,searchResponse.getHits().getTotalHits());
}
}
);
//封装数据
Map<String, Object> result = new HashMap<>();
//获取结果集
result.put("rows", page.getContent());
//获取数据总数
result.put("totalData", page.getTotalElements());
//获取总页数
result.put("totalPages", page.getTotalPages());
return result;
}
6.13 整合
多条件查询及聚合查询
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.*;
@Service
public class ElasticsearchServiceImpl implements ElasticsearchService {
@Autowired
GoodsAPI goodsAPI;
@Autowired
GoodsInfoMapper goodsInfoMapper;
@Autowired
ElasticsearchTemplate elasticsearchTemplate;
/**
* 条件查询
*/
@Override
public SearchResultVO search(SearchVO searchVO) {
//条件封装
NativeSearchQueryBuilder nativeSearchQueryBuilder = buildSearchQuery(searchVO);
//结果集搜索
SearchResultVO result = getResult(nativeSearchQueryBuilder);
//获取分类集合与规格详情
result = searchInfo(nativeSearchQueryBuilder, result);
return result;
}
/**
* 构建搜索条件
*/
private NativeSearchQueryBuilder buildSearchQuery(SearchVO searchVO) {
//搜索条件构建对象,用于封装各种搜索条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//条件封装
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (!ObjectUtils.isEmpty(searchVO)) {
/**
* 关键词分词搜索
*/
String keyword = searchVO.getKeyword();
if (!StringUtils.isEmpty(keyword)) {
//queryStringQuery 对 'goodName' 进行分词查询
boolQueryBuilder.must(QueryBuilders.queryStringQuery(keyword).field("goodName"));
}
/**
* 分类不分词搜索
*/
String category = searchVO.getCategory();
if (!StringUtils.isEmpty(category)) {
//termQuery 对 categoryId 进行不分词查询
boolQueryBuilder.must(QueryBuilders.termQuery("categoryId", category));
}
/**
* 价格范围搜索
* 0-1000;1000-2000;2000
*/
String priceRage = searchVO.getPrice();
if (!StringUtils.isEmpty(priceRage)) {
String[] price = priceRage.split("-");
if (price != null && price.length > 0) {
boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gte(Integer.parseInt(price[0])));
if (price.length == 2) {
boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(price[1])));
}
}
}
}
/**
* 规格查询
* {颜色:红色}
* 'spec_'为前端传值规定的前缀(停用)
*/
Map<String, String> spec = searchVO.getSpec();
if (spec != null && spec.size() > 0) {
for (Map.Entry<String, String> entry : spec.entrySet()) {
//查询的动态域为 ‘specMap.颜色.keyword’
boolQueryBuilder.must(QueryBuilders.termQuery("specMap." + entry.getKey() + ".keyword", entry.getValue()));
}
}
/**
* 排序
*/
String sortField = searchVO.getSortField(); //指定排序的域
String sortRule = searchVO.getSortRule(); //指定排序的规则
if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)) {
nativeSearchQueryBuilder.withSort(new FieldSortBuilder(sortField).order(SortOrder.valueOf(sortRule)));
}
/**
* 分页
* 默认第一页
*/
String pageNum = searchVO.getPageNum();
if (StringUtils.isEmpty(pageNum)) {
pageNum = "1";
}
Integer size = 10;
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, size));
//将boolQueryBuilder 对象填充到 nativeSearchQueryBuilder
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
return nativeSearchQueryBuilder;
}
/**
* 查询结果集
*/
private SearchResultVO getResult(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
//高亮配置
HighlightBuilder.Field field = new HighlightBuilder.Field("goodName"); // 指定高亮域
//前缀<em style="color:red;">
field.postTags("<em style='color:red;'>");
//后缀</em>
field.postTags("</em>");
//碎片长度
field.fragmentOffset(4);
//添加高亮配置
nativeSearchQueryBuilder.withHighlightFields(field);
//queryForPage(搜索条件封装的数据,结果集需要转换的类型)
//AggregatedPage<GoodInfo> page = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), GoodInfo.class);
AggregatedPage<GoodInfo> page = elasticsearchTemplate
.queryForPage(
nativeSearchQueryBuilder.build(), //搜索条件封装
GoodInfo.class, //转换的类型
new SearchResultMapper() { //SearchResultMapper 执行搜索后,将数据结果集封装到该对象中
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//存储转化后的高亮数据
List<T> list = Lists.newArrayList();
//执行查询,获取所有数据
SearchHits searchHitFields = searchResponse.getHits();
for (SearchHit searchHitField : searchHitFields) {
//分析结果集数据,获取非高亮数据
GoodInfo goodInfo = JSON.parseObject(searchHitField.getSourceAsString(), GoodInfo.class);
//分级结果集数据,获取制定域的高亮数据
HighlightField highlightField = searchHitField.getHighlightFields().get("goodName");
if (highlightField != null && highlightField.getFragments() != null) {
//读取高亮数据
Text[] fragments = highlightField.getFragments();
StringBuffer buffer = new StringBuffer();
for (Text fragment : fragments) {
buffer.append(fragment.toString());
}
//高亮数据替换非高亮数据
goodInfo.setGoodName(buffer.toString());
}
//将数据添加到集合中
list.add((T) goodInfo);
}
/**
* param1. (携带高亮数据)搜索的集合数据:List<T> content
* param2. 分页对象信息, Pageable
* param3. 搜索记录的总条数 total
*/
return new AggregatedPageImpl<T>(list, pageable, searchResponse.getHits().getTotalHits());
}
}
);
//封装数据
SearchResultVO result = new SearchResultVO() {{
setRows(page.getContent()); //获取结果集
setTotalData(page.getTotalElements()); //获取数据总数
setTotalPages(page.getTotalPages()); //获取总页数
}};
return result;
}
/**
* 获取分类集合与规格详情
*/
private SearchResultVO searchInfo(NativeSearchQueryBuilder nativeSearchQueryBuilder, SearchResultVO result) {
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("specList").field("spec.keyword").size(100000));
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("categoryGroupName").field("categoryId"));
AggregatedPage<GoodInfo> groupPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), GoodInfo.class);
List<String> categoryList = getCategoryList(groupPage);
Map<String, Set<String>> specList = getSpecList(groupPage);
result.setCategoryInfo(categoryList);
result.setSpecInfo(specList);
return result;
}
private List<String> getCategoryList(AggregatedPage<GoodInfo> groupPage) {
//获取分组;groupPage.getAggregations()获取的是集合,可以根据多个域进行分组
//get("categoryGroupName")获取指定域的集合
StringTerms stringTerms = groupPage.getAggregations().get("categoryGroupName");
List<String> categoryList = Lists.newArrayList();
for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
//获取其中一个分类名称
String categoryName = bucket.getKeyAsString();
categoryList.add(categoryName);
}
return categoryList;
}
private Map<String, Set<String>> getSpecList(AggregatedPage<GoodInfo> groupPage) {
//获取分组;groupPage.getAggregations()获取的是集合,可以根据多个域进行分组
//get("categoryGroupName")获取指定域的集合
StringTerms stringTerms = groupPage.getAggregations().get("specList");
List<String> specList = Lists.newArrayList();
for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
String specInfo = bucket.getKeyAsString();
specList.add(specInfo);
}
Map<String, Set<String>> allSpec = new HashMap<String, Set<String>>();
for (String spec : specList) {
//将每个JSON串转成MAP
Map<String, String> specMap = JSON.parseObject(spec, Map.class);
//合并规格值
for (Map.Entry<String, String> entry : specMap.entrySet()) {
String key = entry.getKey(); //规格名称
String value = entry.getValue(); //规格值
Set<String> specSet = allSpec.get(key);
if (specSet == null) {
specSet = new HashSet<String>();
}
specSet.add(value);
allSpec.put(key, specSet);
}
}
return allSpec;
}
/**
* 导入数据到ES
*/
@Override
public void importGoodsData() {
//调用GoodsAPI 接口获取所有商品信息
List<Good> goods = goodsAPI.getGoods();
//转化为Elasticsearch 所需的Bean类型
List<GoodInfo> goodInfos = JSON.parseArray(JSON.toJSONString(goods), GoodInfo.class);
//获取商品规格
for (GoodInfo goodInfo : goodInfos) {
//将json字符串的规格数据转成map<String,Object>类型。
//map<String,Object>类型会自动转成动态域,其中key为域的名称
goodInfo.setSpecMap(JSON.parseObject(goodInfo.getSpec()));
}
//实现数据批量导入
goodsInfoMapper.saveAll(goodInfos);
}
}
返回值封装SearchResultVO
public class SearchResultVO {
/**
* 结果集
*/
private List<GoodInfo> rows;
/**
* 总数据量
*/
private Long totalData;
/**
* 总页数
*/
private int totalPages;
/**
* 分类列表
*/
private List<String> categoryInfo;
/**
* 规格列表
*/
private Map<String, Set<String>> specInfo;
}
条件查询封装SearchVO
public class SearchVO {
/**
* 关键词
*/
private String keyword;
/**
* 分类
*/
private String category;
/**
* 价格
*/
private String price;
/**
* 排序的域
*/
private String sortField;
/**
* 规格
*/
private Map<String, String> spec;
/**
* 排序的规则
*/
private String sortRule;
/**
* 页码
*/
private String pageNum;
}