当前位置:   article > 正文

11. ElasticSearch实战3——检索服务-SearchRequest构建-检索_es searchrequest

es searchrequest

1. 检索

根据DSL语句构建检索条件

1.1 DSL语句

  1. GET gulimall_product/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "skuTitle": "华为"
  9. }
  10. }
  11. ],
  12. "filter": [
  13. {
  14. "term": {
  15. "catalogId": "1111133"
  16. }
  17. },
  18. {
  19. "terms": {
  20. "brandId": [
  21. "3",
  22. "5"
  23. ]
  24. }
  25. },
  26. {
  27. "term": {
  28. "hasStock": true
  29. }
  30. },
  31. {
  32. "nested": {
  33. "path": "attrs",
  34. "query": {
  35. "bool": {
  36. "must": [
  37. {
  38. "term": {
  39. "attrs.attrId": {
  40. "value": "15"
  41. }
  42. }
  43. },
  44. {
  45. "terms": {
  46. "attrs.attrValue": [
  47. "aaa",
  48. "白色",
  49. "OCE-AN10"
  50. ]
  51. }
  52. }
  53. ]
  54. }
  55. }
  56. }
  57. }
  58. ]
  59. }
  60. },
  61. "sort": [
  62. {
  63. "skuPrice": {
  64. "order": "desc"
  65. }
  66. }
  67. ],
  68. "aggs": {
  69. "brand_agg": {
  70. "terms": {
  71. "field": "brandId",
  72. "size": 10
  73. },
  74. "aggs": {
  75. "brand_name_agg": {
  76. "terms": {
  77. "field": "brandName",
  78. "size": 10
  79. }
  80. },
  81. "brand_img_agg":{
  82. "terms": {
  83. "field": "brandImg",
  84. "size": 10
  85. }
  86. }
  87. }
  88. },
  89. "catalog_agg":{
  90. "terms": {
  91. "field": "catalogId",
  92. "size": 10
  93. },
  94. "aggs": {
  95. "catalog_name_agg": {
  96. "terms": {
  97. "field": "catalogName",
  98. "size": 10
  99. }
  100. }
  101. }
  102. },
  103. "attr_agg":{
  104. "nested": {
  105. "path": "attrs"
  106. },
  107. "aggs": {
  108. "attr_id": {
  109. "terms": {
  110. "field": "attrs.attrId",
  111. "size": 10
  112. },
  113. "aggs": {
  114. "attr_name_id": {
  115. "terms": {
  116. "field": "attrs.attrName",
  117. "size": 10
  118. }
  119. }
  120. }
  121. }
  122. }
  123. }
  124. },
  125. "from": 0,
  126. "size": 2,
  127. "highlight": {
  128. "fields": {"skuTitle": {}},
  129. "pre_tags": "<b style='color:red'>",
  130. "post_tags": "</b>"
  131. }
  132. }

1.2 java代码

在java中使用es检索,比较重要的一个对象是SearchSourceBuilder

