1. day07 ES搜索

2. 根据关键字查询

changgou_service_search项目创建SearchService接口

  1. package com.changgou.search.service;
  2. import java.util.Map;
  3. public interface SearchService {
  4. //按照查询添加进行数据查询
  5. Map search(Map<String ,String> searchMap);
  6. }

创建SearchService接口实现类SearchServiceImpl

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.data.domain.Pageable;
  17. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  18. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  19. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  20. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  21. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  22. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  23. import org.springframework.stereotype.Service;
  24. import java.util.*;
  25. @Service
  26. public class SearchServiceImpl implements SearchService {
  27. @Autowired
  28. private ElasticsearchTemplate elasticsearchTemplate;
  29. /**
  30. * 根据前端传过来的字段进行查询
  31. *
  32. * @param searchMap
  33. * @return
  34. */
  35. @Override
  36. public Map search(Map<String, String> searchMap) {
  37. Map<String, Object> resultMap = new HashMap<>();
  38. //构建查询
  39. if (searchMap != null) {
  40. //构建查询条件封装对象
  41. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  42. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  43. //按照关键字查询
  44. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  45. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  46. }
  47. nativeSearchQueryBuilder.withQuery(boolQuery);
  48. //开启查询
  49. /**
  50. * 第一个参数: 条件的构建对象
  51. * 第二个参数: 查询操作实体类
  52. * 第三个参数: 查询结果操作对象
  53. */
  54. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  55. new SearchResultMapper() {
  56. @Override
  57. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  58. //查询结果操作
  59. List<T> list = new ArrayList<>();
  60. //获取查询命中的数据
  61. SearchHits hits = searchResponse.getHits();
  62. if (hits != null) {
  63. //非空
  64. for (SearchHit hit : hits) {
  65. //SearchHit转换为skuinfo
  66. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  67. list.add((T) skuInfo);
  68. }
  69. }
  70. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  71. }
  72. });
  73. //封装最终返回结果
  74. //总记录数
  75. resultMap.put("total", resultInfo.getTotalElements());
  76. //总页数
  77. resultMap.put("totalPages", resultInfo.getTotalPages());
  78. //数据集合
  79. resultMap.put("rows", resultInfo.getContent());
  80. return resultMap;
  81. }
  82. return null;
  83. }
  84. }

changgou_service_search项目创建SearchController

  1. package com.changgou.search.controller;
  2. import com.changgou.search.service.SearchService;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RequestParam;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import java.util.Map;
  9. import java.util.Set;
  10. @RestController
  11. @RequestMapping("/search")
  12. public class SearchController {
  13. @Autowired
  14. private SearchService searchService;
  15. @GetMapping
  16. public Map search(@RequestParam Map<String, String> searchMap) {
  17. //特殊符号处理
  18. this.handleSearchMap(searchMap);
  19. Map searchResult = searchService.search(searchMap);
  20. return searchResult;
  21. }
  22. private void handleSearchMap(Map<String, String> searchMap) {
  23. Set<Map.Entry<String, String>> entries = searchMap.entrySet();
  24. for (Map.Entry<String, String> entry : entries) {
  25. if (entry.getKey().startsWith("spec_")) {
  26. searchMap.put(entry.getKey(), entry.getValue().replace("+", "%2B"));
  27. }
  28. }
  29. }
  30. }

删除索引库 重新导入索引 请求指定 localhost:9009/manager/importAll

测试关键字查询 localhost:9009/search?keywords=手机

3. 品牌条件筛选

用户有可能会根据分类搜索、品牌搜索,还有可能根据规格搜索,以及价格搜索和排序操作。根据分类和品牌搜索的时候,可以直接根据指定域搜索,而规格搜索的域数据是不确定的,价格是一个区间搜索,所以我们可以分为三段实现,先实现分类、品牌搜素,再实现规格搜索,然后实现价格区间搜索。

07. day07 ES搜索 - 图1

页面每次向后台传入对应的分类和品牌,后台据分类和品牌进行条件过滤即可。

修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法 在关键字查询boolQuery构建下添加品牌过滤

  1. //按照品牌进行过滤查询
  2. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  3. boolQuery.filter(QueryBuilders.termQuery("brandName",searchMap.get("brand")));
  4. }

