本文要实现的一个功能,根据品牌、分类、规格、价格过滤查询商品的功能,并对查询结果的关键字进行高亮显示。只做后端功能。

本文是以代码驱动,如果看不太懂,可以先复制代码,再慢慢看,注释很详细。
1、引入相关依赖

主要就是fastjsonspring-boot-starter-data-elasticsearch(SpringBoot项目),fastJson的作用是转换对象使用,当然也可以进行时间格式化(本文未作处理)。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.28</version>
</dependency>
2、ElasticSearch数据测试准备

①创建Goods类,测试类里注入相关对象

@Document(indexName = "goods_sku",type = "goods")
@Data
public class Goods {@Id@Field(type = FieldType.Long,store = true)private Long id;            // 主键Id@Field(type = FieldType.Text,analyzer = "ik_smart",store = true)private String name;        // 商品名称@Field(type = FieldType.Integer,store = true)private Integer price;      // 商品价格@Field(type = FieldType.Text,store = true,index = false)private String image;       // 商品图片src@Field(type = FieldType.Date,store = true,index = false)private Date createTime;    // 商品创建时间@Field(type = FieldType.Long,store = true,index = false)private Long spuId;         // Spu的Id@Field(type = FieldType.Keyword,store = true)private String categoryName;// 分类名称@Field(type = FieldType.Keyword,store = true)private String brandName;   // 品牌名称@Field(type = FieldType.Object,store = true)private Map spec;           // 规格Map Map<String,String>,如<"颜色","黑色">@Field(type = FieldType.Integer,store = true,index = false)private Integer saleNum;    // 销量public Goods(){}public Goods(Long id, String name, Integer price, String image, Date createTime, Long spuId, String categoryName, String brandName, Map spec, Integer saleNum) {this.id = id;this.name = name;this.price = price;this.image = image;this.createTime = createTime;this.spuId = spuId;this.categoryName = categoryName;this.brandName = brandName;this.spec = spec;this.saleNum = saleNum;}
}
@Autowired
private ElasticsearchTemplate template;
@Autowired
private GoodsRepository goodsRepository;
@Autowired
private EsResultMapper esResultMapper;

②数据准备 - 尽量多准备一些数据,方便测试查询

@Test
public void createIndex(){template.createIndex(Goods.class);
}@Test
public void createDoc(){Map map1 = new HashMap();map1.put("颜色","紫色");map1.put("套餐","标准套餐");Goods goods1 = new Goods(7L,"小米 Mini9秘境黑优惠套餐16G+64G",100,"xxxx",new Date(),2L,"手机","小米",map1,100);goodsRepository.save(goods1);// 使用saveAll批量存储
}

3、构建基本查询方法

该方法通过传过来的条件Map,根据条件进行过滤查询,比如分类、品牌、规格、价格区间等(具体取决于需求)。

/*** 构建基本查询 - 搜索关键字、分类、品牌、规格、价格* @param searchMap* @return*/
private BoolQueryBuilder buildBasicQuery(Map searchMap) {// 构建布尔查询BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 关键字查询boolQueryBuilder.must(QueryBuilders.matchQuery("name",searchMap.get("keywords")));// 分类、品牌、规格 都是需要精准查询的,无需分词// 商品分类过滤if (searchMap.get("category") != null){boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("categoryName",searchMap.get("category")));}// 商品品牌过滤if(searchMap.get("brand") != null){boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("brandName",searchMap.get("brand")));}// 规格过滤if(searchMap.get("spec") != null){Map<String,String> map = (Map) searchMap.get("spec");for(Map.Entry<String,String> entry : map.entrySet()){// 规格查询[spec.xxx],因为规格是不确定的,所以需要精确查找,加上.keyword,如spec.颜色.keywordboolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("spec." + entry.getKey() + ".keyword",entry.getValue()));}}// 价格过滤if(searchMap.get("price") != null){// 价格: 0-500  0-*String[] prices = ((String)searchMap.get("price")).split("-");if(!prices[0].equals("0")){  // 加两个0是,因为价格转换成分boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gt(prices[0] + "00"));}if(!prices[1].equals("*")){  // 价格有上限boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lt(prices[1] + "00"));}}return boolQueryBuilder;
}
4、查询分类列表