1.2.1 不带聚合条件

  1. import com.bjc.gulimall.search.config.GulimallElasticSearchConfig;
  2. import com.bjc.gulimall.search.constant.EsConstant;
  3. import com.bjc.gulimall.search.service.MallSearchService;
  4. import com.bjc.gulimall.search.vo.SearchParam;
  5. import com.bjc.gulimall.search.vo.SearchResult;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.apache.commons.lang3.StringUtils;
  8. import org.apache.lucene.search.join.ScoreMode;
  9. import org.elasticsearch.action.search.SearchRequest;
  10. import org.elasticsearch.action.search.SearchResponse;
  11. import org.elasticsearch.client.RestHighLevelClient;
  12. import org.elasticsearch.index.query.*;
  13. import org.elasticsearch.search.builder.SearchSourceBuilder;
  14. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  15. import org.elasticsearch.search.sort.SortOrder;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.stereotype.Service;
  18. import org.springframework.util.CollectionUtils;
  19. @Service
  20. @Slf4j
  21. public class MallSearchServiceImpl implements MallSearchService {
  22. @Autowired
  23. private RestHighLevelClient client;
  24. /*
  25. * 根据检索参数返回检索结果
  26. * SearchResult 包含页面需要的所有信息
  27. * */
  28. @Override
  29. public SearchResult search(SearchParam searchParam) {
  30. SearchResult result = null;
  31. /*
  32. * 1. 动态构建出查询需要的DSL语句
  33. * */
  34. // 1.1 创建检索请求对象
  35. // SearchRequest searchRequest = new SearchRequest();
  36. SearchRequest searchRequest = buildSearchRequest(searchParam);
  37. try {
  38. /* 2. 执行检索请求 */
  39. SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
  40. /* 3. 分析响应数据,封装成指定的数据格式 */
  41. result = buildSearchResult(response);
  42. } catch (Exception e) {
  43. log.error("动态构建出查询需要的DSL语句出错,原因:",e);
  44. }
  45. return null;
  46. }
  47. /* 根据响应构建返回结果对象 */
  48. private SearchResult buildSearchResult(SearchResponse response) {
  49. return null;
  50. }
  51. /* 准备检索请求
  52. * 关键字模糊匹配、过滤(按照属性、分类、品牌、价格区间),排序,分页,高亮,聚合分析
  53. * */
  54. private SearchRequest buildSearchRequest(SearchParam searchParam) {
  55. // 用于构建DSL语句
  56. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  57. /*
  58. * 1. 查询部分DSL构建
  59. * */
  60. // 1.1 构建queryBuilder对象。复杂的query是通过bool组合检索的,因此需要构建BoolQueryBuilder对象
  61. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  62. // 1.2 构建全文检索条件must,关键字模糊匹配. 如果页面有关键字搜索,才进行全文模糊检索
  63. if(StringUtils.isNotEmpty(searchParam.getKeyword())){
  64. // 将按照skuTitle全文检索的条件封装到boolQuery中
  65. boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
  66. }
  67. // 1.3 构建过滤filter条件
  68. // 1.3.1 按照三级分类id过滤
  69. if(null != searchParam.getCatalog3Id()){
  70. // 三级分类id是精确匹配,用term
  71. boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
  72. }
  73. // 1.3.2 按照品牌ID过滤(支持多选)
  74. if(!CollectionUtils.isEmpty(searchParam.getBrandId())){
  75. boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
  76. }
  77. // 1.3.3 按照所有指定的属性进行查询
  78. /*
  79. * {
  80. "nested": {
  81. "path": "attrs",
  82. "query": {
  83. "bool": {
  84. "must": [
  85. {
  86. "term": {
  87. "attrs.attrId": {
  88. "value": "15"
  89. }
  90. }
  91. },
  92. {
  93. "terms": {
  94. "attrs.attrValue": [
  95. "aaa",
  96. "白色",
  97. "OCE-AN10"
  98. ]
  99. }
  100. }
  101. ]
  102. }
  103. }
  104. }
  105. }
  106. * */
  107. if(!CollectionUtils.isEmpty(searchParam.getAttrs())){
  108. searchParam.getAttrs().forEach(attrStr -> {
  109. BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
  110. // attrs=1_5寸:8寸&attrs=2_8G:16G
  111. String[] s = attrStr.split("_");
  112. // 检索的属性ID
  113. String attrId = s[0];
  114. // 检索的属性ID对应的属性值的数组
  115. String[] attrValues = s[1].split(":");
  116. nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
  117. nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
  118. // 每一个属性都需要生成一个嵌入式查询
  119. // ScoreMode.None 表示不参与评分
  120. NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
  121. boolQuery.filter(nestedQueryBuilder);
  122. });
  123. }
  124. // 1.3.4 按照库存是否有进行查询
  125. boolQuery.filter(QueryBuilders.termQuery("hasStock",searchParam.getHasStock() == 1));
  126. // 1.3.5 按照价格区间进行查询
  127. if(StringUtils.isNotEmpty(searchParam.getSkuPrice())){
  128. // 1_500/_500/500_
  129. /*
  130. * "range": {
  131. "skuPrice": {
  132. "gte": 0.0,
  133. "lte": 20000.0
  134. }
  135. }
  136. * */
  137. int g = 0;
  138. int l = 0;
  139. // 1)构建rangeQueryBuilder对象
  140. RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
  141. // 2)解析查询参数
  142. String[] s = searchParam.getSkuPrice().split("_");
  143. if(s.length == 2){
  144. rangeQueryBuilder.gte(s[0]).lte(s[1]);
  145. } else if(s.length == 1){ // 否则就是单值
  146. if(searchParam.getSkuPrice().startsWith("_")){
  147. rangeQueryBuilder.lte(s[0]);
  148. } else {
  149. rangeQueryBuilder.gte(s[0]);
  150. }
  151. }
  152. boolQuery.filter(rangeQueryBuilder);
  153. }
  154. // 将所有的查询条件封装到sourceBuilder
  155. sourceBuilder.query(boolQuery);
  156. /*
  157. * 2. 排序、分页、高亮
  158. * */
  159. // 2.1 排序
  160. if(StringUtils.isNotEmpty(searchParam.getSort())){
  161. String sort = searchParam.getSort(); // &sort = hotScore_asc/desc
  162. String[] s = sort.split("_");
  163. // SortOrder sortOrder = s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
  164. sourceBuilder.sort(s[0], SortOrder.fromString(s[1]));
  165. }
  166. // 2.2 分页 from = (当前页-1) * 每页显示条数
  167. sourceBuilder.from((searchParam.getPageNum()-1) * EsConstant.PRODUCT_PAGESIZE) // 第几页
  168. .size(EsConstant.PRODUCT_PAGESIZE); // 每页显示记录数
  169. // 2.3 高亮 只有关键字查询才高亮
  170. if(StringUtils.isNotEmpty(searchParam.getKeyword())){
  171. HighlightBuilder highlight = new HighlightBuilder();
  172. // 指定需要高亮的字段是哪个
  173. highlight.field("skuTitle");
  174. // 指定高亮前置标签
  175. highlight.preTags("<b style='color:red'>");
  176. // 指定后缀标签
  177. highlight.postTags("</b>");
  178. sourceBuilder.highlighter(highlight);
  179. }
  180. /*
  181. * 3. 聚合分析
  182. * */
  183. System.out.println("构建的DSL:" + sourceBuilder.toString());
  184. SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
  185. return searchRequest;
  186. }
  187. }

