条件筛选
用户有可能会根据分类搜索、品牌搜索,还有可能根据规格搜索,以及价格搜索和排序操作。根据分类和品牌搜索的时候,可以直接根据指定域搜索,而规格搜索的域数据是不确定的,价格是一个区间搜索,所以我们可以分为三段实现,先实现分类、品牌搜素,再实现规格搜索,然后实现价格区间搜索。
品牌筛选
需求分析
页面每次向后台传入对应的分类和品牌,后台据分类和品牌进行条件过滤即可。
代码实现
修改搜索微服务com.changgou.service.SearchServiceImpl的搜索方法,添加品牌过滤,代码如下:
代码如下:
@Override public Map search(Map<String, String> searchMap) throws Exception { Map<String, Object> resultMap = new HashMap<>(); //有条件才查询Es if (null != searchMap) { //组合条件对象 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); //0:关键词 if (StringUtils.isNotEmpty(searchMap.get("keywords"))) { boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND)); } //1:条件 品牌 if (StringUtils.isNotEmpty(searchMap.get("brand"))) { boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand"))); } //4. 原生搜索实现类 NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); nativeSearchQueryBuilder.withQuery(boolQuery); //6. 品牌聚合(分组)查询 String skuBrand = "skuBrand"; nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName")); //10: 执行查询, 返回结果对象 AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() { @Override public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) { List<T> list = new ArrayList<>(); SearchHits hits = searchResponse.getHits(); if (null != hits) { for (SearchHit hit : hits) { SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class); list.add((T) skuInfo); } } return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations()); } }); //11. 总条数 resultMap.put("total", aggregatedPage.getTotalElements()); //12. 总页数 resultMap.put("totalPages", aggregatedPage.getTotalPages()); //13. 查询结果集合 resultMap.put("rows", aggregatedPage.getContent()); //14. 获取品牌聚合结果 StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand); List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList()); resultMap.put("brandList", brandList); return resultMap; } return null; }
测试
测试效果如下:
访问地址:http://localhost:9009/sku_search?keywords=手机
此时只能搜到华为手环设备
规格过滤
需求分析
规格这一部分,需要向后台发送规格名字以及规格值,我们可以按照一定要求来发送数据,例如规格名字以特殊前缀提交到后台:spec_网络制式:电信4G、spec_显示屏尺寸:4.0-4.9英寸
后台接到数据后,可以根据前缀spec_来区分是否是规格,如果以spec_xxx
开始的数据则为规格数据,需要根据指定规格找信息。
上图是规格的索引存储格式,真实数据在spechMap.规格名字.keyword中,所以找数据也是按照如下格式去找:
spechMap.规格名字.keyword
代码实现
修改com.changgou.service.SearchServiceImpl的搜索方法,增加规格查询操作,代码如下:
代码如下:
@Override public Map search(Map<String, String> searchMap) throws Exception { Map<String, Object> resultMap = new HashMap<>(); //有条件才查询Es if (null != searchMap) { //组合条件对象 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); //0:关键词 if (!StringUtils.isEmpty(searchMap.get("keywords"))) { boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND)); } //1:条件 品牌 if (!StringUtils.isEmpty(searchMap.get("brand"))) { boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand"))); } //2:条件 规格 for (String key : searchMap.keySet()) { if (key.startsWith("spec_")) { String value = searchMap.get(key).replace("%2B", "+"); boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword",value)); } } //4. 原生搜索实现类 NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); nativeSearchQueryBuilder.withQuery(boolQuery); //6. 品牌聚合(分组)查询 String skuBrand = "skuBrand"; nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName")); //7. 规格聚合(分组)查询 String skuSpec = "skuSpec"; nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword")); //10: 执行查询, 返回结果对象 AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() { @Override public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) { List<T> list = new ArrayList<>(); SearchHits hits = searchResponse.getHits(); if (null != hits) { for (SearchHit hit : hits) { SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class); list.add((T) skuInfo); } } return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations()); } }); //11. 总条数 resultMap.put("total", aggregatedPage.getTotalElements()); //12. 总页数 resultMap.put("totalPages", aggregatedPage.getTotalPages()); //13. 查询结果集合 resultMap.put("rows", aggregatedPage.getContent()); //14. 获取品牌聚合结果 StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand); List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList()); resultMap.put("brandList", brandList); //15. 获取规格聚合结果 StringTerms specTerms = (StringTerms) aggregatedPage.getAggregation(skuSpec); List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList()); resultMap.put("specList", specList(specList)); return resultMap; } return null; } //处理规格集合 public Map<String, Set<String>> specList(List<String> specList) { Map<String, Set<String>> specMap = new HashMap<>(); if (null != specList && specList.size() > 0) { for (String spec : specList) { Map<String, String> map = JSON.parseObject(spec, Map.class); Set<Map.Entry<String, String>> entries = map.entrySet(); for (Map.Entry<String, String> entry : entries) { String key = entry.getKey(); String value = entry.getValue(); Set<String> specValues = specMap.get(key); if (null == specValues) { specValues = new HashSet<>(); } specValues.add(value); specMap.put(key, specValues); } } } return specMap; }
测试
访问地址:http://localhost:9009/sku_search?keywords=手机
价格区间查询
需求分析
价格区间查询,每次需要将价格传入到后台,前端传入后台的价格大概是price=0-500
或者price=500-1000
依次类推,最后一个是price=3000
,后台可以根据-分割,如果分割得到的结果最多有2个,第1个表示x<price
,第2个表示price<=y
。
代码实现
修改com.changgou.service.impl.SearchServiceImpl的搜索方法,增加价格区间查询操作,代码如下:
代码如下:
@Override public Map search(Map<String, String> searchMap) throws Exception { Map<String, Object> resultMap = new HashMap<>(); //有条件才查询Es if (null != searchMap) { //组合条件对象 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); //0:关键词 if (!StringUtils.isEmpty(searchMap.get("keywords"))) { boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND)); } //1:条件 品牌 if (!StringUtils.isEmpty(searchMap.get("brand"))) { boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand"))); } //2:条件 规格 for (String key : searchMap.keySet()) { if (key.startsWith("spec_")) { String value = searchMap.get(key).replace("%2B", "+"); boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword",value)); } } //3:条件 价格 if (StringUtils.isNotEmpty(searchMap.get("price"))) { String[] p = searchMap.get("price").split("-"); if (p.length == 2) { boolQuery.filter(QueryBuilders.rangeQuery("price").lte(p[1])); } boolQuery.filter(QueryBuilders.rangeQuery("price").gte(p[0])); } //4. 原生搜索实现类 NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); nativeSearchQueryBuilder.withQuery(boolQuery); //6. 品牌聚合(分组)查询 String skuBrand = "skuBrand"; nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName")); //7. 规格聚合(分组)查询 String skuSpec = "skuSpec"; nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword")); //10: 执行查询, 返回结果对象 AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() { @Override public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) { List<T> list = new ArrayList<>(); SearchHits hits = searchResponse.getHits(); if (null != hits) { for (SearchHit hit : hits) { SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class); list.add((T) skuInfo); } } return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations()); } }); //11. 总条数 resultMap.put("total", aggregatedPage.getTotalElements()); //12. 总页数 resultMap.put("totalPages", aggregatedPage.getTotalPages()); //13. 查询结果集合 resultMap.put("rows", aggregatedPage.getContent()); //14. 获取品牌聚合结果 StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand); List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList()); resultMap.put("brandList", brandList); //15. 获取规格聚合结果 StringTerms specTerms = (StringTerms) aggregatedPage.getAggregation(skuSpec); List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList()); resultMap.put("specList", specList(specList)); return resultMap; } return null; }
测试
访问地址:http://localhost:9009/sku_search?keywords=手机
效果如下(部分数据):
[ { "id": 1088256019328536576, "name": "守护宝幼儿安全手环", "price": 500, "num": 100, "image": "http://img10.360buyimg.com/n1/s450x450_jfs/t3457/294/236823024/102048/c97f5825/58072422Ndd7e66c4.jpg", "status": "1", "createTime": "2019-01-24T10:03:48.000+0000", "updateTime": "2019-01-24T10:03:48.000+0000", "isDefault": null, "spuId": 1088256019315953664, "categoryId": 1108, "categoryName": "户外工具", "brandName": "守护宝", "spec": "{\"颜色\":\"红\",\"机身内存\":\"64G\"}", "specMap": { "颜色": "红", "机身内存": "64G" } }, { "id": 1088256014043713536, "name": "计步器小米手环,适用老人、小孩", "price": 800, "num": 100, "image": "http://img10.360buyimg.com/n1/s450x450_jfs/t3457/294/236823024/102048/c97f5825/58072422Ndd7e66c4.jpg", "status": "1", "createTime": "2019-01-24T10:03:47.000+0000", "updateTime": "2019-01-24T10:03:47.000+0000", "isDefault": null, "spuId": 1088256014026936320, "categoryId": 1192, "categoryName": "小家电", "brandName": "小米", "spec": "{\"颜色\":\"红\",\"机身内存\":\"64G\"}", "specMap": { "颜色": "红", "机身内存": "64G" } } ]