一.关键词查询
1. 实现思路:
2. search微服务查询接口: com.changgou.search.service.SearchServiceImpl.java
(1) Map封装返回结果: 分页功能的实现
Map封装查询条件: 关键字 / 品牌 / 规格 / 销量 / 价格区间..
(2) @Service
public class SearchServiceImple implemtns SearchServiec{
//注入ES模板类完成查询
@Autowired
private ElasticSearchTemplate elasticsearchTemplate;
@Override
public Map search(Map<String, String> searchMap){
//查询结果封装map
Map<String, String> resultMap = new HashMap<>();
if(searchMap!=null){
//参数1: 条件对象
NativeSearchQuery nativesSearchQueryBuilder = new NativeSearchQueryBuilder();
//拼接查询条件
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if(StringUtils.isNotEmpty(searchMap.get("keyword"))){
//模糊匹配查询 商品name域;值
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords").operator(Operator.AND));
}
//封装查询条件
nativeSearchQueryBuilder.withQuery(boolQuery);
//参数2: 查询实体类
//参数3: 结果操作对象
//开启查询
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder(), SkuInfo.class, new SearchResultMapper(){
//参数3接口的方法重写, 查询结果
@Override
public<T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable){
//结果查询集合
List<T> list = new ArrayList<>();
//数据命中
SearchHits hits = searchResponse.getHits();
//遍历命中记录, 得到索引库的每一个docs, 即商品
for(SearchHit hit : hits){
//hit数据的json格式转换为skuInfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T)skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregation());
}
});
//封装返回结果,总记录数
resultMap.put("total", resultInfo.getTotalElements()); //总记录数
resultMap.put("totalPage", resultInfo.getTotalPages()): //总页数
resultMap.put("rows", resultInfo.getContent()); //数据集合
return resultMap;
}
return null;
}
3. search.controller.SearchController.java
@RestController
@RequestMapping("/search")
public class SearchController{
@Autowired
private SearchService searchService;
@GetMapping()
public Map search(@RequestParam Map<String,String> searchMap){
//特殊符号处理: get请求传参在路径上 -> 值会被编码解码
this.handleSearchMap(searchMap);
Map searchResult = searchService.search(searchMap);
//总记录数/页数/数据量
return searchResult;
}
//方法实现
//对搜索入参带有特殊符号进行处理
public void handlerSearchMap(Map<String,String> searchMap){
if(null != searchMap){
//获取键值对集合
Set<Map.Entry<String, String>> entries = searchMap.entrySet();
//遍历
for (Map.Entry<String, String> entry : entries) {
//如果以规格名开头, 则转换
if(entry.getKey().startsWith("spec_")){
//转换"+"为 "%2B
searchMap.put(entry.getKey(),entry.getValue().replace("+","%2B"));
}
}
}
}
}
二. 条件筛选
1. 品牌过滤:
(1) 修改 com.changgou.search.service.impl.SearchServiecImpl.java
//按品牌过滤查询
if(StringUtils.isNotEmpty(searchMap.get("brand"))){
//过滤精确查询
boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
//对品牌域分组查询 -> 聚合查询
String skuBrand = "skuBrand";
//条件查询对象 -> 聚合(分组(列名).(需要分组的域名)) nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName));
//封装品牌分组结果 -> 聚合查询的列名
StringTerms brandTerms = (StringTerms)resultInfo.getAggregation(skuBrand);
//封装数据到集合, 返回给前端 -> 流运算->遍历得到品牌 -> 转换集合保存
List<String> brandList = brandTerms.getBuckets().stream().map(bucket->bucket.getKeyAsString()).colletct(Collectors.toList());
//封装到结果map
resultMap.put("brandList", brandList);
三. 规格过滤 -> 数据操作问题
1. 规格信息 -> 多次筛选 -> 约定规格名 -> 前缀 "spec_" -> 提取spec_开头的字段 ->符合则判断为规格信息
2. 操作域的拼接: SpecMap.specName.keyword
(1)修改com.changgou.search.service.SearchServiceImpl.java
//按照规格进行过滤查询
for(String key: searchMap.keySet()){
if(key.startsWith("spec_"){
//编码解码
String value = searchMap.get(key).replace("%2B", "+");
//过滤精确查询 -> 查询(规格域名, 具体指)
boolQuery.filter(QueryBuilders.termQuery("specMap."+key.subString(5)+".keyword"), value));
}
}
(2) 规格也需要分组/聚合查询 -> 随着搜索结果的改变而变化
修改SearchServiceImpl.java
//按照规格查询
String skuSpec= "skuSpec";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword);
//封装规格分组结果
StringTerms specTerms = (StringTerms)resultInfo.getAggregation(skuSpec);
List<String> specList = specTerms.getBuckets().stream().map(bucket->bucket.getKeyAsString()).toCollect(Collectors.toList());
resultMap.put("specList", specList);
四. 价格区间搜索 -> 聚合查询
1. 需求: 用户选择价格区间 -> 切割价格区间字符串 -> 得到数组的两个元素 -> 价格区间作为过滤条件 -> 如果切割后的数组长度为1, 条件是>=3000
2.实现:
(1) search微服务添加价格区间过滤方法 com.changgou.search.serivce.impl.SearchServiceImpl.java
if(StringUtils.isNotEmpty(searchMap.get("price"))){
String[] prices = searchMap.get("get").split("-");
//0-500元...
if(prices.length==2){ //小于等于第二个元素
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
}
//数组长度不为2, >=3000元, 大于等于第一个元素
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]);
}
五. 分页查询
1. 前端传递参数(当前页+每页显示数)
2. 实现:
(1)修改SearchServiceImple.java -> 开启分页查询
//当前页
String pageNum = searchMap.get("pageNum");
//每页显示数
String pageSize = searchMap.get("pageSize");
//默认第1页
if(StringUtils.isEmpty(pageNum)){
pageNum="1";
}
//默认30页
if(StringUtils.isEmpty(pageSize)){
pageSize="30";
}
(2) //设置分页, 当前页从0开始, 每页显示条数
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum)-1, Integer.parseInt(pageSize)));
(3)返回当前页
//封装当前页
resultMap.put("pageNum", pageNum)
六. 排序查询
1. 分类: 销量排序 / 新品排序 /评价排序
(1). 价格: 降序(高->低), 升序(低->高)
(2). 评价: 好中 差评 -> 数据库3个列 -> 记录评价数量 -> 排序时按照好评的比例排序 -> 条数限制(评价数>N条)
(3). 新品: 商品发布时间 / 更新时间
(4). 销量: 销售数量 -> 时间段限制
(1) 修改SearchServiceImpl.java
//按照相关字段排序查询
//1.当前域 2.升序ASC / 降序DESC if(StringUtils.isNotEmpty(searchMap.get("sortField"))&&StringUtils.isNotEmpty("sortRule"))){
//升序
if("ASC".equals(searchMap.get("sortRule"))){
//条件查询对象
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField))).order(SortOrder.ASC));
}else{
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField))).order(SortOrder.DESC));
}
}
七. 高亮查询
1. 关键词添加html标签(color red) -> 高亮显示搜索条件
2. 通过ES指定高亮域 -> 指定前后缀/html样式 -> 高亮搜索 -> 高亮数据替换非高亮
3.实现:
(1) 基于nativeSearchQueryBuilder对象 -> 设置高亮域 -> 高亮样式
HighlightBuilder.Field field = new HighlightBuilder.Field("name") //高亮域
.preTags("<span style='color:red'>") //高亮前缀
.postTags("</span>"); //高亮后缀
nativeSearchQueryBuilder.withHighlighFields(fields);
(2) 高亮数据替换非高亮数据
Map<String, HighlightField> highlightFields = hit.getHighLightFields(); //得到高亮域
if(highlightFields !=null&&highlightFields.size()>0){
//替换数据
skuInfo.setName(highlightFields.get("name").getFragmetns()[0].toString());
}