测试:

1)带keyword

控制台结果:

将该结果复制到Kibana上检索,如图:

2)添加属性和分类查询条件

1.2.2 聚合分析的构建

  1. /*
  2. * 3. 聚合分析
  3. * */
  4. // 3.1 品牌聚合
  5. AggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg") // 参数为聚合的名称
  6. .field("brandId") // 要聚合的字段
  7. .size(50); // 查询并显示多少条记录
  8. // 3.1.1 子聚合
  9. brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg")
  10. .field("brandName").size(1));
  11. // 3.1.2 子聚合
  12. brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg")
  13. .field("brandImg").size(1));
  14. // 3.1.3 添加聚合条件到sourceBuilder
  15. sourceBuilder.aggregation(brand_agg);
  16. // 3.2 分类聚合
  17. AggregationBuilder catalog_agg = AggregationBuilders.terms("catalogAgg").field("catalogId").size(50);
  18. catalog_agg.subAggregation(AggregationBuilders.terms("catalogNameAgg").field("catalogName").size(1));
  19. sourceBuilder.aggregation(catalog_agg);
  20. // 3.3 属性聚合(嵌入式聚合)
  21. AggregationBuilder attrAgg = AggregationBuilders.nested("attrAgg", "attrs");
  22. // 3.3.1 聚合出当前所有的AttrId
  23. AggregationBuilder attrIdAggregation = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(100);
  24. // 3.3.2 聚合分析出当前attrId对应的名称
  25. attrIdAggregation.subAggregation(AggregationBuilders.terms("attrNameIdAgg").field("attrs.attrName").size(1));
  26. // 3.3.3 聚合分析出当前attrId对应的所有可能的属性值attrValue
  27. attrIdAggregation.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(50));
  28. // 3.3.4 将attrId子聚合添加到嵌入式聚合attrAgg中
  29. attrAgg.subAggregation(attrIdAggregation);
  30. // 3.3.4 将嵌入式聚合attrAgg聚合条件添加到sourceBuilder中
  31. sourceBuilder.aggregation(attrAgg);

