目录
一、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: search
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
data:
elasticsearch:
cluster-name: my-application
cluster-nodes: 192.168.47.142:9300
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
# 超时配置
ribbon:
ReadTimeout: 300000
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制
enabled: false
isolation:
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;
//SPUID
private Long spuId;
//类目ID
private 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);
// 循环当前 skuInfoList
for (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() { // 执行搜索后,将数据结果集封装到该对象中
@Override
public <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 => ASC
builder.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() { // 执行搜索后,将数据结果集封装到该对象中
@Override
public <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 {
@Autowired
private SkuFeign skuFeign;
@Autowired
private SkuEsMapper skuEsMapper;
/**
* ElasticsearchTemplate: 可以实现索引库的增删改查
*/
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 多条件搜索
* @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 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() { // 执行搜索后,将数据结果集封装到该对象中
@Override
public <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字符串转成Map
Map<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;
}
/**
* 导入索引库
*/
@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);
// 循环当前 skuInfoList
for (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);
}
}