主要是根据搜索关键字查询查询出来的结果,将其分类,然后把分类查询出来,显示到前端。

/*** 查询分类列表* @param searchMap* @return*/
private List<String> searchCategoryList(Map searchMap) {NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();// 构建查询BoolQueryBuilder boolQueryBuilder = buildBasicQuery(searchMap);nativeSearchQueryBuilder.withQuery(boolQueryBuilder);// 分类聚合名String groupName = "sku_category";// 构建聚合查询TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms(groupName).field("categoryName");nativeSearchQueryBuilder.addAggregation(termsAggregationBuilder);// 获取聚合分页结果AggregatedPage<Goods> goodsList = (AggregatedPage<Goods>) goodsRepository.search(nativeSearchQueryBuilder.build());// 在查询结果中找到聚合 - 根据聚合名称StringTerms stringTerms = (StringTerms) goodsList.getAggregation(groupName);// 获取桶List<StringTerms.Bucket> buckets = stringTerms.getBuckets();// 使用流Stream 将分类名存入集合List<String> categoryList = buckets.stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());// 打印分类名称categoryList.forEach(System.out::println);return categoryList;
}

既然有了分类,那么肯定还有对应的品牌、规格。其实品牌和规格与分类是有一个联系的。ElasticSearch查询出分类,每个分类对应一个id,也就是说所有分类和分类的id应该存到Redis中去,这样前端就可以根据返回的分类集合去查询对应的品牌和规格,这里只是提供一个实现思路。

String categoryName = "";       // 分类名
if(searchMap.get("category") == null){  // 如果查询条件没有分类// 默认取分类列表的第一个if(categoryList.size() > 0){categoryName = categoryList.get(0);}
}else{      // 如果查询条件有分类// 则取查询条件中的分类categoryName = searchMap.get("category");
}// 根据分类名查询品牌 - 实际应该从Redis中查询
if(searchMap.get("brand")==null) {List<Map> brandList = brandDao.findListByCategoryName(categoryName);resultMap.put("brandList", brandList);
}// 根据分类查询规格 - 实际应该从Redis中查询
List<Map> specList = specDao.findListByCategoryName(categoryName);
for(Map spec:specList){// 规格选项列表 - 选项与选项之间是以,(逗号)分隔的String[] options = ((String) spec.get("options")).split(",");// 讲过规格选项放入到规格对象中spec.put("options",options);
}
// 将规格对象放入到结果集
resultMap.put("specList",specList);
5、重新实现SearchResultMapper - 高亮前奏

因为默认的SearchResultMapper是没有高亮的,我们需要重新实现,重写AggregatedPage方法。