完整代码

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.data.domain.Pageable;
  17. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  18. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  19. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  20. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  21. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  22. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  23. import org.springframework.stereotype.Service;
  24. import java.util.*;
  25. @Service
  26. public class SearchServiceImpl implements SearchService {
  27. @Autowired
  28. private ElasticsearchTemplate elasticsearchTemplate;
  29. /**
  30. * 根据前端传过来的字段进行查询
  31. *
  32. * @param searchMap
  33. * @return
  34. */
  35. @Override
  36. public Map search(Map<String, String> searchMap) {
  37. Map<String, Object> resultMap = new HashMap<>();
  38. //构建查询
  39. if (searchMap != null) {
  40. //构建查询条件封装对象
  41. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  42. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  43. //按照关键字查询
  44. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  45. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  46. }
  47. //按照品牌进行过滤查询
  48. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  49. boolQuery.filter(QueryBuilders.termQuery("brandName",searchMap.get("brand")));
  50. }
  51. nativeSearchQueryBuilder.withQuery(boolQuery);
  52. //开启查询
  53. /**
  54. * 第一个参数: 条件的构建对象
  55. * 第二个参数: 查询操作实体类
  56. * 第三个参数: 查询结果操作对象
  57. */
  58. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  59. new SearchResultMapper() {
  60. @Override
  61. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  62. //查询结果操作
  63. List<T> list = new ArrayList<>();
  64. //获取查询命中的数据
  65. SearchHits hits = searchResponse.getHits();
  66. if (hits != null) {
  67. //非空
  68. for (SearchHit hit : hits) {
  69. //SearchHit转换为skuinfo
  70. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  71. list.add((T) skuInfo);
  72. }
  73. }
  74. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  75. }
  76. });
  77. //封装最终返回结果
  78. //总记录数
  79. resultMap.put("total", resultInfo.getTotalElements());
  80. //总页数
  81. resultMap.put("totalPages", resultInfo.getTotalPages());
  82. //数据集合
  83. resultMap.put("rows", resultInfo.getContent());
  84. return resultMap;
  85. }
  86. return null;
  87. }
  88. }

测试请求 localhost:9009/search?keywords=手机&brand=华为

3.1. 按照品牌进行聚合查询

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.aggregations.AggregationBuilders;
  16. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.data.domain.Pageable;
  19. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  20. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  21. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  22. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  23. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  24. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  25. import org.springframework.stereotype.Service;
  26. import java.util.*;
  27. import java.util.stream.Collectors;
  28. @Service
  29. public class SearchServiceImpl implements SearchService {
  30. @Autowired
  31. private ElasticsearchTemplate elasticsearchTemplate;
  32. /**
  33. * 根据前端传过来的字段进行查询
  34. *
  35. * @param searchMap
  36. * @return
  37. */
  38. @Override
  39. public Map search(Map<String, String> searchMap) {
  40. Map<String, Object> resultMap = new HashMap<>();
  41. //构建查询
  42. if (searchMap != null) {
  43. //构建查询条件封装对象
  44. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  45. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  46. //按照关键字查询
  47. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  48. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  49. }
  50. //按照品牌进行过滤查询
  51. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  52. boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
  53. }
  54. nativeSearchQueryBuilder.withQuery(boolQuery);
  55. //按照品牌进行分组(聚合)查询
  56. String skuBrand = "skuBrand";
  57. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
  58. //开启查询
  59. /**
  60. * 第一个参数: 条件的构建对象
  61. * 第二个参数: 查询操作实体类
  62. * 第三个参数: 查询结果操作对象
  63. */
  64. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  65. new SearchResultMapper() {
  66. @Override
  67. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  68. //查询结果操作
  69. List<T> list = new ArrayList<>();
  70. //获取查询命中的数据
  71. SearchHits hits = searchResponse.getHits();
  72. if (hits != null) {
  73. //非空
  74. for (SearchHit hit : hits) {
  75. //SearchHit转换为skuinfo
  76. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  77. list.add((T) skuInfo);
  78. }
  79. }
  80. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  81. }
  82. });
  83. //封装最终返回结果
  84. //总记录数
  85. resultMap.put("total", resultInfo.getTotalElements());
  86. //总页数
  87. resultMap.put("totalPages", resultInfo.getTotalPages());
  88. //数据集合
  89. resultMap.put("rows", resultInfo.getContent());
  90. //封装品牌的分组结果
  91. StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
  92. List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  93. resultMap.put("brandList", brandList);
  94. return resultMap;
  95. }
  96. return null;
  97. }
  98. }

请求地址 localhost:9009/search?keywords=电视

添加品牌聚合后 通过关键字查询出结果中包含的品牌列表

4. 规格过滤

规格这一部分,需要向后台发送规格名字以及规格值,我们可以按照一定要求来发送数据,例如规格名字以特殊前缀提交到后台:spec_网络制式:电信4G、spec_显示屏尺寸:4.0-4.9英寸

