目录
一、SpringData ElasticSearch介绍
1.1 SpringData
1.2 SpringData ES
二、使用
2.1 配置文件
2.2 Bean对象构造
2.3 功能讲解
2.3.1 数据批量导入
2.3.2 多条件过滤
2.3.3 聚合搜索
2.3.4 分页查询
2.3.5 排序
2.3.6 高亮搜索
2.3.7 全部代码
一、SpringData ElasticSearch介绍
1.1 SpringData
Spring Data 是一个用于简化数据库访问,并支持云服务的开源框架。
其主要目标是使得对数据的访问变得方便快捷,极大的简化 JPA 的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。
1.2 SpringData ES
SpringData ElasticSearch 是基于 spring data API 简化 elasticsearch 操作。让我们无需使用 elasticsearch 自身提供的 API 就能访问和操作数据库。
二、使用
2.1 配置文件
server:port: 18085
spring:application:name: searchmain:allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册data:elasticsearch:cluster-name: my-applicationcluster-nodes: 192.168.47.142:9300
eureka:client:service-url:defaultZone: http://127.0.0.1:7001/eurekainstance:prefer-ip-address: true
feign:hystrix:enabled: true
# 超时配置
ribbon:ReadTimeout: 300000
#hystrix 配置
hystrix:command:default:execution:timeout:#如果enabled设置为false,则请求超时交给ribbon控制enabled: falseisolation:thread:timeoutInMillisenconds: 10000
2.2 Bean对象构造
import lombok.Data;
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.util.Date;
import java.util.Map;@Document(indexName = "skuinfo", type = "docs")
@Data
public class SkuInfo implements Serializable {//商品id,同时也是商品编号@Id@Field(index = true, store = true, type = FieldType.Keyword)private Long id;//SKU名称/*** index: 添加数据的时候,是否分词* type : text 支持分词* analyzer : 创建索引的分词器* store:是否存储* searchAnalyzer: 搜索的时候使用的分词器*/@Field(index = true, store = false, type = FieldType.Text, analyzer = "ik_smart")private String name;//商品价格,单位为:元@Field(type = FieldType.Double)private Long price;//库存数量private Integer num;//商品图片private String image;//商品状态,1-正常,2-下架,3-删除private String status;//创建时间private Date createTime;//更新时间private Date updateTime;//是否默认private String isDefault;//SPUIDprivate Long spuId;//类目IDprivate Long categoryId;//类目名称/*** Type = FieldType.Keyword:不分词* 为了确保搜索更精准,不应对类目进行分词* 例如:手机、电脑*/@Field(type = FieldType.Keyword)private String categoryName;//品牌名称/*** 为了确保品牌搜索更为精准,不应对品牌进行分词* 例如华为,就是华为*/@Field(type = FieldType.Keyword)private String brandName;//规格private String spec;//规格参数private Map<String, Object> specMap;
}
2.3 功能讲解
2.3.1 数据批量导入
在使用 elasticsearch 前,需要将数据提交到elasticsearch便于搜索。项目中使用 Feign 从商品服务中获取到数据,并导入这些数据到 elasticsearch
/*** 导入索引库*/
@Override
public void importData() {// Feign调用,查出SKU数据Result<List<Sku>> skuResult = skuFeign.findAll();// 将LIST<SKU>转换成LIST<SKUINFO>List<SkuInfo> skuInfoList = JSON.parseArray(JSON.toJSONString(skuResult.getData()), SkuInfo.class);// 循环当前 skuInfoListfor (SkuInfo skuInfo : skuInfoList) {// 获取spec => Map(Strig) => Map类型Map<String, Object> specMap = JSON.parseObject(skuInfo.getSpec(), Map.class);// 如果需要生成动态的域,只需要将该域存入到一个Map<String,Object>对象中即可,该// Map<String,Object>的key会生成一个域,域的名字为该Map的key// 当前Map<String,Object>后面Object的值会作为当前Sku对象该域的值skuInfo.setSpecMap(specMap);}// 调用Dao实现批量导入数据skuEsMapper.saveAll(skuInfoList);
}
2.3.2 多条件过滤
通过 NativeSearchQueryBuilder 构建搜索对象,使用 BoolQueryBuilder 构建多条件对象,并且对多条件对象设值的方式,实现多条件过滤
/*** 查询条件构建* @param searchMap* @return*/
private NativeSearchQueryBuilder buildBasicQuery(Map<String, String> searchMap) {// 构建搜索对象,用于封装各种搜索条件的NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();// 构建多条件对象BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();/*** 根据关键词搜索* 构造对应的关键词搜索对象*/if (searchMap != null && searchMap.size() > 0) {// 根据关键词搜索String keywords = searchMap.get("keywords");if (!StringUtils.isEmpty(keywords)) {boolQueryBuilder.must(QueryBuilders.queryStringQuery(keywords).field("name"));}// 根据关键词搜索String category = searchMap.get("category");if (!StringUtils.isEmpty(category)) {boolQueryBuilder.must(QueryBuilders.termQuery("categoryName", category));}// 根据关键词搜索String brand = searchMap.get("brand");if (!StringUtils.isEmpty(brand)) {boolQueryBuilder.must(QueryBuilders.termQuery("brandName", brand));}// 规格过滤实现// 由于可能存在多个规格,故需要循环取出以spec_开头的内容for (Map.Entry<String, String> entry : searchMap.entrySet()) {String key = entry.getKey();if (key.startsWith("spec_")) {String value = entry.getValue();boolQueryBuilder.must(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));}}// 加个区间筛选 0-500元 500-1000元 ... 3000元以上// 去掉元和以上,根据-分割 [0,500] ... [3000]// 根据关键词搜索String price = searchMap.get("price");if (!StringUtils.isEmpty(price)) {price = price.replace("元", "").replace("以上", "");String[] prices = price.split("-");if (prices != null && prices.length > 0) {if (prices[0] != null) {boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.parseInt(prices[0])));}if (prices.length > 1 && Double.valueOf(prices[1]) > Double.valueOf(prices[0])) {boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(prices[1])));}}}}builder.withQuery(boolQueryBuilder);return builder;
}
2.3.3 聚合搜索
聚合搜索等同于数据库中的 group by,即对结果集进行分组
调用函数:
/*** 多条件搜索* @param searchMap* @return*/
@Override
public Map<String, Object> search(Map<String, String> searchMap){// 基本条件构建NativeSearchQueryBuilder builder = buildBasicQuery(searchMap);// 集合搜索// 当用户没有输入集合选择的时候,则需要去搜索,若选择了,则无需再去搜索Map<String, Object> resultMap = searchList(builder);// 获取分组数据Map<String, Object> groupMap = searchGroupList(builder, searchMap);// 合并分组数据和搜索数据resultMap.putAll(groupMap);return resultMap;
}
实现方法:
/*** 结果集搜索* @param builder* @return*/
private Map<String, Object> searchList(NativeSearchQueryBuilder builder) {/// 开启高亮和配置 ///// 将商品名称作为高亮对象HighlightBuilder.Field field = new HighlightBuilder.Field("name");// 前缀 <em style="color:red">field.preTags("<em style=\"color:red\">");// 后缀 </em>field.postTags("</em>");// 后缀长度 关键词数据的长度field.fragmentSize(100);// 添加高亮builder.withHighlightFields(field);/*** 执行搜索* 1. 搜索条件封装* 2. 搜索结果集(集合数据),需要转换的类型* 3. AggregatedPage<SkuInfo> 搜索结果集的封装*/AggregatedPage<SkuInfo> page = elasticsearchTemplate.queryForPage(builder.build(), // 搜索条件封装SkuInfo.class, // 数据集合要转换的类型的字节码new SearchResultMapper() { // 执行搜索后,将数据结果集封装到该对象中@Overridepublic <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {// 存储所有转换后的高亮数据对象List<T> list = new ArrayList<>();// 执行查询,获取所有数据=>结果集[非高亮数据,高亮数据]for (SearchHit hit : response.getHits()) {// 分析结果集数据,获取非高亮数据SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);// 分析结果集数据,获取高亮数据 => 只有某个域的高亮数据HighlightField highlightField = hit.getHighlightFields().get("name");if (highlightField != null && highlightField.getFragments() != null) {// 将高亮数据读取出来Text[] fragments = highlightField.getFragments();StringBuffer buffer = new StringBuffer();for (Text fragment : fragments) {buffer.append(fragment.toString());}// 将非高亮数据中指定的域替换成高亮数据skuInfo.setName(buffer.toString());}// 将高亮数据添加到集合中list.add((T)skuInfo);}// 将数据返回/*** 1. 搜索的集合数据(携带高亮)* 2. 分页对象信息* 3. 搜索记录的总条数*/return new AggregatedPageImpl<T>(list, pageable, response.getHits().getTotalHits());}});// 获取数据结果集long totalElements = page.getTotalElements();int totalPages = page.getTotalPages();List<SkuInfo> contents = page.getContent();// 封装一个Map存储所有数据,并返回Map<String, Object> resultMap = new HashMap<String, Object>();resultMap.put("rows", contents);resultMap.put("total", totalElements);resultMap.put("totalPages", totalPages);return resultMap;
}/*** 分组查询 => 根据分类分组、品牌分组、规格分组* @param builder* @return*/
private Map<String, Object> searchGroupList(NativeSearchQueryBuilder builder, Map<String, String> searchMap) {/*** 根据分类名称进行分组* addAggregation 添加一个聚合操作* 1)skuCategory 取别名* 2)categoryName 表示根据哪个域进行分组查询*/if (searchMap == null || StringUtils.isEmpty(searchMap.get("category"))) {builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));}if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {builder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName"));}builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));// 根据条件分组获取AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);// 定义一个Map,存储所有分组结果Map<String, Object> groupMapResult = new HashMap<String, Object>();/*** 获取分组数据* getAggregations 获取的是集合,可以根据多个域进行分组* .get("skuCategory") 获取指定域的集合数据*/if (searchMap == null || StringUtils.isEmpty(searchMap.get("caategory"))) {StringTerms categoryTerms = aggregatedPage.getAggregations().get("skuCategory");// 获取分类分组集合数据List<String> categoryList = getGroupList(categoryTerms);groupMapResult.put("categoryList", categoryList);}if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {StringTerms brandTerms = aggregatedPage.getAggregations().get("skuBrand");// 获取品牌分组集合数据List<String> brandList = getGroupList(brandTerms);groupMapResult.put("brandList", brandList);}// 获取规格分组集合数据StringTerms specTerms = aggregatedPage.getAggregations().get("skuSpec");List<String> specList = getGroupList(specTerms);groupMapResult.put("specList", specList);return groupMapResult;
}
2.3.4 分页查询
当数据量过多时,可以使用分页查询配合前端展示。
通过 withPageable 设置分页参数
/*** 查询条件构建* @param searchMap* @return*/
private NativeSearchQueryBuilder buildBasicQuery(Map<String, String> searchMap) {// 构建搜索对象,用于封装各种搜索条件的NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();// 构建多条件对象BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();/*** 根据关键词搜索* 构造对应的关键词搜索对象*/if (searchMap != null && searchMap.size() > 0) {.....}// 分页处理,若不传则默认第一页Map<String, Integer> pageMap = coverterPage(searchMap);builder.withPageable(PageRequest.of(pageMap.get("pageNum") - 1, pageMap.get("pageSize")));builder.withQuery(boolQueryBuilder);return builder;
}/*** 接收前端传入的分页参数* @param searchMap* @return*/
public Map<String,Integer> coverterPage(Map<String,String> searchMap) {Map<String, Integer> map = new HashMap<String, Integer>();Integer defPageNum = 1;Integer defPageSize = 10;if (searchMap != null){try {if (!StringUtils.isEmpty(searchMap.get("pageNum"))) {map.put("pageNum",Integer.parseInt(searchMap.get("pageNum")));} else {map.put("pageNum", defPageNum);}if (!StringUtils.isEmpty(searchMap.get("pageSize"))) {map.put("pageSize",Integer.parseInt(searchMap.get("pageSize")));} else {map.put("pageSize", defPageSize);}} catch (Exception e) {map.put("pageNum", defPageNum);map.put("pageSize", defPageSize);}}return map;
}
测试:
http://localhost:18085/search?keywords=%E5%8D%8E%E4%B8%BA&pageNum=2&pageSize=3
2.3.5 排序
通过 withSort 实现排序
/*** 查询条件构建* @param searchMap* @return*/
private NativeSearchQueryBuilder buildBasicQuery(Map<String, String> searchMap) {// 构建搜索对象,用于封装各种搜索条件的NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();// 构建多条件对象BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();/*** 根据关键词搜索* 构造对应的关键词搜索对象*/if (searchMap != null && searchMap.size() > 0) {....../*** 排序实现*/String sortField = searchMap.get("sortField");String sortRole = searchMap.get("sortRule");if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRole.toUpperCase())) {// 要将传来的字符串转成大写,如asc => ASCbuilder.withSort(new FieldSortBuilder(sortField).order(SortOrder.valueOf(sortRole)));}}...builder.withQuery(boolQueryBuilder);return builder;
}
测试:
http://localhost:18085/search?pageNum=1&sortField=price&sortRule=desc
2.3.6 高亮搜索
高亮搜索指根据商品关键字搜索商品时,显示的页面对关键字给定了特殊样式,让他显示得更加突出。
如京东,对高亮搜索的关键词显示为红色
/*** 结果集搜索* @param builder* @return*/
private Map<String, Object> searchList(NativeSearchQueryBuilder builder) {/// 开启高亮和配置 ///// 将商品名称作为高亮对象HighlightBuilder.Field field = new HighlightBuilder.Field("name");// 前缀 <em style="color:red">field.preTags("<em style=\"color:red\">");// 后缀 </em>field.postTags("</em>");// 后缀长度 关键词数据的长度field.fragmentSize(100);// 添加高亮builder.withHighlightFields(field);/*** 执行搜索* 1. 搜索条件封装* 2. 搜索结果集(集合数据),需要转换的类型* 3. AggregatedPage<SkuInfo> 搜索结果集的封装*/AggregatedPage<SkuInfo> page = elasticsearchTemplate.queryForPage(builder.build(), // 搜索条件封装SkuInfo.class, // 数据集合要转换的类型的字节码new SearchResultMapper() { // 执行搜索后,将数据结果集封装到该对象中@Overridepublic <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {// 存储所有转换后的高亮数据对象List<T> list = new ArrayList<>();// 执行查询,获取所有数据=>结果集[非高亮数据,高亮数据]for (SearchHit hit : response.getHits()) {// 分析结果集数据,获取非高亮数据SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);// 分析结果集数据,获取高亮数据 => 只有某个域的高亮数据HighlightField highlightField = hit.getHighlightFields().get("name");if (highlightField != null && highlightField.getFragments() != null) {// 将高亮数据读取出来Text[] fragments = highlightField.getFragments();StringBuffer buffer = new StringBuffer();for (Text fragment : fragments) {buffer.append(fragment.toString());}// 将非高亮数据中指定的域替换成高亮数据skuInfo.setName(buffer.toString());}// 将高亮数据添加到集合中list.add((T)skuInfo);}// 将数据返回/*** 1. 搜索的集合数据(携带高亮)* 2. 分页对象信息* 3. 搜索记录的总条数*/return new AggregatedPageImpl<T>(list, pageable, response.getHits().getTotalHits());}});// 获取数据结果集long totalElements = page.getTotalElements();int totalPages = page.getTotalPages();List<SkuInfo> contents = page.getContent();// 封装一个Map存储所有数据,并返回Map<String, Object> resultMap = new HashMap<String, Object>();resultMap.put("rows", contents);resultMap.put("total", totalElements);resultMap.put("totalPages", totalPages);return resultMap;
}
测试:
2.3.7 全部代码
import com.alibaba.fastjson.JSON;
import io.swagger.models.auth.In;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
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.StringUtils;import java.util.*;/*** @File: SkuServiceImpl* @Description:* @Author: tom* @Create: 2020-06-04 09:13**/
@Service
public class SkuServiceImpl implements SkuService {@Autowiredprivate SkuFeign skuFeign;@Autowiredprivate SkuEsMapper skuEsMapper;/*** ElasticsearchTemplate: 可以实现索引库的增删改查*/@Autowiredprivate ElasticsearchTemplate elasticsearchTemplate;/*** 多条件搜索* @param searchMap* @return*/@Overridepublic Map<String, Object> search(Map<String, String> searchMap){// 基本条件构建NativeSearchQueryBuilder builder = buildBasicQuery(searchMap);// 集合搜索// 当用户没有输入集合选择的时候,则需要去搜索,若选择了,则无需再去搜索Map<String, Object> resultMap = searchList(builder);// 获取分组数据Map<String, Object> groupMap = searchGroupList(builder, searchMap);resultMap.putAll(groupMap);return resultMap;}/*** 查询条件构建* @param searchMap* @return*/private NativeSearchQueryBuilder buildBasicQuery(Map<String, String> searchMap) {// 构建搜索对象,用于封装各种搜索条件的NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();// 构建多条件对象BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();/*** 根据关键词搜索* 构造对应的关键词搜索对象*/if (searchMap != null && searchMap.size() > 0) {// 根据关键词搜索String keywords = searchMap.get("keywords");if (!StringUtils.isEmpty(keywords)) {boolQueryBuilder.must(QueryBuilders.queryStringQuery(keywords).field("name"));}// 根据关键词搜索String category = searchMap.get("category");if (!StringUtils.isEmpty(category)) {boolQueryBuilder.must(QueryBuilders.termQuery("categoryName", category));}// 根据关键词搜索String brand = searchMap.get("brand");if (!StringUtils.isEmpty(brand)) {boolQueryBuilder.must(QueryBuilders.termQuery("brandName", brand));}// 规格过滤实现// 由于可能存在多个规格,故需要循环取出以spec_开头的内容for (Map.Entry<String, String> entry : searchMap.entrySet()) {String key = entry.getKey();if (key.startsWith("spec_")) {String value = entry.getValue();boolQueryBuilder.must(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));}}// 加个区间筛选 0-500元 500-1000元 ... 3000元以上// 去掉元和以上,根据-分割 [0,500] ... [3000]// 根据关键词搜索String price = searchMap.get("price");if (!StringUtils.isEmpty(price)) {price = price.replace("元", "").replace("以上", "");String[] prices = price.split("-");if (prices != null && prices.length > 0) {if (prices[0] != null) {boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.parseInt(prices[0])));}if (prices.length > 1 && Double.valueOf(prices[1]) > Double.valueOf(prices[0])) {boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(prices[1])));}}}/*** 排序实现*/String sortField = searchMap.get("sortField");String sortRole = searchMap.get("sortRule");if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRole)) {builder.withSort(new FieldSortBuilder(sortField).order(SortOrder.valueOf(sortRole.toUpperCase())));}}// 分页处理,若不传则默认第一页Map<String, Integer> pageMap = coverterPage(searchMap);builder.withPageable(PageRequest.of(pageMap.get("pageNum") - 1, pageMap.get("pageSize")));builder.withQuery(boolQueryBuilder);return builder;}/*** 接收前端传入的分页参数* @param searchMap* @return*/public Map<String,Integer> coverterPage(Map<String,String> searchMap) {Map<String, Integer> map = new HashMap<String, Integer>();Integer defPageNum = 1;Integer defPageSize = 10;if (searchMap != null){try {if (!StringUtils.isEmpty(searchMap.get("pageNum"))) {map.put("pageNum",Integer.parseInt(searchMap.get("pageNum")));} else {map.put("pageNum", defPageNum);}if (!StringUtils.isEmpty(searchMap.get("pageSize"))) {map.put("pageSize",Integer.parseInt(searchMap.get("pageSize")));} else {map.put("pageSize", defPageSize);}} catch (Exception e) {map.put("pageNum", defPageNum);map.put("pageSize", defPageSize);}}return map;}/*** 结果集搜索* @param builder* @return*/private Map<String, Object> searchList(NativeSearchQueryBuilder builder) {/// 开启高亮和配置 ///// 将商品名称作为高亮对象HighlightBuilder.Field field = new HighlightBuilder.Field("name");// 前缀 <em style="color:red">field.preTags("<em style=\"color:red\">");// 后缀 </em>field.postTags("</em>");// 后缀长度 关键词数据的长度field.fragmentSize(100);// 添加高亮builder.withHighlightFields(field);/*** 执行搜索* 1. 搜索条件封装* 2. 搜索结果集(集合数据),需要转换的类型* 3. AggregatedPage<SkuInfo> 搜索结果集的封装*/AggregatedPage<SkuInfo> page = elasticsearchTemplate.queryForPage(builder.build(), // 搜索条件封装SkuInfo.class, // 数据集合要转换的类型的字节码new SearchResultMapper() { // 执行搜索后,将数据结果集封装到该对象中@Overridepublic <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {// 存储所有转换后的高亮数据对象List<T> list = new ArrayList<>();// 执行查询,获取所有数据=>结果集[非高亮数据,高亮数据]for (SearchHit hit : response.getHits()) {// 分析结果集数据,获取非高亮数据SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);// 分析结果集数据,获取高亮数据 => 只有某个域的高亮数据HighlightField highlightField = hit.getHighlightFields().get("name");if (highlightField != null && highlightField.getFragments() != null) {// 将高亮数据读取出来Text[] fragments = highlightField.getFragments();StringBuffer buffer = new StringBuffer();for (Text fragment : fragments) {buffer.append(fragment.toString());}// 将非高亮数据中指定的域替换成高亮数据skuInfo.setName(buffer.toString());}// 将高亮数据添加到集合中list.add((T)skuInfo);}// 将数据返回/*** 1. 搜索的集合数据(携带高亮)* 2. 分页对象信息* 3. 搜索记录的总条数*/return new AggregatedPageImpl<T>(list, pageable, response.getHits().getTotalHits());}});// 获取数据结果集long totalElements = page.getTotalElements();int totalPages = page.getTotalPages();List<SkuInfo> contents = page.getContent();// 封装一个Map存储所有数据,并返回Map<String, Object> resultMap = new HashMap<String, Object>();resultMap.put("rows", contents);resultMap.put("total", totalElements);resultMap.put("totalPages", totalPages);return resultMap;}/*** 分组查询 => 根据分类分组、品牌分组、规格分组* @param builder* @return*/private Map<String, Object> searchGroupList(NativeSearchQueryBuilder builder, Map<String, String> searchMap) {/*** 根据分类名称进行分组* addAggregation 添加一个聚合操作* 1)skuCategory 取别名* 2)categoryName 表示根据哪个域进行分组查询*/if (searchMap == null || StringUtils.isEmpty(searchMap.get("category"))) {builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));}if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {builder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName"));}builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);// 定义一个Map,存储所有分组结果Map<String, Object> groupMapResult = new HashMap<String, Object>();/*** 获取分组数据* getAggregations 获取的是集合,可以根据多个域进行分组* .get("skuCategory") 获取指定域的集合数据*/if (searchMap == null || StringUtils.isEmpty(searchMap.get("caategory"))) {StringTerms categoryTerms = aggregatedPage.getAggregations().get("skuCategory");// 获取分类分组集合数据List<String> categoryList = getGroupList(categoryTerms);groupMapResult.put("categoryList", categoryList);}if (searchMap == null || StringUtils.isEmpty(searchMap.get("brand"))) {StringTerms brandTerms = aggregatedPage.getAggregations().get("skuBrand");// 获取品牌分组集合数据List<String> brandList = getGroupList(brandTerms);groupMapResult.put("brandList", brandList);}// 获取规格分组集合数据StringTerms specTerms = aggregatedPage.getAggregations().get("skuSpec");List<String> specList = getGroupList(specTerms);groupMapResult.put("specList", specList);return groupMapResult;}/*** 获取分组集合数据* @param stringTerms* @return*/public List<String> getGroupList(StringTerms stringTerms) {List<String> groupList = new ArrayList<String>();for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {String keyName = bucket.getKeyAsString();groupList.add(keyName);}return groupList;}/*** 分类分组查询* @param builder* @return*/private List<String> searchCategoryList(NativeSearchQueryBuilder builder) {/*** 根据分类名称进行分组* addAggregation 添加一个聚合操作* 1)skuCategory 取别名* 2)categoryName 表示根据哪个域进行分组查询*/builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);/*** 获取分组数据* getAggregations 获取的是集合,可以根据多个域进行分组* .get("skuCategory") 获取指定域的集合数据*/StringTerms aggregation = aggregatedPage.getAggregations().get("skuCategory");List<String> categoryList = new ArrayList<String>();for (StringTerms.Bucket bucket : aggregation.getBuckets()) {String categoryName = bucket.getKeyAsString();// 其中的一个分类名称,如手机categoryList.add(categoryName);}return categoryList;}/*** 品牌分组查询* @param builder* @return*/private List<String> searchBrandList(NativeSearchQueryBuilder builder) {/*** 根据品牌名称进行分组* addAggregation 添加一个聚合操作* 1)skuCategory 取别名* 2)categoryName 表示根据哪个域进行分组查询*/builder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName"));AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);/*** 获取分组数据* getAggregations 获取的是集合,可以根据多个域进行分组* .get("skuCategory") 获取指定域的集合数据 {华为,小米,中型}*/StringTerms aggregation = aggregatedPage.getAggregations().get("skuBrand");List<String> brandList = new ArrayList<String>();for (StringTerms.Bucket bucket : aggregation.getBuckets()) {String categoryName = bucket.getKeyAsString();// 其中的一个分类名称,如手机brandList.add(categoryName);}return brandList;}/*** 规格分组查询* @param builder* @return*/private Map<String, Set<String>> searchSpecList(NativeSearchQueryBuilder builder) {/*** 根据规格名称进行分组* addAggregation 添加一个聚合操作* 1)skuCategory 取别名* 2)categoryName 表示根据哪个域进行分组查询*/builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(builder.build(), SkuInfo.class);/*** 获取规格数据* getAggregations 获取的是集合,可以根据多个域进行分组* .get("skuCategory") 获取指定域的集合数据*/StringTerms aggregation = aggregatedPage.getAggregations().get("skuSpec");List<String> specList = new ArrayList<String>();for (StringTerms.Bucket bucket : aggregation.getBuckets()) {String specName = bucket.getKeyAsString();// 其中的一个分类名称,如手机specList.add(specName);}// 循环 specList,将每个JSON字符串转成MapMap<String, Set<String>> allSpec = new HashMap<String, Set<String>>();for (String spec : specList) {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;}/*** 导入索引库*/@Overridepublic void importData() {// Feign调用,查出SKU数据Result<List<Sku>> skuResult = skuFeign.findAll();// 将LIST<SKU>转换成LIST<SKUINFO>List<SkuInfo> skuInfoList = JSON.parseArray(JSON.toJSONString(skuResult.getData()), SkuInfo.class);// 循环当前 skuInfoListfor (SkuInfo skuInfo : skuInfoList) {// 获取spec => Map(Strig) => Map类型Map<String, Object> specMap = JSON.parseObject(skuInfo.getSpec(), Map.class);// 如果需要生成动态的域,只需要将该域存入到一个Map<String,Object>对象中即可,该// Map<String,Object>的key会生成一个域,域的名字为该Map的key// 当前Map<String,Object>后面Object的值会作为当前Sku对象该域的值skuInfo.setSpecMap(specMap);}// 调用Dao实现批量导入数据skuEsMapper.saveAll(skuInfoList);}
}