@Component
public class EsResultMapper implements SearchResultMapper {@Overridepublic <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {// 记录总条数long totalHits = response.getHits().getTotalHits();// 记录列表(泛型) - 构建Aggregate使用List<T> list = Lists.newArrayList();// 获取搜索结果(真正的的记录)SearchHits hits = response.getHits();for (SearchHit hit : hits) {if(hits.getHits().length <= 0){return null;}// 将原本的JSON对象转换成Map对象Map<String, Object> map = hit.getSourceAsMap();// 获取高亮的字段MapMap<String, HighlightField> highlightFields = hit.getHighlightFields();for (Map.Entry<String, HighlightField> highlightField : highlightFields.entrySet()) {// 获取高亮的KeyString key = highlightField.getKey();// 获取高亮的ValueHighlightField value = highlightField.getValue();// 实际fragments[0]就是高亮的结果,无需遍历拼接Text[] fragments = value.getFragments();StringBuilder sb = new StringBuilder();for (Text text : fragments) {sb.append(text);}// 因为高亮的字段必然存在于Map中,就是key值// 可能有一种情况,就是高亮的字段是嵌套Map,也就是说在Map里面还有Map的这种情况,这里没有考虑map.put(key, sb.toString());}// 把Map转换成对象T item = JSON.parseObject(JSONObject.toJSONString(map),aClass);list.add(item);}// 返回的是带分页的结果return new AggregatedPageImpl<>(list, pageable, totalHits);}}
6、查询商品(sku)列表
/*** 查询Sku集合 - 商品列表* @param searchMap 查询条件* @return*/
private Map searchSkuList(Map searchMap) {Map resultMap = new HashMap();NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();BoolQueryBuilder boolQueryBuilder = buildBasicQuery(searchMap);// 查询nativeSearchQueryBuilder.withQuery(boolQueryBuilder);// 排序String sortField = (String)searchMap.get("sortField");      // 排序字段String sortRule = (String)searchMap.get("sortRule");        // 排序规则 - 顺序(ASC)/倒序(DESC)if(sortField!= null && !"".equals(sortField)){nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));}// 构建分页nativeSearchQueryBuilder.withPageable(PageRequest.of(0,15));// 构建高亮查询HighlightBuilder.Field field = new HighlightBuilder.Field("name").preTags("<font style='color:red'>").postTags("</font>");nativeSearchQueryBuilder.withHighlightFields(field);  // 名字高亮NativeSearchQuery build = nativeSearchQueryBuilder.build();// 获取查询结果AggregatedPage<Goods> goodsPage = template.queryForPage(build, Goods.class, esResultMapper);long total = goodsPage.getTotalElements();  // 总数据量long totalPage = goodsPage.getTotalPages(); // 总页数// ...你还要将是否有上页下页等内容传过去List<Goods> goodsList = goodsPage.getContent();goodsList.forEach(System.out::println);resultMap.put("rows",goodsList);resultMap.put("total",total);resultMap.put("totalPage",totalPage);return resultMap;
}
7、查询
/*** 搜索方法 - searchMap应该由前端传过来* searchMap里封装了一些条件,根据条件进行过滤*/
@Test
public void search(){// 搜索条件MapMap searchMap = new HashMap();searchMap.put("keywords","小米");
//        searchMap.put("category","手机");
//        searchMap.put("brand","小米");Map map = new HashMap();map.put("颜色","紫色");
//        map.put("","");   // 其他规格类型searchMap.put("spec",map);
//        searchMap.put("price","0-3000");// 返回结果MapMap resultMap = new HashMap();// 查询商品列表resultMap.putAll(searchSkuList(searchMap));// 查询分类列表List<String> categoryList = searchCategoryList(searchMap);resultMap.put("categoryList",categoryList);
}

测试类完整代码

@RunWith(SpringRunner.class)
@SpringBootTest
public class GoodsTest {@Autowiredprivate ElasticsearchTemplate template;@Autowiredprivate GoodsRepository goodsRepository;@Autowiredprivate EsResultMapper esResultMapper;@Testpublic void createIndex(){template.createIndex(Goods.class);}@Testpublic void createDoc(){//        Map map1 = new HashMap();
//        map1.put("颜色","蓝色");
//        map1.put("套餐","标准套餐");
//        Goods goods1 = new Goods(2L,"Redmi Note7秘境黑优惠套餐16G+64G",100,"xxxx",new Date(),2L,"手机","小米",map1,100);
//
//        Map map2 = new HashMap();
//        map2.put("颜色","蓝色");
//        map2.put("套餐","标准套餐");
//        Goods goods2 = new Goods(3L,"Redmi Note7秘境黑优惠套餐16G+64G",500,"xxxx",new Date(),3L,"手机","小米",map2,100);
//
//        Map map3 = new HashMap();
//        map3.put("颜色","黑色");
//        map3.put("尺寸","64寸");
//        Goods goods3 = new Goods(4L,"小米电视 黑色 64寸 优惠套餐",1000,"xxxx",new Date(),4L,"电视","小米",map3,100);
//
//        Map map4 = new HashMap();
//        map4.put("颜色","金色");
//        map4.put("尺寸","46寸");
//        Goods goods4 = new Goods(5L,"华为电视 金色 46寸 优惠套餐",1500,"xxxx",new Date(),5L,"电视","华为",map4,100);
//
//        Map map5 = new HashMap();
//        map5.put("颜色","白金色");
//        map5.put("网络制式","全网通5G");
//        Goods goods5 = new Goods(6L,"华为P30 金色 全网通5G 优惠套餐",2000,"xxxx",new Date(),6L,"手机","华为",map5,100);
//        List<Goods> list = new ArrayList<>();
//        list.add(goods1);
//        list.add(goods2);
//        list.add(goods3);
//        list.add(goods4);
//        list.add(goods5);
//        goodsRepository.saveAll(list);Map map1 = new HashMap();map1.put("颜色","紫色");map1.put("套餐","标准套餐");Goods goods1 = new Goods(7L,"小米 Mini9秘境黑优惠套餐16G+64G",100,"xxxx",new Date(),2L,"手机","小米",map1,100);goodsRepository.save(goods1);
//                Map map1 = new HashMap();
//        map1.put("颜色","蓝色");
//        map1.put("套餐","标准套餐");
//        Goods goods1 = new Goods(2L,"Redmi Note7秘境黑优惠套餐16G+64G",100,"xxxx",new Date(),2L,"手机","小米",map1,100);
//        goodsRepository.save(goods1);}/*** 搜索方法 - searchMap应该由前端传过来* searchMap里封装了一些条件,根据条件进行过滤*/@Testpublic void search(){// 搜索条件MapMap searchMap = new HashMap();searchMap.put("keywords","小米");
//        searchMap.put("category","手机");
//        searchMap.put("brand","小米");Map map = new HashMap();map.put("颜色","紫色");
//        map.put("","");   // 其他规格类型searchMap.put("spec",map);
//        searchMap.put("price","0-3000");// 返回结果MapMap resultMap = new HashMap();// 查询商品列表resultMap.putAll(searchSkuList(searchMap));// 查询分类列表List<String> categoryList = searchCategoryList(searchMap);resultMap.put("categoryList",categoryList);}/*** 查询Sku集合 - 商品列表* @param searchMap 查询条件* @return*/private Map searchSkuList(Map searchMap) {Map resultMap = new HashMap();NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();BoolQueryBuilder boolQueryBuilder = buildBasicQuery(searchMap);// 查询nativeSearchQueryBuilder.withQuery(boolQueryBuilder);// 排序String sortField = (String)searchMap.get("sortField");      // 排序字段String sortRule = (String)searchMap.get("sortRule");        // 排序规则 - 顺序(ASC)/倒序(DESC)if(sortField!= null && !"".equals(sortField)){nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));}// 构建分页nativeSearchQueryBuilder.withPageable(PageRequest.of(0,15));// 构建高亮查询HighlightBuilder.Field field = new HighlightBuilder.Field("name").preTags("<font style='color:red'>").postTags("</font>");nativeSearchQueryBuilder.withHighlightFields(field);  // 名字高亮NativeSearchQuery build = nativeSearchQueryBuilder.build();// 获取查询结果AggregatedPage<Goods> goodsPage = template.queryForPage(build, Goods.class, esResultMapper);long total = goodsPage.getTotalElements();  // 总数据量long totalPage = goodsPage.getTotalPages(); // 总页数// ...你还要将是否有上页下页等内容传过去List<Goods> goodsList = goodsPage.getContent();goodsList.forEach(System.out::println);resultMap.put("rows",goodsList);resultMap.put("total",total);resultMap.put("totalPage",totalPage);return resultMap;}/*** 查询分类列表* @param searchMap* @return*/private List<String> searchCategoryList(Map searchMap) {NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();// 构建查询BoolQueryBuilder boolQueryBuilder = buildBasicQuery(searchMap);nativeSearchQueryBuilder.withQuery(boolQueryBuilder);// 分类聚合名String groupName = "sku_category";// 构建聚合查询TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms(groupName).field("categoryName");nativeSearchQueryBuilder.addAggregation(termsAggregationBuilder);// 获取聚合分页结果AggregatedPage<Goods> goodsList = (AggregatedPage<Goods>) goodsRepository.search(nativeSearchQueryBuilder.build());// 在查询结果中找到聚合 - 根据聚合名称StringTerms stringTerms = (StringTerms) goodsList.getAggregation(groupName);// 获取桶List<StringTerms.Bucket> buckets = stringTerms.getBuckets();// 使用流Stream 将分类名存入集合List<String> categoryList = buckets.stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());// 打印分类名称categoryList.forEach(System.out::println);return categoryList;}/*** 构建基本查询 - 搜索关键字、分类、品牌、规格、价格* @param searchMap* @return*/private BoolQueryBuilder buildBasicQuery(Map searchMap) {// 构建布尔查询BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 关键字查询boolQueryBuilder.must(QueryBuilders.matchQuery("name",searchMap.get("keywords")));// 分类、品牌、规格 都是需要精准查询的,无需分词// 商品分类过滤if (searchMap.get("category") != null){boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("categoryName",searchMap.get("category")));}// 商品品牌过滤if(searchMap.get("brand") != null){boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("brandName",searchMap.get("brand")));}// 规格过滤if(searchMap.get("spec") != null){Map<String,String> map = (Map) searchMap.get("spec");for(Map.Entry<String,String> entry : map.entrySet()){// 规格查询[spec.xxx],因为规格是不确定的,所以需要精确查找,加上.keyword,如spec.颜色.keywordboolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("spec." + entry.getKey() + ".keyword",entry.getValue()));}}// 价格过滤if(searchMap.get("price") != null){// 价格: 0-500  0-*String[] prices = ((String)searchMap.get("price")).split("-");if(!prices[0].equals("0")){  // 加两个0是,因为价格转换成分boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gt(prices[0] + "00"));}if(!prices[1].equals("*")){  // 价格有上限boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lt(prices[1] + "00"));}}return boolQueryBuilder;}
}

SpringDataElasticSearch - NativeSearchQueryBuilder过滤聚合高亮查询相关推荐