后台接到数据后,可以根据前缀spec_来区分是否是规格,如果以spec_xxx开始的数据则为规格数据,需要根据指定规格找信息。

修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法 在关键字查询boolQuery构建下添加

  1. //按照规格进行过滤查询
  2. for (String key : searchMap.keySet()) {
  3. if (key.startsWith("spec_")){
  4. String value = searchMap.get(key).replace("%2B","+");
  5. boolQuery.filter(QueryBuilders.termQuery("specMap."+key.substring(5)+".keyword",value));
  6. }
  7. }

完整代码

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.aggregations.AggregationBuilders;
  16. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.data.domain.Pageable;
  19. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  20. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  21. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  22. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  23. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  24. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  25. import org.springframework.stereotype.Service;
  26. import java.util.*;
  27. import java.util.stream.Collectors;
  28. @Service
  29. public class SearchServiceImpl implements SearchService {
  30. @Autowired
  31. private ElasticsearchTemplate elasticsearchTemplate;
  32. /**
  33. * 根据前端传过来的字段进行查询
  34. *
  35. * @param searchMap
  36. * @return
  37. */
  38. @Override
  39. public Map search(Map<String, String> searchMap) {
  40. Map<String, Object> resultMap = new HashMap<>();
  41. //构建查询
  42. if (searchMap != null) {
  43. //构建查询条件封装对象
  44. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  45. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  46. //按照关键字查询
  47. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  48. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  49. }
  50. //按照品牌进行过滤查询
  51. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  52. boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
  53. }
  54. //按照规格进行过滤查询
  55. for (String key : searchMap.keySet()) {
  56. if (key.startsWith("spec_")){
  57. String value = searchMap.get(key).replace("%2B","+");
  58. boolQuery.filter(QueryBuilders.termQuery("specMap."+key.substring(5)+".keyword",value));
  59. }
  60. }
  61. nativeSearchQueryBuilder.withQuery(boolQuery);
  62. //按照品牌进行分组(聚合)查询
  63. String skuBrand = "skuBrand";
  64. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
  65. //开启查询
  66. /**
  67. * 第一个参数: 条件的构建对象
  68. * 第二个参数: 查询操作实体类
  69. * 第三个参数: 查询结果操作对象
  70. */
  71. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  72. new SearchResultMapper() {
  73. @Override
  74. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  75. //查询结果操作
  76. List<T> list = new ArrayList<>();
  77. //获取查询命中的数据
  78. SearchHits hits = searchResponse.getHits();
  79. if (hits != null) {
  80. //非空
  81. for (SearchHit hit : hits) {
  82. //SearchHit转换为skuinfo
  83. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  84. list.add((T) skuInfo);
  85. }
  86. }
  87. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  88. }
  89. });
  90. //封装最终返回结果
  91. //总记录数
  92. resultMap.put("total", resultInfo.getTotalElements());
  93. //总页数
  94. resultMap.put("totalPages", resultInfo.getTotalPages());
  95. //数据集合
  96. resultMap.put("rows", resultInfo.getContent());
  97. //封装品牌的分组结果
  98. StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
  99. List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  100. resultMap.put("brandList", brandList);
  101. return resultMap;
  102. }
  103. return null;
  104. }
  105. }

请求地址 localhost:9009/search?keywords=电视&spec_尺寸=金色

4.1. 按照规格进行聚合查询

修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法 在关键字查询boolQuery构建下添加

  1. //按照规格进行聚合查询
  2. String skuSpec = "skuSpec";
  3. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
  1. //封装规格分组结果
  2. StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
  3. List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  4. resultMap.put("specList", specList);

