目标
- 根据搜索关键字查询
- 条件筛选
- 规格过滤
- 价格区间搜索
- 分页查询
- 排序查询
- 高亮查询
搭建框架
关键字查询-构建搜索条件
需求:01页面原型-》search.html
1搜索服务 com.changgou.search.service
public interface SearchService {
//按照查询条件进行数据查询
Map search(Map<String,String> searchMap);
}
2com.changgou.search.service.impl
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
ElasticsearchTemplate elasticsearchTemplate;
@Override
public Map search(Map<String, String> searchMap) {
//构建查询
if(searchMap!=null){
// 条件构建对象
NativeSearchQueryBuilder nativeSearchQueryBuilder=new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//关键字查询
if(StringUtils.isNotEmpty(searchMap.get("keywords"))){
boolQueryBuilder.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
//开启查询
/**
* 条件构建对象
* 查询实体类
* 查询结果对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
return null;
}
});
}
return null;
}
}
封装查询结果
完善com.changgou.search.service.impl
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
ElasticsearchTemplate elasticsearchTemplate;
@Override
public Map search(Map<String, String> searchMap) {
//结果map
Map<String,Object> resultMap=new HashMap<>();
//构建查询
if(searchMap!=null){
// 条件构建对象
NativeSearchQueryBuilder nativeSearchQueryBuilder=new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//关键字查询
if(StringUtils.isNotEmpty(searchMap.get("keywords"))){
boolQueryBuilder.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
//开启查询
/**
* 条件构建对象
* 查询实体类
* 查询结果对象
*/
//封装查询结果
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
// 查询结果操作
List<T> list=new ArrayList<>();
// 获取结果
SearchHits hits = searchResponse.getHits();
if (hits!=null){
for (SearchHit hit : hits) {
// hit对象转为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
//构建返回对象
return new AggregatedPageImpl<>(list,pageable,hits.getTotalHits(),searchResponse.getAggregations());
}
});
// 封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages",resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
}
return resultMap;
}
}
表现层定义
1 com.changgou.search.controller
@RestController
@RequestMapping("/search")
public class SearchController {
@Autowired
SearchService searchService;
@GetMapping
public Map search(@RequestParam Map<String,String> searchMap){
//特殊符号处理
this.handleSearchMap(searchMap);
Map searchResult = searchService.search(searchMap);
return searchResult;
}
private void handleSearchMap(Map<String, String> searchMap) {
Set<Map.Entry<String, String>> entries = searchMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
if (entry.getKey().startsWith("spec_")){
searchMap.put(entry.getKey(), entry.getValue().replace("+", "%2B"));
}
}
}
}
测试
postman测试 http://localhost:9009/search?keywords=手机
结果:默认分页 每页10条
各种查询
条件查询需求
用户有可能会根据分类搜索、品牌搜索,还有可能根据规格搜索,以及价格搜索和排序 操作。根据分类和品牌搜索的时候,可以直接根据指定域搜索,而规格搜索的域数据是不确定的,价格是一个区间搜索,所以我们可以分为三段实现,先实现分类、品牌搜索,再实现规格搜索,然后实现价格区间搜索。
品牌过滤查询
1完善 SearchServiceImpl
//按照品牌查询
if(StringUtils.isNotEmpty(searchMap.get("brand"))){
boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
测试 http://localhost:9009/search?keywords=手机&brand=华为
品牌聚合查询
需求:查看静态页面,品牌的显示要根据搜索结果不同展现。
1完善 SearchServiceImpl
//按照品牌聚合
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()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
测试: http://localhost:9009/search?keywords=手机
按照规格过滤
需求:
(1)前端发来规格查询:
http://localhost:9009/search?keywords=手机&spec_颜色=红色&spec_网络制式=电信3G&spec_版本=8G%2B256G
(2)后台接到数据后,可以根据前缀spec_来区分是否是规格,如果以 spec_xxx 开始的数据 则为规格数据,需要根据指定规格找信息。
(3)
上图是规格的索引存储格式,真实数据在 “specMap.规格名字.keyword” 中,所以找数据 也是按照如下格式去找。
1完善 SearchServiceImpl
//按照规格过滤查询
for (String key : searchMap.keySet()) {
if(key.startsWith("spec_")){
String value = searchMap.get(key).replace("%2B", "+"); boolQueryBuilder.filter(QueryBuilders.termQuery("specMap."+key.substring(5)+".keyword", value));
}
}
测试: http://localhost:9009/search?keywords=手机&spec_颜色=红色
按照规格聚合
1完善 SearchServiceImpl
//按照规格聚合
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()).collect(Collectors.toList());
resultMap.put("specList", specList);
测试: http://localhost:9009/search?keywords=手机
text 类型 不能进行聚合和排序
keyword 类型 能进行聚合和排序
价格区间查询
需求:价格区间查询,每次需要将价格传入到后台,前端传入后台的价格大概是 price=0‐500 或者 price=500‐1000 依次类推,最后一个是 price=3000 ,后台可以根据-分割,如果分割 得到的结果最多有2个,第1个表示 x<price ,第2个表示 price<=y。
1完善 SearchServiceImpl
//价格区间过滤查询
if(StringUtils.isNotEmpty(searchMap.get("price"))){
String[] prices = searchMap.get("price").split("-");
//500-1000
if(prices.length==2){
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
}
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
}
测试: http://localhost:9009/search?keywords=手机&price=0-500
分页
1完善 SearchServiceImpl
//分页查询
String pageNum = searchMap.get("pageNum");
String pageSize = searchMap.get("pageSize");
if(StringUtils.isEmpty(pageNum)){
pageNum="1";
}
if(StringUtils.isEmpty(pageSize)){
pageSize="30";
}
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum)-1, Integer.parseInt(pageSize)));
// 当前页
resultMap.put("pageNum", pageNum);
测试: http://localhost:9009/search?keywords=手机&pageNum=5&pageSize=20
排序
-
排序这里总共有根据价格排序、根据评价排序、根据新品排序、根据销量排序,排序要 想实现非常简单,只需要告知排序的域以及排序方式即可实现。
-
价格排序:只需要根据价格高低排序即可,降序价格高->低,升序价格低->高
评价排序:评价分为好评、中评、差评,可以在数据库中设计3个列,用来记录好评、中 评、差评的量,每次排序的时候,好评的比例来排序,当然还要有 -
条数限制,评价条数 需要超过N条。
-
新品排序:直接根据商品的发布时间或者更新时间排序。
-
销量排序:销量排序除了销售数量外,还应该要有时间段限制。
1完善 SearchServiceImpl
//排序 //1当前域2升序降序 if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("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)); } }
测试: http://localhost:9009/search?keywords=手机&pageSize=1000&sortField=price&sortRule=Desc
高亮介绍
需求:京东搜索电脑
什么是高亮:搜索结果文字样式不同于其他。
<font class="skcolor_ljg">P30</font>
实现步骤:
1.指定高亮字段:name字段。关键词前后加标签。
2.将高亮字段提取,替换高亮字段:name。
高亮实现
//1设置高亮域
HighlightBuilder.Field field = new HighlightBuilder.Field("name");
//前缀
field.preTags("<span style='color:red'>");
//后缀
field.postTags("</span>");
nativeSearchQueryBuilder.withHighlightFields(field);
//2获取高亮字段
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (highlightFields != null&&highlightFields.size()>0) { skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
}
测试: http://localhost:9009/search?keywords=手机