  1. Elasticsearch实现商品搜索(关键字查询 条件筛选 规格过滤 价格区间搜索 分页查询 排序查询 高亮查询)

    Elasticsearch实现商品搜索 商品搜索 1.根据关键字查询 2.条件筛选 2.1 品牌筛选 2.1.1 需求分析 2.1.2 代码实现 2.2 规格过滤 2.2.1 需求分析 2.2.2 代 ...

  2. ElasticSerach的简单创建和高亮查询

    ElasticSerach 特点: ​ 高可用 :是指通过设计减少系统不能提供服务的时间,通常的方式为在一个服务器宕机时,我们会自动使用备份服务器进行服务,以保证我们的服务不会停止 ​ 水平扩展 :在 ...

  3. ElasticSearch搜索语法进阶学习(搜索+聚合,过滤+聚合)

    ElasticSearch聚合+搜索语法学习 目录 搜索+聚合:统计指定品牌下每个颜色的销量 global bucket:单个品牌与所有品牌销量对比 过滤+聚合:统计价格大于1200的电视平均价格 b ...

  4. Lucene4:创建查询,并高亮查询关键词

    1. 要求 环境: Lucene 4.1版本/IKAnalyzer 2012 FF版本/mmseg4j 1.9版本 功能: 1).高亮查询演示 注意: 此篇文章开始,索引目录将不再使用示范目录,而是使 ...