完整代码

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.aggregations.AggregationBuilders;
  16. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.data.domain.Pageable;
  19. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  20. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  21. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  22. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  23. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  24. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  25. import org.springframework.stereotype.Service;
  26. import java.util.*;
  27. import java.util.stream.Collectors;
  28. @Service
  29. public class SearchServiceImpl implements SearchService {
  30. @Autowired
  31. private ElasticsearchTemplate elasticsearchTemplate;
  32. /**
  33. * 根据前端传过来的字段进行查询
  34. *
  35. * @param searchMap
  36. * @return
  37. */
  38. @Override
  39. public Map search(Map<String, String> searchMap) {
  40. Map<String, Object> resultMap = new HashMap<>();
  41. //构建查询
  42. if (searchMap != null) {
  43. //构建查询条件封装对象
  44. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  45. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  46. //按照关键字查询
  47. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  48. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  49. }
  50. //按照品牌进行过滤查询
  51. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  52. boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
  53. }
  54. //按照规格进行过滤查询
  55. for (String key : searchMap.keySet()) {
  56. if (key.startsWith("spec_")) {
  57. String value = searchMap.get(key).replace("%2B", "+");
  58. boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
  59. }
  60. }
  61. nativeSearchQueryBuilder.withQuery(boolQuery);
  62. //按照品牌进行分组(聚合)查询
  63. String skuBrand = "skuBrand";
  64. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
  65. //按照规格进行聚合查询
  66. String skuSpec = "skuSpec";
  67. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
  68. //开启查询
  69. /**
  70. * 第一个参数: 条件的构建对象
  71. * 第二个参数: 查询操作实体类
  72. * 第三个参数: 查询结果操作对象
  73. */
  74. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  75. new SearchResultMapper() {
  76. @Override
  77. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  78. //查询结果操作
  79. List<T> list = new ArrayList<>();
  80. //获取查询命中的数据
  81. SearchHits hits = searchResponse.getHits();
  82. if (hits != null) {
  83. //非空
  84. for (SearchHit hit : hits) {
  85. //SearchHit转换为skuinfo
  86. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  87. list.add((T) skuInfo);
  88. }
  89. }
  90. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  91. }
  92. });
  93. //封装最终返回结果
  94. //总记录数
  95. resultMap.put("total", resultInfo.getTotalElements());
  96. //总页数
  97. resultMap.put("totalPages", resultInfo.getTotalPages());
  98. //数据集合
  99. resultMap.put("rows", resultInfo.getContent());
  100. //封装品牌的分组结果
  101. StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
  102. List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  103. resultMap.put("brandList", brandList);
  104. //封装规格分组结果
  105. StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
  106. List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  107. resultMap.put("specList", specList);
  108. return resultMap;
  109. }
  110. return null;
  111. }
  112. }

请求地址 localhost:9009/search?keywords=电视

5. 价格区间查询

价格区间查询,每次需要将价格传入到后台,前端传入后台的价格大概是price=0-500或者price=500-1000依次类推,最后一个是price=3000,后台可以根据-分割,如果分割得到的结果最多有2个,第1个表示x<price,第2个表示price<=y

修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法 在关键字查询boolQuery构建下添加

  1. //按照价格进行区间过滤查询
  2. if (StringUtils.isNotEmpty(searchMap.get("price"))) {
  3. String[] prices = searchMap.get("price").split("-");
  4. if (prices.length == 2) {
  5. //是xx - xx 价格区间的条件
  6. boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
  7. }
  8. boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
  9. }

完整代码

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.aggregations.AggregationBuilders;
  16. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.data.domain.Pageable;
  19. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  20. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  21. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  22. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  23. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  24. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  25. import org.springframework.stereotype.Service;
  26. import java.util.*;
  27. import java.util.stream.Collectors;
  28. @Service
  29. public class SearchServiceImpl implements SearchService {
  30. @Autowired
  31. private ElasticsearchTemplate elasticsearchTemplate;
  32. /**
  33. * 根据前端传过来的字段进行查询
  34. *
  35. * @param searchMap
  36. * @return
  37. */
  38. @Override
  39. public Map search(Map<String, String> searchMap) {
  40. Map<String, Object> resultMap = new HashMap<>();
  41. //构建查询
  42. if (searchMap != null) {
  43. //构建查询条件封装对象
  44. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  45. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  46. //按照关键字查询
  47. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  48. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  49. }
  50. //按照品牌进行过滤查询
  51. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  52. boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
  53. }
  54. //按照规格进行过滤查询
  55. for (String key : searchMap.keySet()) {
  56. if (key.startsWith("spec_")) {
  57. String value = searchMap.get(key).replace("%2B", "+");
  58. boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
  59. }
  60. }
  61. nativeSearchQueryBuilder.withQuery(boolQuery);
  62. //按照价格进行区间过滤查询
  63. if (StringUtils.isNotEmpty(searchMap.get("price"))) {
  64. String[] prices = searchMap.get("price").split("-");
  65. if (prices.length == 2) {
  66. //是xx - xx 价格区间的条件
  67. boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
  68. }
  69. boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
  70. }
  71. //按照品牌进行分组(聚合)查询
  72. String skuBrand = "skuBrand";
  73. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
  74. //按照规格进行聚合查询
  75. String skuSpec = "skuSpec";
  76. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
  77. //开启查询
  78. /**
  79. * 第一个参数: 条件的构建对象
  80. * 第二个参数: 查询操作实体类
  81. * 第三个参数: 查询结果操作对象
  82. */
  83. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  84. new SearchResultMapper() {
  85. @Override
  86. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  87. //查询结果操作
  88. List<T> list = new ArrayList<>();
  89. //获取查询命中的数据
  90. SearchHits hits = searchResponse.getHits();
  91. if (hits != null) {
  92. //非空
  93. for (SearchHit hit : hits) {
  94. //SearchHit转换为skuinfo
  95. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  96. list.add((T) skuInfo);
  97. }
  98. }
  99. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  100. }
  101. });
  102. //封装最终返回结果
  103. //总记录数
  104. resultMap.put("total", resultInfo.getTotalElements());
  105. //总页数
  106. resultMap.put("totalPages", resultInfo.getTotalPages());
  107. //数据集合
  108. resultMap.put("rows", resultInfo.getContent());
  109. //封装品牌的分组结果
  110. StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
  111. List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  112. resultMap.put("brandList", brandList);
  113. //封装规格分组结果
  114. StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
  115. List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  116. resultMap.put("specList", specList);
  117. return resultMap;
  118. }
  119. return null;
  120. }
  121. }