2. 结果封装

  1. /* 根据响应构建返回结果对象 */
  2. private SearchResult buildSearchResult(SearchResponse response,SearchParam searchParam) {
  3. SearchResult result = new SearchResult();
  4. // 获取命中结果
  5. SearchHits hits = response.getHits();
  6. // 从名字结果中获取命中记录
  7. SearchHit[] resultHits = hits.getHits();
  8. // 1. 封装所有查询到的商品
  9. List<SkuEsModel> esModels = new ArrayList<>();
  10. if(ArrayUtils.isNotEmpty(resultHits)){
  11. for(SearchHit hit : resultHits){
  12. String sourceAsString = hit.getSourceAsString();
  13. SkuEsModel esModel = JSONObject.parseObject(sourceAsString, SkuEsModel.class);
  14. // 设置高亮
  15. Map<String, HighlightField> highlightFields = hit.getHighlightFields();
  16. if(!CollectionUtils.isEmpty(highlightFields)){
  17. HighlightField skuTitle = highlightFields.get("skuTitle");
  18. String title = skuTitle.getFragments()[0].string();
  19. esModel.setSkuTitle(title);
  20. }
  21. esModels.add(esModel);
  22. }
  23. }
  24. result.setProducts(esModels);
  25. // 获取所有的聚合信息
  26. Aggregations aggregations = response.getAggregations();
  27. // 2. 封装当前所有商品涉及到的所有属性信息
  28. List<SearchResult.AttrVo> attrs = new ArrayList<>();
  29. // 获取嵌套聚合
  30. ParsedNested parsedNested = aggregations.get("attrAgg");
  31. // 获取属性id聚合
  32. ParsedLongTerms attrIdAgg = parsedNested.getAggregations().get("attrIdAgg");
  33. // 遍历属性id聚合,得到每一个属性下的属性值
  34. attrIdAgg.getBuckets().forEach(item -> {
  35. SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
  36. // 获取属性id
  37. String key = item.getKeyAsString();
  38. attrVo.setAttrId(Long.parseLong(key));
  39. // 获取属性名称
  40. ParsedStringTerms attrNameTerms = item.getAggregations().get("attrNameIdAgg");
  41. String name = attrNameTerms.getBuckets().get(0).getKeyAsString();
  42. attrVo.setAttrName(name);
  43. // 获取属性值
  44. ParsedStringTerms attrValueTerms = item.getAggregations().get("attrValueAgg");
  45. List<String> attrValues = attrValueTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
  46. attrVo.setAttrValue(attrValues);
  47. // 将每一个属性对象添加到属性集合
  48. attrs.add(attrVo);
  49. });
  50. result.setAttrs(attrs);
  51. // 3. 封装当前商品所涉及到的品牌信息
  52. List<SearchResult.BrandVo> brandVos = new ArrayList<>();
  53. ParsedLongTerms brandAgg = aggregations.get("brand_agg");
  54. brandAgg.getBuckets().forEach(bucket -> {
  55. SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
  56. // 获取品牌id
  57. String key = bucket.getKeyAsString();
  58. brandVo.setBrandId(Long.parseLong(key));
  59. // 获取品牌名称
  60. ParsedStringTerms stringTerms = bucket.getAggregations().get("brand_name_agg");
  61. String name = stringTerms.getBuckets().get(0).getKeyAsString();
  62. brandVo.setBrandName(name);
  63. // 获取品牌图片
  64. ParsedStringTerms imgTerms = bucket.getAggregations().get("brand_img_agg");
  65. String img = imgTerms.getBuckets().get(0).getKeyAsString();
  66. brandVo.setBrandImg(img);
  67. brandVos.add(brandVo);
  68. });
  69. result.setBrandVos(brandVos);
  70. // 4. 封装当前商品所属的分类
  71. List<SearchResult.CatalogVo> catalogs = new ArrayList<>();
  72. // 获取分类聚合信息(因为分类id是long类型的,所以用Aggregations接口的实现类ParsedLongTerms)
  73. ParsedLongTerms catalogAgg = aggregations.get("catalogAgg");
  74. List<? extends Terms.Bucket> buckets = catalogAgg.getBuckets();
  75. buckets.forEach(bucket -> {
  76. SearchResult.CatalogVo cVo = new SearchResult.CatalogVo();
  77. // bucket的key就是分类的id
  78. String key = bucket.getKeyAsString();
  79. cVo.setCatalogId(Long.parseLong(key));
  80. // 获取分类名称 分类的名称是分类id的子聚合
  81. ParsedStringTerms nameAgg = bucket.getAggregations().get("catalogNameAgg");
  82. String name = nameAgg.getBuckets().get(0).getKeyAsString();
  83. cVo.setCatalogName(name);
  84. catalogs.add(cVo);
  85. });
  86. result.setCatalogs(catalogs);
  87. // 5. 设置分页信息
  88. long total = hits.getTotalHits().value;
  89. // 5.1 页码
  90. result.setPageNum(searchParam.getPageNum());
  91. // 5.2 总记录数
  92. result.setTotal(total);
  93. // 5.3 总页码
  94. int pages = (int)(total/EsConstant.PRODUCT_PAGESIZE + (total%EsConstant.PRODUCT_PAGESIZE > 0 ? 1 : 0));
  95. result.setTotalPages(pages);
  96. return result;
  97. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/842374
推荐阅读
相关标签
  

闽ICP备14008679号