检索服务

封装查询条件

注意请求标识

  1. /**
  2. * 封装页面所有可能传递过来的查询条件
  3. * 三种点击搜索的方式
  4. * 1、点击搜索:keyword 【skuTitle】
  5. * 2、点击分类:传catalog3Id【catalogld】
  6. * 3、选择筛选条件
  7. * 1、全文检索: keyword【skuTitle】
  8. * 2、排序: saleCount_asc【销量】、hotScore_asc【综合排序:热度评分】、skuPrice_asc【价格】
  9. * 3、过滤: hasStock、skuPrice区间、brandld、catalog3ld、attrs
  10. * 4、聚合: attrs
  11. * attrs=2_5寸 传参格式,所以直接for循环split("_")就可以得到attrId与attrValue
  12. * attrs=1_白色:蓝色 然后值split(":")得到各项值attrValue
  13. */
  14. @Data
  15. public class SearchParam {
  16. private String keyword;// 页面传递过来的全文匹配关键字
  17. private Long catalog3Id;// 三级分类的id
  18. /**
  19. * 排序:sort=saleCount_asc sort=hotScore_asc sort=skuPrice_asc
  20. */
  21. private String sort;
  22. /**
  23. * 过滤条件:
  24. * hasStock=0/1【有货】
  25. * skuPrice=0_500/500_/_500【价格区间】
  26. * brandld=1
  27. * attrs=1_白色:蓝色&attrs=2_2寸:5寸【属性可多选,值也可多选】
  28. */
  29. private Integer hasStock = 1;// 是否只显示有货,默认显示有货,null == 1会NullPoint异常 0/1
  30. private String skuPrice;// 按照价格区间查询
  31. private List<Long> brandId;// 品牌id,可多选
  32. private List<String> attrs;// 三级分类id+属性值
  33. private Integer pageNum = 1;// 页码
  34. private String _queryString;// 原生的所有查询条件(来自url的请求参数),用于构建面包屑
  35. }

返回结果类

显示结果VO类

检索服务 - 图1

包括下面商品信息

面包屑导航

检索服务 - 图2

  1. @Data
  2. public class SearchResult {
  3. private List<SkuEsModel> products;// es检索到的所有商品信息
  4. /**
  5. * 分页信息
  6. */
  7. private Integer pageNum;// 当前页码
  8. private Long total;// 总记录数
  9. private Integer totalPages;// 总页码
  10. private List<Integer> pageNavs;// 导航页码[1、2、3、4、5]
  11. private List<BrandVo> brands;// 当前查询到的结果所有涉及到的品牌
  12. private List<CatalogVo> catalogs;// 当前查询到的结果所有涉及到的分类
  13. /**
  14. * attrs=1_anzhuo&attrs=5_其他:1080P
  15. */
  16. private List<AttrVo> attrs = new ArrayList<>();// 当前查询到的结果所有涉及到的属性【符合检索条件的,可检索的属性】
  17. // ============================以上是要返回的数据====================================
  18. // 面包屑导航数据
  19. private List<NavVo> navs = new ArrayList<>();
  20. // 封装筛选条件中的属性id集合【用于面包屑,选择属性后出现在面包屑中,下面的属性栏则隐藏】
  21. // 该字段是提供前端用的
  22. private List<Long> attrIds = new ArrayList<>();
  23. /**
  24. * 面包屑导航VO
  25. */
  26. @Data
  27. public static class NavVo {
  28. private String navName;// 属性名
  29. private String navValue;// 属性值
  30. private String link;// 回退地址(删除该面包屑筛选条件回退地址)
  31. }
  32. @Data
  33. public static class BrandVo {
  34. private Long brandId;//
  35. private String brandName;//
  36. private String brandImg;//
  37. }
  38. @Data
  39. public static class CatalogVo {
  40. private Long catalogId;// 显示分类id
  41. private String catalogName;// 显示分类name
  42. }
  43. @Data
  44. public static class AttrVo {
  45. private Long attrId;// 允许检索的 属性Id
  46. private String attrName;// 允许检索的 属性名
  47. private List<String> attrValue;// 属性值【多个】
  48. }
  49. }