请求地址 localhost:9009/search?price=0-500

localhost:9009/search?price=4000

6. 分页查询

修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法 在关键字查询boolQuery构建下添加

  1. //开启分页查询
  2. String pageNum = searchMap.get("pageNum"); //当前页
  3. String pageSize = searchMap.get("pageSize"); //每页显示多少条
  4. if (StringUtils.isEmpty(pageNum)) {
  5. pageNum = "1";
  6. }
  7. if (StringUtils.isEmpty(pageSize)) {
  8. pageSize = "30";
  9. }
  10. //设置分页
  11. nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));

封装当前页码到返回结果中

  1. //当前页
  2. resultMap.put("pageNum", pageNum);

完整代码

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.aggregations.AggregationBuilders;
  16. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.data.domain.PageRequest;
  19. import org.springframework.data.domain.Pageable;
  20. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  21. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  22. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  23. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  24. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  25. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  26. import org.springframework.stereotype.Service;
  27. import java.util.*;
  28. import java.util.stream.Collectors;
  29. @Service
  30. public class SearchServiceImpl implements SearchService {
  31. @Autowired
  32. private ElasticsearchTemplate elasticsearchTemplate;
  33. /**
  34. * 根据前端传过来的字段进行查询
  35. *
  36. * @param searchMap
  37. * @return
  38. */
  39. @Override
  40. public Map search(Map<String, String> searchMap) {
  41. Map<String, Object> resultMap = new HashMap<>();
  42. //构建查询
  43. if (searchMap != null) {
  44. //构建查询条件封装对象
  45. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  46. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  47. //按照关键字查询
  48. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  49. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  50. }
  51. //按照品牌进行过滤查询
  52. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  53. boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
  54. }
  55. //按照规格进行过滤查询
  56. for (String key : searchMap.keySet()) {
  57. if (key.startsWith("spec_")) {
  58. String value = searchMap.get(key).replace("%2B", "+");
  59. boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
  60. }
  61. }
  62. nativeSearchQueryBuilder.withQuery(boolQuery);
  63. //按照价格进行区间过滤查询
  64. if (StringUtils.isNotEmpty(searchMap.get("price"))) {
  65. String[] prices = searchMap.get("price").split("-");
  66. if (prices.length == 2) {
  67. //是xx - xx 价格区间的条件
  68. boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
  69. }
  70. boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
  71. }
  72. //按照品牌进行分组(聚合)查询
  73. String skuBrand = "skuBrand";
  74. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
  75. //按照规格进行聚合查询
  76. String skuSpec = "skuSpec";
  77. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
  78. //开启分页查询
  79. String pageNum = searchMap.get("pageNum"); //当前页
  80. String pageSize = searchMap.get("pageSize"); //每页显示多少条
  81. if (StringUtils.isEmpty(pageNum)) {
  82. pageNum = "1";
  83. }
  84. if (StringUtils.isEmpty(pageSize)) {
  85. pageSize = "30";
  86. }
  87. //设置分页
  88. nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));
  89. //开启查询
  90. /**
  91. * 第一个参数: 条件的构建对象
  92. * 第二个参数: 查询操作实体类
  93. * 第三个参数: 查询结果操作对象
  94. */
  95. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  96. new SearchResultMapper() {
  97. @Override
  98. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  99. //查询结果操作
  100. List<T> list = new ArrayList<>();
  101. //获取查询命中的数据
  102. SearchHits hits = searchResponse.getHits();
  103. if (hits != null) {
  104. //非空
  105. for (SearchHit hit : hits) {
  106. //SearchHit转换为skuinfo
  107. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  108. list.add((T) skuInfo);
  109. }
  110. }
  111. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  112. }
  113. });
  114. //封装最终返回结果
  115. //总记录数
  116. resultMap.put("total", resultInfo.getTotalElements());
  117. //总页数
  118. resultMap.put("totalPages", resultInfo.getTotalPages());
  119. //数据集合
  120. resultMap.put("rows", resultInfo.getContent());
  121. //封装品牌的分组结果
  122. StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
  123. List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  124. resultMap.put("brandList", brandList);
  125. //封装规格分组结果
  126. StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
  127. List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  128. resultMap.put("specList", specList);
  129. //当前页
  130. resultMap.put("pageNum", pageNum);
  131. return resultMap;
  132. }
  133. return null;
  134. }
  135. }