  5. MySQL之聚合函数查询

    本文主要介绍一些关于聚合函数查询的语句. create table `t_grade` (`id` int ,`stuName` varchar (60),`course` varchar (60), ...

  6. hql查询过滤器及相关聚合函数查询详解

    一.参数查询 参数查询其实是使用等价代换的方法,使用设置的值替换字符串中指定位置的符号,或者通过使用参数名称,使用字符串来替换参数名称的值,这样能避免sql注入的问题,拼接字符串会出现sql注入的问题 ...

  7. Elasticsearch 7.X-8.0 AggregationBuliders 相关聚合函数(二)桶聚合-嵌套查询

    global 全局聚合 定义搜索执行上下文中所有文档的单个存储桶.此上下文由要搜索的索引和文档类型定义,但不受搜索查询‎‎本身的影响.‎ ‎全局聚合器只能作为顶级聚合器放置,因为将全局聚合器嵌入到另一 ...

  8. 《天池龙珠 - SQL训练营》02.SQL基础:查询与排序-select、运算符、聚合分组查询等

    本笔记为阿里云天池龙珠计划SQL训练营的学习内容,链接为:https://tianchi.aliyun.com/specials/promotion/aicampsql 目录 一.SELECT语句基础 ...

  9. day67 ORM模型之高阶用法整理,聚合,分组查询以及F和Q用法,附练习题整理

    归纳总结的笔记: day67ORM特殊的语法一个简单的语法 --翻译成--> SQL语句语法:1. 操作数据库表 创建表.删除表.修改表2. 操作数据库行 增.删.改.查怎么连数据库:需要手动创 ...

最新文章

  1. 8)排序②排序算法之选择排序[1]直接选择排序
  2. oauth最后的确认按钮_绕过GitHub的OAuth授权验证机制($25000)
  3. POJ 1028: Web Navigation
  4. java中删除字符串的头尾空白符。
  5. 【Idea解法】Failed to execute goal on project : Could not resolve dependencies for pro
  6. sql in 用法(mysql)
  7. Mybatis if标签判断大小
  8. 16c语言第七届省赛,第十二届全国青少年信息学奥林匹克联赛初赛试题及答案普及组、C语言...
  9. 华为惨遭围剿;京东人工智能养猪;三星承认中国市场失败 | 极客头条
  10. 统计标识符个数C语言,C语言文件-统计其中的用户自定义标识符号的个数,并列出用户自定义的标识符号...
  11. hdu1711 Number Sequence kmp模板
  12. 【Qt】arm-none-eabi-gdb-py.exe由于找不到python27.dll 无法继续执行代码
  13. IXDC2018国际体验设计大会精华汇总,微软、阿里巴巴、小米、Adobe等大咖都说了啥?...
  14. iOS逆向之微信和支付宝修改步数 简洁无脑版
  15. 概率论三大公式 排列组合
  16. Android apk 腾讯云-乐固的加固及签名
  17. 计算机网络基础以及linux面试知识点总结
  18. 微型计算机音节,二年级微机下册教案
  19. iOS之nib、xib及storyboard的区别
  20. 如何更高效地在IT职场中摸爬滚打

热门文章

  1. MySQL调优之关联查询、子查询优化
  2. 怎么提升自己的c语言算法,如何快速提高自己的编程能力
  3. Vue实战篇二十七:实现走马灯效果的商品轮播图
  4. 探究InnoDB可重复读
  5. 为什么苹果手机下载不了软件?3步教你解决
  6. apache 多站点配置(httpd.conf简单示例)
  7. 每日linux命令学习-引用符号(反斜杠\,单引号'',双引号)
  8. 【Boost】boost库asio详解1——strand与io_service区别
  9. 工业设计如何从零开始学手绘
  10. 「 程序员的理财与风险控制」让财富跟你一起持续成长:增额终身寿