ES查询的实例语句

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

请求封装接口

  1. @Override
  2. public SearchResult search(SearchParam searchParam) {
  3. // 动态构建查询需要的DSL语句
  4. SearchResult searchResult = null;
  5. // 准备检索请求
  6. SearchRequest searchRequest = buildSearchRequest(searchParam);
  7. // 执行检索请求
  8. try {
  9. SearchResponse response = restHighLevelClient.search(searchRequest, GulimallSearchConfig.COMMON_OPTIONS);
  10. // 封装返回结果
  11. searchResult = buildSearchResponse(response, searchParam);
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. }
  15. return searchResult;
  16. }
  17. /**
  18. * 准备检索请求(参照dsl.json文件)
  19. *
  20. * @param searchParam
  21. * @return
  22. */
  23. private SearchRequest buildSearchRequest(SearchParam searchParam) {
  24. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // 构建DSL语句
  25. // 构建 bool-query,根据商品的标题进行检索
  26. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  27. if (!StringUtils.isEmpty(searchParam.getKeyword())) {
  28. boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
  29. }
  30. // 构建 bool-filter-term,根据三级分类进行检索
  31. if (!StringUtils.isEmpty(searchParam.getCatalog3Id())) {
  32. boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
  33. }
  34. // 构建 bool-filter-term,根据品牌id进行检索
  35. if (!StringUtils.isEmpty(searchParam.getBrandId())) {
  36. boolQueryBuilder.filter(QueryBuilders.termQuery("brandId", searchParam.getBrandId()));
  37. }
  38. // bool-filter,根据属性值进行检索
  39. if (!StringUtils.isEmpty(searchParam.getAttrs())) {
  40. for (String attr : searchParam.getAttrs()) {
  41. BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
  42. // attr格式 attrs=1_白色:蓝色
  43. String[] attrs = attr.split("_");
  44. String attrId = attrs[0];
  45. String[] attrValues = attrs[1].split(":");// 再将多个属性值分开
  46. // TODO 此时如果属性只有一个,那么在ES中会封装为[高通],此时会查不到数据
  47. nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
  48. if (attrValues.length == 1) {
  49. nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", attrValues[0]));
  50. } else {
  51. // 多个就放入数组
  52. nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", attrValues));
  53. }
  54. // 创建nested查询(注意每一个属性值,都需要一个nested进行查询)
  55. NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None); // 最后参数表示不参与评分
  56. boolQueryBuilder.filter(nestedQuery);
  57. }
  58. }
  59. // 构建 bool-filter-term,根据是否有库存进行检索
  60. boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
  61. // bool-filter,根据价格区间进行查询
  62. // skuPrice=0_500/500_/_500【价格区间】
  63. if (!StringUtils.isEmpty(searchParam.getSkuPrice())) {
  64. RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
  65. String skuPrice = searchParam.getSkuPrice();
  66. String[] s = skuPrice.split("_");
  67. if (s.length == 2) { // 指定了两边区间 0_500
  68. rangeQuery.gte(s[0]).lte(s[1]);
  69. } else { // 指定了一边区间 500_/_500
  70. if (skuPrice.endsWith("_")) {
  71. rangeQuery.gte(s[0]);
  72. }
  73. if (skuPrice.startsWith("_")) {
  74. rangeQuery.lte(s[0]);
  75. }
  76. }
  77. boolQueryBuilder.filter(rangeQuery);
  78. }
  79. // 把以前所有的条件封装起来
  80. searchSourceBuilder.query(boolQueryBuilder);
  81. // 排序:sort=saleCount_asc
  82. if (!StringUtils.isEmpty(searchParam.getSort())) {
  83. String[] s = searchParam.getSort().split("_");
  84. SortOrder sortOrder = s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
  85. searchSourceBuilder.sort(s[0], sortOrder);
  86. }
  87. // 分页
  88. // "from": 0,
  89. // "size": 2,
  90. searchSourceBuilder.from((searchParam.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
  91. searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
  92. // 高亮
  93. // "highlight": {
  94. // "fields": {
  95. // "skuTitle": {}
  96. // },
  97. // "pre_tags": "<b style='color:red'>",
  98. // "post_tags": "</b>"
  99. // }
  100. if (!StringUtils.isEmpty(searchParam.getKeyword())) {
  101. HighlightBuilder highlightBuilder = new HighlightBuilder();
  102. highlightBuilder.field("skuTitle");
  103. highlightBuilder.preTags("<b style='color:red'>");
  104. highlightBuilder.postTags("</b>");
  105. searchSourceBuilder.highlighter(highlightBuilder);
  106. }
  107. // 聚合分析
  108. // 品牌聚合
  109. TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg").field("brandId").size(50);
  110. brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
  111. brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
  112. searchSourceBuilder.aggregation(brand_agg);
  113. // 分类聚合
  114. TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
  115. catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
  116. searchSourceBuilder.aggregation(catalog_agg);
  117. // 属性聚合(nested属性)
  118. NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
  119. TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
  120. attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
  121. attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
  122. attr_agg.subAggregation(attr_id_agg);
  123. searchSourceBuilder.aggregation(attr_agg);
  124. System.out.println(searchSourceBuilder.toString());
  125. SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);
  126. return searchRequest;
  127. }