请求地址 localhost:9009/search?price=4000&pageNum=10&pageSize=20

7. 搜索排序

07. day07 ES搜索 - 图2

排序这里总共有根据价格排序、根据评价排序、根据新品排序、根据销量排序,排序要想实现非常简单,只需要告知排序的域以及排序方式即可实现。

价格排序:只需要根据价格高低排序即可,降序价格高->低,升序价格低->高

评价排序:评价分为好评、中评、差评,可以在数据库中设计3个列,用来记录好评、中评、差评的量,每次排序的时候,好评的比例来排序,当然还要有条数限制,评价条数需要超过N条。

新品排序:直接根据商品的发布时间或者更新时间排序。

销量排序:销量排序除了销售数量外,还应该要有时间段限制。

修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法 在关键字查询boolQuery构建下添加

  1. //按照相关字段进行排序查询
  2. //1.当前域 2.当前的排序操作(升序ASC,降序DESC)
  3. if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))) {
  4. if ("ASC".equals(searchMap.get("sortRule"))) {
  5. //升序操作
  6. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
  7. }else {
  8. //降序
  9. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
  10. }
  11. }

完整代码

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.aggregations.AggregationBuilders;
  16. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  17. import org.elasticsearch.search.sort.SortBuilders;
  18. import org.elasticsearch.search.sort.SortOrder;
  19. import org.springframework.beans.factory.annotation.Autowired;
  20. import org.springframework.data.domain.PageRequest;
  21. import org.springframework.data.domain.Pageable;
  22. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  23. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  24. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  25. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  26. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  27. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  28. import org.springframework.stereotype.Service;
  29. import java.util.*;
  30. import java.util.stream.Collectors;
  31. @Service
  32. public class SearchServiceImpl implements SearchService {
  33. @Autowired
  34. private ElasticsearchTemplate elasticsearchTemplate;
  35. /**
  36. * 根据前端传过来的字段进行查询
  37. *
  38. * @param searchMap
  39. * @return
  40. */
  41. @Override
  42. public Map search(Map<String, String> searchMap) {
  43. Map<String, Object> resultMap = new HashMap<>();
  44. //构建查询
  45. if (searchMap != null) {
  46. //构建查询条件封装对象
  47. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  48. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  49. //按照关键字查询
  50. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  51. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  52. }
  53. //按照品牌进行过滤查询
  54. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  55. boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
  56. }
  57. //按照规格进行过滤查询
  58. for (String key : searchMap.keySet()) {
  59. if (key.startsWith("spec_")) {
  60. String value = searchMap.get(key).replace("%2B", "+");
  61. boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
  62. }
  63. }
  64. nativeSearchQueryBuilder.withQuery(boolQuery);
  65. //按照价格进行区间过滤查询
  66. if (StringUtils.isNotEmpty(searchMap.get("price"))) {
  67. String[] prices = searchMap.get("price").split("-");
  68. if (prices.length == 2) {
  69. //是xx - xx 价格区间的条件
  70. boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
  71. }
  72. boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
  73. }
  74. //按照品牌进行分组(聚合)查询
  75. String skuBrand = "skuBrand";
  76. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
  77. //按照规格进行聚合查询
  78. String skuSpec = "skuSpec";
  79. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
  80. //开启分页查询
  81. String pageNum = searchMap.get("pageNum"); //当前页
  82. String pageSize = searchMap.get("pageSize"); //每页显示多少条
  83. if (StringUtils.isEmpty(pageNum)) {
  84. pageNum = "1";
  85. }
  86. if (StringUtils.isEmpty(pageSize)) {
  87. pageSize = "30";
  88. }
  89. //设置分页
  90. nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));
  91. //按照相关字段进行排序查询
  92. //1.当前域 2.当前的排序操作(升序ASC,降序DESC)
  93. if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))) {
  94. if ("ASC".equals(searchMap.get("sortRule"))) {
  95. //升序操作
  96. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
  97. }else {
  98. //降序
  99. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
  100. }
  101. }
  102. //开启查询
  103. /**
  104. * 第一个参数: 条件的构建对象
  105. * 第二个参数: 查询操作实体类
  106. * 第三个参数: 查询结果操作对象
  107. */
  108. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  109. new SearchResultMapper() {
  110. @Override
  111. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  112. //查询结果操作
  113. List<T> list = new ArrayList<>();
  114. //获取查询命中的数据
  115. SearchHits hits = searchResponse.getHits();
  116. if (hits != null) {
  117. //非空
  118. for (SearchHit hit : hits) {
  119. //SearchHit转换为skuinfo
  120. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  121. list.add((T) skuInfo);
  122. }
  123. }
  124. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  125. }
  126. });
  127. //封装最终返回结果
  128. //总记录数
  129. resultMap.put("total", resultInfo.getTotalElements());
  130. //总页数
  131. resultMap.put("totalPages", resultInfo.getTotalPages());
  132. //数据集合
  133. resultMap.put("rows", resultInfo.getContent());
  134. //封装品牌的分组结果
  135. StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
  136. List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  137. resultMap.put("brandList", brandList);
  138. //封装规格分组结果
  139. StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
  140. List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  141. resultMap.put("specList", specList);
  142. //当前页
  143. resultMap.put("pageNum", pageNum);
  144. return resultMap;
  145. }
  146. return null;
  147. }
  148. }

请求地址 localhost:9009/search?pageSize=1000&sortField=price&sortRule=DESC

8. 高亮显示

07. day07 ES搜索 - 图3

高亮显示是指根据商品关键字搜索商品的时候,显示的页面对关键字给定了特殊样式,让它显示更加突出,如上图商品搜索中,关键字编程了红色,其实就是给定了红色样式。

07. day07 ES搜索 - 图4

第1点,例如在百度中搜索数据的时候,会有2个地方高亮显示,分别是标题和描述,商城搜索的时候,只是商品名称高亮显示了。而高亮显示其实就是添加了样式,例如<span style="color:red;">笔记本</span>,而其中span开始标签可以称为前缀,span结束标签可以称为后缀。

第2点,高亮搜索使用ElasticsearchTemplate实现。

第3点,高亮搜索后,会搜出非高亮数据和高亮数据,高亮数据会加上第1点中的高亮样式,此时我们需要将非高亮数据换成高亮数据即可。例如非高亮:华为笔记本性能超强悍 高亮数据:华为<span style="color:red;"笔记本</span>性能超强悍,将非高亮的换成高亮的,到页面就能显示样式了。

修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法 在关键字查询boolQuery构建下添加

  1. //设置高亮域以及高亮的样式
  2. HighlightBuilder.Field field =new HighlightBuilder.Field("name")//高亮域
  3. //高亮前缀
  4. .preTags("<span style='color:red'>")
  5. //高亮后缀
  6. .postTags("</span>");
  7. nativeSearchQueryBuilder.withHighlightFields(field);

在SearchHit转换为skuinfo后 降skuinfo中的name属性 替换为添加高亮样式后的name

  1. Map<String, HighlightField> highlightFields = hit.getHighlightFields(); //获取所有高亮域
  2. if (highlightFields !=null && highlightFields.size() > 0){
  3. //替换数据
  4. skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
  5. }