ES返回JSON数据

  1. {
  2. "took" : 14,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 4,
  13. "relation" : "eq"
  14. },
  15. "max_score" : null,
  16. "hits" : [
  17. {
  18. "_index" : "gulimall_product",
  19. "_type" : "_doc",
  20. "_id" : "51",
  21. "_score" : null,
  22. "_source" : {
  23. "attrs" : [
  24. {
  25. "attrId" : 15,
  26. "attrName" : "CPU品牌",
  27. "attrValue" : "高通(Qualcomm)"
  28. },
  29. {
  30. "attrId" : 16,
  31. "attrName" : "CPU型号",
  32. "attrValue" : "鸿蒙"
  33. }
  34. ],
  35. "brandId" : 9,
  36. "brandImg" : "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-18/de2426bd-a689-41d0-865a-d45d1afa7cde_huawei.png",
  37. "brandName" : "华为",
  38. "catalogId" : 225,
  39. "catalogName" : "手机",
  40. "hasStock" : true,
  41. "hotScore" : 0,
  42. "saleCount" : 0,
  43. "skuId" : 51,
  44. "skuImg" : "https://project-guli-education.oss-cn-chengdu.aliyuncs.com/2022-05-03/842c391d-6a37-441e-8aa4-ce5041b23ecb_23d9fbb256ea5d4a.jpg",
  45. "skuPrice" : 6099.0,
  46. "skuTitle" : "华为mate40pro 5G手机 夏日胡杨 8+128G全网通(4G版) 黑色 8+256GB 4G",
  47. "spuId" : 29
  48. },
  49. "highlight" : {
  50. "skuTitle" : [
  51. "<b style='color:red'>华为</b>mate40pro 5G手机 夏日胡杨 8+128G全网通(4G版) 黑色 8+256GB 4G"
  52. ]
  53. },
  54. "sort" : [
  55. 0
  56. ]
  57. },
  58. {
  59. "_index" : "gulimall_product",
  60. "_type" : "_doc",
  61. "_id" : "52",
  62. "_score" : null,
  63. "_source" : {
  64. "attrs" : [
  65. {
  66. "attrId" : 15,
  67. "attrName" : "CPU品牌",
  68. "attrValue" : "高通(Qualcomm)"
  69. },
  70. {
  71. "attrId" : 16,
  72. "attrName" : "CPU型号",
  73. "attrValue" : "鸿蒙"
  74. }
  75. ],
  76. "brandId" : 9,
  77. "brandImg" : "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-18/de2426bd-a689-41d0-865a-d45d1afa7cde_huawei.png",
  78. "brandName" : "华为",
  79. "catalogId" : 225,
  80. "catalogName" : "手机",
  81. "hasStock" : true,
  82. "hotScore" : 0,
  83. "saleCount" : 0,
  84. "skuId" : 52,
  85. "skuImg" : "https://project-guli-education.oss-cn-chengdu.aliyuncs.com/2022-05-03/842c391d-6a37-441e-8aa4-ce5041b23ecb_23d9fbb256ea5d4a.jpg",
  86. "skuPrice" : 6099.0,
  87. "skuTitle" : "华为mate40pro 5G手机 夏日胡杨 8+128G全网通(4G版) 黑色 8+256GB 5G",
  88. "spuId" : 29
  89. },
  90. "highlight" : {
  91. "skuTitle" : [
  92. "<b style='color:red'>华为</b>mate40pro 5G手机 夏日胡杨 8+128G全网通(4G版) 黑色 8+256GB 5G"
  93. ]
  94. },
  95. "sort" : [
  96. 0
  97. ]
  98. }
  99. ]
  100. },
  101. "aggregations" : {
  102. "catalog_agg" : {
  103. "doc_count_error_upper_bound" : 0,
  104. "sum_other_doc_count" : 0,
  105. "buckets" : [
  106. {
  107. "key" : 225,
  108. "doc_count" : 4,
  109. "catalog_name_agg" : {
  110. "doc_count_error_upper_bound" : 0,
  111. "sum_other_doc_count" : 0,
  112. "buckets" : [
  113. {
  114. "key" : "手机",
  115. "doc_count" : 4
  116. }
  117. ]
  118. }
  119. }
  120. ]
  121. },
  122. "attr_agg" : {
  123. "doc_count" : 8,
  124. "attr_id_agg" : {
  125. "doc_count_error_upper_bound" : 0,
  126. "sum_other_doc_count" : 0,
  127. "buckets" : [
  128. {
  129. "key" : 15,
  130. "doc_count" : 4,
  131. "attr_name_agg" : {
  132. "doc_count_error_upper_bound" : 0,
  133. "sum_other_doc_count" : 0,
  134. "buckets" : [
  135. {
  136. "key" : "CPU品牌",
  137. "doc_count" : 4
  138. }
  139. ]
  140. },
  141. "attr_value_agg" : {
  142. "doc_count_error_upper_bound" : 0,
  143. "sum_other_doc_count" : 0,
  144. "buckets" : [
  145. {
  146. "key" : "高通(Qualcomm)",
  147. "doc_count" : 4
  148. }
  149. ]
  150. }
  151. },
  152. {
  153. "key" : 16,
  154. "doc_count" : 4,
  155. "attr_name_agg" : {
  156. "doc_count_error_upper_bound" : 0,
  157. "sum_other_doc_count" : 0,
  158. "buckets" : [
  159. {
  160. "key" : "CPU型号",
  161. "doc_count" : 4
  162. }
  163. ]
  164. },
  165. "attr_value_agg" : {
  166. "doc_count_error_upper_bound" : 0,
  167. "sum_other_doc_count" : 0,
  168. "buckets" : [
  169. {
  170. "key" : "鸿蒙",
  171. "doc_count" : 4
  172. }
  173. ]
  174. }
  175. }
  176. ]
  177. }
  178. },
  179. "brand_agg" : {
  180. "doc_count_error_upper_bound" : 0,
  181. "sum_other_doc_count" : 0,
  182. "buckets" : [
  183. {
  184. "key" : 9,
  185. "doc_count" : 4,
  186. "brand_img_agg" : {
  187. "doc_count_error_upper_bound" : 0,
  188. "sum_other_doc_count" : 0,
  189. "buckets" : [
  190. {
  191. "key" : "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-18/de2426bd-a689-41d0-865a-d45d1afa7cde_huawei.png",
  192. "doc_count" : 4
  193. }
  194. ]
  195. },
  196. "brand_name_agg" : {
  197. "doc_count_error_upper_bound" : 0,
  198. "sum_other_doc_count" : 0,
  199. "buckets" : [
  200. {
  201. "key" : "华为",
  202. "doc_count" : 4
  203. }
  204. ]
  205. }
  206. }
  207. ]
  208. }
  209. }
  210. }