完整代码

  1. package com.changgou.search.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.changgou.search.pojo.SkuInfo;
  4. import com.changgou.search.service.SearchService;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.elasticsearch.action.get.GetResponse;
  7. import org.elasticsearch.action.get.MultiGetResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.Operator;
  11. import org.elasticsearch.index.query.QueryBuilder;
  12. import org.elasticsearch.index.query.QueryBuilders;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.aggregations.AggregationBuilders;
  16. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  17. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  18. import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
  19. import org.elasticsearch.search.sort.SortBuilders;
  20. import org.elasticsearch.search.sort.SortOrder;
  21. import org.springframework.beans.factory.annotation.Autowired;
  22. import org.springframework.data.domain.PageRequest;
  23. import org.springframework.data.domain.Pageable;
  24. import org.springframework.data.elasticsearch.core.AbstractResultMapper;
  25. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  26. import org.springframework.data.elasticsearch.core.SearchResultMapper;
  27. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  28. import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
  29. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  30. import org.springframework.stereotype.Service;
  31. import java.util.*;
  32. import java.util.stream.Collectors;
  33. @Service
  34. public class SearchServiceImpl implements SearchService {
  35. @Autowired
  36. private ElasticsearchTemplate elasticsearchTemplate;
  37. /**
  38. * 根据前端传过来的字段进行查询
  39. *
  40. * @param searchMap
  41. * @return
  42. */
  43. @Override
  44. public Map search(Map<String, String> searchMap) {
  45. Map<String, Object> resultMap = new HashMap<>();
  46. //构建查询
  47. if (searchMap != null) {
  48. //构建查询条件封装对象
  49. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  50. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  51. //按照关键字查询
  52. if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
  53. boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
  54. }
  55. //按照品牌进行过滤查询
  56. if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
  57. boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
  58. }
  59. //按照规格进行过滤查询
  60. for (String key : searchMap.keySet()) {
  61. if (key.startsWith("spec_")) {
  62. String value = searchMap.get(key).replace("%2B", "+");
  63. boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
  64. }
  65. }
  66. nativeSearchQueryBuilder.withQuery(boolQuery);
  67. //按照价格进行区间过滤查询
  68. if (StringUtils.isNotEmpty(searchMap.get("price"))) {
  69. String[] prices = searchMap.get("price").split("-");
  70. if (prices.length == 2) {
  71. //是xx - xx 价格区间的条件
  72. boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
  73. }
  74. boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
  75. }
  76. //按照品牌进行分组(聚合)查询
  77. String skuBrand = "skuBrand";
  78. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
  79. //按照规格进行聚合查询
  80. String skuSpec = "skuSpec";
  81. nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
  82. //开启分页查询
  83. String pageNum = searchMap.get("pageNum"); //当前页
  84. String pageSize = searchMap.get("pageSize"); //每页显示多少条
  85. if (StringUtils.isEmpty(pageNum)) {
  86. pageNum = "1";
  87. }
  88. if (StringUtils.isEmpty(pageSize)) {
  89. pageSize = "30";
  90. }
  91. //设置分页
  92. nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));
  93. //按照相关字段进行排序查询
  94. //1.当前域 2.当前的排序操作(升序ASC,降序DESC)
  95. if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))) {
  96. if ("ASC".equals(searchMap.get("sortRule"))) {
  97. //升序操作
  98. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
  99. }else {
  100. //降序
  101. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
  102. }
  103. }
  104. //设置高亮域以及高亮的样式
  105. HighlightBuilder.Field field =new HighlightBuilder.Field("name")//高亮域
  106. //高亮前缀
  107. .preTags("<span style='color:red'>")
  108. //高亮后缀
  109. .postTags("</span>");
  110. nativeSearchQueryBuilder.withHighlightFields(field);
  111. //开启查询
  112. /**
  113. * 第一个参数: 条件的构建对象
  114. * 第二个参数: 查询操作实体类
  115. * 第三个参数: 查询结果操作对象
  116. */
  117. AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
  118. new SearchResultMapper() {
  119. @Override
  120. public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
  121. //查询结果操作
  122. List<T> list = new ArrayList<>();
  123. //获取查询命中的数据
  124. SearchHits hits = searchResponse.getHits();
  125. if (hits != null) {
  126. //非空
  127. for (SearchHit hit : hits) {
  128. //SearchHit转换为skuinfo
  129. SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
  130. Map<String, HighlightField> highlightFields = hit.getHighlightFields(); //获取所有高亮域
  131. if (highlightFields !=null && highlightFields.size() > 0){
  132. //替换数据
  133. skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
  134. }
  135. list.add((T) skuInfo);
  136. }
  137. }
  138. return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
  139. }
  140. });
  141. //封装最终返回结果
  142. //总记录数
  143. resultMap.put("total", resultInfo.getTotalElements());
  144. //总页数
  145. resultMap.put("totalPages", resultInfo.getTotalPages());
  146. //数据集合
  147. resultMap.put("rows", resultInfo.getContent());
  148. //封装品牌的分组结果
  149. StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
  150. List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  151. resultMap.put("brandList", brandList);
  152. //封装规格分组结果
  153. StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
  154. List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
  155. resultMap.put("specList", specList);
  156. //当前页
  157. resultMap.put("pageNum", pageNum);
  158. return resultMap;
  159. }
  160. return null;
  161. }
  162. }

请求地址 localhost:9009/search?keywords=电脑 查看关键字是否被添加高亮样式