返回结果封装方法

  1. /**
  2. * 封装返回结果(参照result.json文件)
  3. *
  4. * @param response
  5. * @return
  6. */
  7. private SearchResult buildSearchResponse(SearchResponse response, SearchParam searchParam) {
  8. SearchResult searchResult = new SearchResult();
  9. SearchHits hits = response.getHits();
  10. // 封装商品信息
  11. // 保存进去就是以SkuEsModel保存的
  12. List<SkuEsModel> skuEsModelList = new ArrayList<>();
  13. // hits的hits中的source保存的是商品信息
  14. for (SearchHit hit : response.getHits()) {
  15. // 获取到json数据,转换为对象
  16. String sourceAsString = hit.getSourceAsString();
  17. SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
  18. // 设置高亮标题
  19. if (!StringUtils.isEmpty(searchParam.getKeyword())) {
  20. Map<String, HighlightField> highlightFieldMap = hit.getHighlightFields();
  21. String skuTitle = highlightFieldMap.get("skuTitle").getFragments()[0].string();
  22. skuEsModel.setSkuTitle(skuTitle);
  23. }
  24. skuEsModelList.add(skuEsModel);
  25. }
  26. searchResult.setProducts(skuEsModelList);
  27. // 当前商品所涉及的品牌信息
  28. ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
  29. List<SearchResult.BrandVo> brandVoList = new ArrayList<>();
  30. List<? extends Terms.Bucket> brandList = brand_agg.getBuckets();
  31. for (Terms.Bucket bucket : brandList) {
  32. SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
  33. // 获取品牌的id
  34. long brandId = bucket.getKeyAsNumber().longValue();
  35. // 获取品牌的名字
  36. String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
  37. // 获取品牌的图片
  38. String brandImage = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
  39. brandVo.setBrandId(brandId);
  40. brandVo.setBrandName(brandName);
  41. brandVo.setBrandImg(brandImage);
  42. brandVoList.add(brandVo);
  43. }
  44. searchResult.setBrands(brandVoList);
  45. // 当前商品所涉及的属性信息
  46. ParsedNested attr_agg = response.getAggregations().get("attr_agg");
  47. List<SearchResult.AttrVo> attrVoList = new ArrayList<>();
  48. ParsedLongTerms attrIdAgg = attr_agg.getAggregations().get("attr_id_agg");
  49. for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
  50. SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
  51. // 属性id
  52. long attrId = bucket.getKeyAsNumber().longValue();
  53. // 属性名
  54. String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
  55. // 属性值
  56. List<String> attrValueList = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
  57. String attrValue = ((Terms.Bucket) item).getKeyAsString();
  58. return attrValue;
  59. }).collect(Collectors.toList());
  60. attrVo.setAttrId(attrId);
  61. attrVo.setAttrName(attrName);
  62. attrVo.setAttrValue(attrValueList);
  63. attrVoList.add(attrVo);
  64. }
  65. searchResult.setAttrs(attrVoList);
  66. // 当前商品所涉及到的所有分类信息(从聚合中取出)
  67. ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
  68. List<SearchResult.CatalogVo> catalogVoList = new ArrayList<>();
  69. List<? extends Terms.Bucket> catalogList = catalog_agg.getBuckets();
  70. for (Terms.Bucket bucket : catalogList) {
  71. SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
  72. // 得到分类id
  73. String catalogId = bucket.getKeyAsString();
  74. catalogVo.setCatalogId(Long.parseLong(catalogId));
  75. // 得到分类名,子聚合
  76. ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
  77. List<? extends Terms.Bucket> catalogNameList = catalog_name_agg.getBuckets();
  78. String catalogName = catalogNameList.get(0).getKeyAsString();
  79. catalogVo.setCatalogName(catalogName);
  80. catalogVoList.add(catalogVo);
  81. }
  82. searchResult.setCatalogs(catalogVoList);
  83. // 封装分页信息
  84. long total = hits.getTotalHits().value;
  85. searchResult.setPageNum(searchParam.getPageNum());
  86. searchResult.setTotal(total);
  87. // 总页码(总页数 / 每页数)有余数加一
  88. int totalPage = (int) total % EsConstant.PRODUCT_PAGESIZE == 1 ? (((int) total / EsConstant.PRODUCT_PAGESIZE) + 1) : (int) total / EsConstant.PRODUCT_PAGESIZE;
  89. searchResult.setTotalPages(totalPage);
  90. // 导航页码[1、2、3、4、5]
  91. List<Integer> pageNavList = new ArrayList<>();
  92. for (int i = 1; i <= totalPage; i++) {
  93. pageNavList.add(i);
  94. }
  95. searchResult.setPageNavs(pageNavList);
  96. // 构建面包屑导航功能
  97. if (searchParam.getAttrs() != null && searchParam.getAttrs().size() > 0) {
  98. List<SearchResult.NavVo> navVoList = searchParam.getAttrs().stream().map(attr -> {
  99. SearchResult.NavVo navVo = new SearchResult.NavVo();
  100. // attr=2_5寸:6寸(此时只有属性id和属性值)
  101. String[] split = attr.split("_");
  102. // 此时需要调用product服务查询属性名
  103. navVo.setNavValue(split[1]);
  104. R r = productFeignClient.attrInfo(Long.parseLong(split[0]));
  105. searchResult.getAttrIds().add(Long.parseLong(split[0]));
  106. if (r.getCode() == 0) {
  107. AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {
  108. });
  109. // 设置属性名
  110. navVo.setNavName(data.getAttrName());
  111. } else {
  112. navVo.setNavName(split[0]);
  113. }
  114. // 取消了这个面包屑以后,我们要跳转到那个地方.将请求地址的urL里面的当前置空拿到所有的查询条件,去掉当前等条件
  115. // attrs=15_海思(Hisilicon)
  116. String replace = replaceQueryString(searchParam, attr, "attrs");
  117. navVo.setLink("http://search.gulimalls.com/list.html?" + replace);
  118. return navVo;
  119. }).collect(Collectors.toList());
  120. searchResult.setNavs(navVoList);
  121. }
  122. if (!StringUtils.isEmpty(searchParam.getBrandId())) {
  123. SearchResult.NavVo navVo = new SearchResult.NavVo();
  124. navVo.setNavName("品牌");
  125. // 远程查询所有品牌
  126. R r = productFeignClient.getInfoByBrandIds(searchParam.getBrandId());
  127. if (r.getCode() == 0) {
  128. List<BrandVo> brandVo = r.getData("brand", new TypeReference<List<BrandVo>>() {
  129. });
  130. StringBuffer sb = new StringBuffer();
  131. String replace = null;
  132. for (BrandVo brand : brandVo) {
  133. sb.append(brand.getBrandName() + ";");
  134. replace = replaceQueryString(searchParam, brand.getBrandId().toString(), "brandId");
  135. }
  136. navVo.setNavValue(sb.toString());
  137. navVo.setLink("http://search.gulimalls.com/list.html?" + replace);
  138. }
  139. searchResult.getNavs().add(navVo);
  140. }
  141. // TODO 分类的面包屑,不需要导航取消
  142. return searchResult;
  143. }
  144. /**
  145. * 替换地址字符串
  146. *
  147. * @param searchParam
  148. * @param attr
  149. * @return
  150. */
  151. private String replaceQueryString(SearchParam searchParam, String attr, String key) {
  152. // TODO 请求地址参数转义有问题
  153. String encode = null;
  154. try {
  155. encode = URLEncoder.encode(attr, "UTF-8");
  156. encode = encode.replace("+", "%20"); // 处理空格
  157. encode = encode.replace("%28", "("); // 处理)
  158. encode = encode.replace("%29", ")"); // 处理(
  159. } catch (UnsupportedEncodingException e) {
  160. e.printStackTrace();
  161. }
  162. String replace = searchParam.get_queryString().replace("&" + key + "=" + encode, " ");
  163. return replace;
  164. }

Controller层

  1. @Controller
  2. public class SearchController {
  3. @Autowired
  4. private ElasticSearchService elasticSearchService;
  5. @GetMapping("/list.html")
  6. public String list(SearchParam searchParam, Model model, HttpServletRequest httpServletRequest) {
  7. searchParam.set_queryString(httpServletRequest.getQueryString()); // 获取查询请求
  8. SearchResult searchResult = elasticSearchService.search(searchParam);
  9. model.addAttribute("result", searchResult);
  10. return "list";
  11. }
  12. }