ES查询

商品的搜索(ES)

当天的git地址:ES商品搜索

一、关键字搜索(搜索框)

1、创建一个接口里面传入map(里面封装的是查询条件),返回也是一个map(里面是封装的查询上来的数据)

/**
 * @Description:
 * @Version: V1.0
 */
public interface SearchService {

    /**
     * 全文检索 万能型
     * @param paramMap
     * @return
     */
    Map search(Map<String,String> paramMap);

}
  • 1、定义一个封装查询结果的map,

  • 2、对条件的非空判断

  • 3、构建查询条件封装对象

  • 4、组合条件对象

  • 5、判断搜索框里不为空 根据搜索分词查询

  • 6、使用布尔查询查询,must查询。里面拼接的是match(分词查询)根据索引名称(name) 2、分词查询(keywords) 3、交集。

  • 7、绑定条件对象

  • 8、开启查询:(1)、条件构建对象 (2)、查询操作实体类 (3)、查询结果操作对象

  • 9、重写结果操作对象接口:获取ES里面的数据封装到list,并把(1)、ES数据集合 (2)、分页总数据 (3)、数据的条目数 (4)、聚合数据,封装到他的实现类中返回去

  • 10、把分页的总条数、分页的总页数、查询上来的结果。封装到定义好的结果集map返回上去

     /**
         * 全文检索
         *
         * @param paramMap
         * @return
         */
        @Override
        public Map search(Map<String, String> paramMap) {
    
            //接收查询上来的数据
            Map<String, Object> resultMap = new HashMap<>();
    
            //条件不为空
            if (paramMap != null) {
    
                //构建查询条件封装对象
                NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
                //组合条件对象
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    
    
                //1、判断搜索框里不为空  根据搜索分词查询
                if (!StringUtils.isEmpty(paramMap.get("keywords"))) {
                    //1、索引名称 2、分词查询 3、交集
                    boolQuery.must(QueryBuilders.matchQuery("name", paramMap.get("keywords")).operator(Operator.AND));
    
                }
    
                
                //绑定条件对象
                nativeSearchQueryBuilder.withQuery(boolQuery);
    
    
                //开启查询
                /**
                 * 第一个参数: 条件构建对象
                 * 第二个参数: 查询操作实体类
                 * 第三个参数: 查询结果操作对象
                 */
    
                //封装查询结果
                AggregatedPage<SkuInfo> skuInfos = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                    @Override
                    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
    
                        ArrayList<T> list = new ArrayList<>();
    
                        //获取命中对象
                        SearchHits hits = searchResponse.getHits();
    
                        if (hits != null) {
    
                            //获取ES每条数据
                            for (SearchHit hit : hits) {
                                SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
                                
                                list.add((T) skuInfo);
                            }
                        }
    
                        //1、ES数据集合 2、分页总数据 3、数据的条目数 4、聚合数据
                        return new AggregatedPageImpl<>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                    }
                });
    
    
                //11. 总条数
                resultMap.put("total", skuInfos.getTotalElements());
                //12. 总页数
                resultMap.put("totalPages", skuInfos.getTotalPages());
                //13. 查询结果集合
                resultMap.put("rows", skuInfos.getContent());
                
    
                return resultMap;
            }
            return null;
        }
    

二、对品牌查询不分词

  • 1、对条件map里的品牌进行非空判断

  • 2、在条件查询使用filter连接,对brandName作用域 ,根据brand进行不分词查询

				//上面代码一样


               //2、对品牌进行查询
            if (!StringUtils.isEmpty(paramMap.get("brand"))) {

                //不分词查询
                boolQuery.filter(QueryBuilders.termQuery("brandName", paramMap.get("brand")));
            }
            
         
            
            //下面代码都是一样的

三、对品牌进行分组查询

  • 1、对品牌进行分组查询,参数1、自己的分组的名称 2、对那个作用域进行分组(注意分组类型必须是keyword不分词)
  • 2、从返回的结果集获取分组对象,通过stream流的方式将分组的名称(key),全部封装到了list集合中返回给前端
            
             //3、对品牌进行分组查询 聚合查询只有keyword的类型才可以
            String  skuBrand="skuBrand";            //参数 1、分组的名称(自己随便起) 2、分组的字段
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
            

            //获取品牌分组的对象
            StringTerms brandTerms = (StringTerms) skuInfos.getAggregation(skuBrand);
            //通过stream流的方式获取品牌的集合
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            //封装品牌
            resultMap.put("brandList", brandList);
            

四、商品的规格的查询

controller层 由于url路径上传入的参数里有加号,‘+’是特殊字符需要转换


    @GetMapping
    @ResponseBody
    public Map handlerSearchMap(@RequestParam Map<String,String> searchMap){

        //特殊符号处理
        handleSearchMap(searchMap);
        Map resultMap = searchService.search(searchMap);
        return resultMap;

    }


//处理特殊字符的方法
    private void handleSearchMap(Map<String, String> searchMap) {
        Set<Map.Entry<String, String>> entries = searchMap.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            if (entry.getKey().startsWith("spec_")){
                searchMap.put(entry.getKey(),entry.getValue().replace("+","%2B"));
            }
        }
    }

service层

  • 1、判断map里的key有没有规格条件(spec_)

  • 2、把%2B再转化回来

  • 3、由于 规格详情为object对象类型需要获取里面的keyword属性值,作为作用域

  • 4、将获取keyword作为作用域,和要查询的商品规格名称

    //格式
    "specMap": {
                "properties": {
                  "颜色": {
                    "type": "text",
                    "fields": {
                      "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                      }
                    }
                  },
                  "尺码": {
                    "type": "text",
                    "fields": {
                      "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                      }
                    }
                  }
                    
                   
    //展示
                     "status": "1",
              "createTime": 1556668800000,
              "updateTime": 1556668800000,
              "isDefault": null,
              "spuId": 2387887412200,
              "categoryId": 0,
              "categoryName": "休闲鞋",
              "brandName": "斯凯奇",
              "spec": "{'颜色': '粉色', '尺码': '40'}",
              "specMap": {          //规格
                "颜色": "粉色",
                "尺码": "40"
              }
    
 
            
             //4、对商品的规格进行查询
            for (String key : paramMap.keySet()) {
                if (key.startsWith("spec_")) {
                    //将特殊字符再转回来
                    String value = paramMap.get(key).replace("%2B","+");
                    //获取对象里的keyword的字段。来进行分组查询
                    boolQuery.filter(QueryBuilders.termQuery(("specMap." + key.substring(5) + ".keyword"), value));
                }
            }
            
            
          

五、对商品规格进行分组查询

总结:对应的mapping信息,spec 作为字符串,存储所有的信息,以字符串的形式,用于统计.

​ specMap: 作为object类型,用于查询

  • 1、将spec 转换为keyword类型,因为聚合查询必须要keyword类

       "spec": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
    
  • 2、传入参数: 设置名称,分组的作用域

  • 3、获取分组的对象,从分组的对象通过stream获取分组数据

  • 4、将分组数据转换为前端需要的数据

//规格分组的条件
            //5、对商品的规格进行分组查询
            String skuSpec="skuSpec";
            //                                      1、分组名称 2、由于spec的类型是text,改为keyword类型,进行统计
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));



 //获取规格的分组对象
            StringTerms specTerms = (StringTerms) skuInfos.getAggregation(skuSpec);
            //通过stream流的方式获取规格的集合
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            
			//调用方法,将规格分组转换一下。给前端使用
            Map<String, Set<String>> specMap = formartSpec(specList);
            resultMap.put("specMap", specMap);




/*

        [
              "{'颜色': '红色', '尺码': '150度'}",
              "{'颜色': '黑色', '尺码': '150度'}",
              "{'颜色': '黑色'}",
              "{'颜色': '红色', '尺码': '100度'}",
              "{'颜色': '红色', '尺码': '250度'}",
              "{'颜色': '红色', '尺码': '350度'}",
              "{'颜色': '黑色', '尺码': '200度'}",
              "{'颜色': '黑色', '尺码': '250度'}"
          ]

            转成页面需要的格式:
         {
             颜色:[黑色,红色],
             尺码:[100度,150度,200度,250度,350度]
         }

      */

    //将规格进行转换
    private Map<String, Set<String>> formartSpec(List<String> specList) {

        //创建一个map,封装成页面需要的格式
        Map<String, Set<String>> resultMap  = new HashMap<>();

        if (specList.size() > 0 && specList != null) {

            for (String spec : specList) {

                //将list的每条json字符串转换成map
                Map<String,String>  specMap= JSON.parseObject(spec, Map.class);

                //遍历获取所有的key (颜色,尺码)
                for (String  key : specMap.keySet()) {

                    //根据key取出规格相对应的信息 例如(颜色:[黑色,黄色])
                    Set<String> specSet  = resultMap.get(key);

                    //没有的话就创建一个,set集合
                    if (specSet == null) {
                        specSet = new HashSet<>();
                    }

                    //将这个key对应value取出,赋值给set
                    specSet.add(specMap.get(key));

                    //将这个key,所对应的set封装到map中
                    resultMap.put(key, specSet);
                }
            }
        }
        return resultMap;

    }


六、价格区间查询

  • 1、判断条件map里有没有这个条件
  • 2、对“-”截取价格字符串 (2000-3000)
  • 3、判断截取后的数组,如果数组长度大于2,进行区间查询(2000<=x<=3000)
  • 4、只有一个的话,将他作为最大区间(x<=3000)
//6、商品的价格区间查询
if (StringUtils.isNotEmpty(paramMap.get("price"))) {

    //对价格字符截取
    String[] prices = paramMap.get("price").split("-");

    //如果价格区间是1000-2000,就会取这之间的
    if (prices.length == 2) {
        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(Long.parseLong(prices[1])));
    }

    //只有一个价格,就会取大于等于这个价格
    boolQuery.filter(QueryBuilders.rangeQuery("price").gte(Long.parseLong(prices[0])));
}

七、分页查询

  • 1、对当前页,一页显示多少条,进行非空判断如果为空的话,就赋一个默认值。
  • 2、将分页条件传入,注意springboot的当前页是从0开始的。所以需要减1。
//7、分页查询

String pageNum = paramMap.get("pageNum"); //当前页
String pageSize = paramMap.get("pageSize"); //每页显示多少条

//给当前分页数组默认值
if (StringUtils.isEmpty(pageNum)) {
    pageNum = "1";
}

if (StringUtils.isEmpty(pageSize)) {
    pageSize = "20";
}
//注意当前页是0开始的,需要减一
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));

八、价格的排序查询

  • 1、价格排序需要两个条件 (1):作用域 (2):排序的规则

  • 2、判断这个两个条件不为空

  • 3、如果是排序条件是ASC 那就是排序操作

  • 4、如果是排序条件是DESC 那就是降序操作

    //8、价格排序查询
    //参数1、当前域(价格、销量)   2、排序的关键字  (升序ASC,降序DESC)
    if (StringUtils.isNotEmpty(paramMap.get("sortField")) && StringUtils.isNotEmpty(paramMap.get("sortRule"))){
        //升序
        if ("ASC".equals(paramMap.get("sortRule"))) {
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((paramMap.get("sortField"))).order(SortOrder.ASC));
        }
    
        //降序
        if ("DESC".equals(paramMap.get("sortRule"))) {
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((paramMap.get("sortField"))).order(SortOrder.DESC));
        }
    }
    

九、高亮查询

  • 1、高亮查询的作用域

  • 2、高亮的前缀

  • 3、高亮的后缀

  • 4、取出高亮数据替换原来内容

    //9、设置高亮域以及高亮的样式
    HighlightBuilder.Field field = new HighlightBuilder.Field("name")
    .preTags("<span style='color:red'>")//高亮样式的前缀
    .postTags("</span>");//高亮样式的后缀
    nativeSearchQueryBuilder.withHighlightFields(field);
    
    
    
    
      //封装查询结果
                AggregatedPage<SkuInfo> skuInfos = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                    @Override
                    public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
    
                        ArrayList<T> list = new ArrayList<>();
    
                        //获取命中对象
                        SearchHits hits = searchResponse.getHits();
    
                        if (hits != null) {
    
                            //获取ES每条数据
                            for (SearchHit hit : hits) {
                                SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
    
                                //获取高亮对象
                                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                                if (highlightFields != null && highlightFields.size() > 0) {
    
                                    //将高亮的样式替换上原来数据
                                    skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
                                }
    
                                list.add((T) skuInfo);
                            }
                        }
    

完整代码:

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ElasticsearchTemplate esTemplate;

    /**
     * 全文检索
     *
     * @param paramMap
     * @return
     */
    @Override
    public Map search(Map<String, String> paramMap) {

        //接收查询上来的数据
        Map<String, Object> resultMap = new HashMap<>();

        //条件不为空
        if (paramMap != null) {

            //构建查询条件封装对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //组合条件对象
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();


            //1、判断搜索框里不为空  根据搜索分词查询
            if (!StringUtils.isEmpty(paramMap.get("keywords"))) {
                //1、索引名称 2、分词查询 3、交集
                boolQuery.must(QueryBuilders.matchQuery("name", paramMap.get("keywords")).operator(Operator.AND));

            }

            //2、对品牌进行查询
            if (!StringUtils.isEmpty(paramMap.get("brand"))) {

                //不分词查询
                boolQuery.filter(QueryBuilders.termQuery("brandName", paramMap.get("brand")));
            }

            //3、对品牌进行分组查询 聚合查询只有keyword的类型才可以
            String  skuBrand="skuBrand";            //参数 1、分组的名称(自己随便起) 2、分组的字段
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));


            //4、对商品的规格进行查询
            for (String key : paramMap.keySet()) {
                if (key.startsWith("spec_")) {
                    //将特殊字符再转回来
                    String value = paramMap.get(key).replace("%2B","+");
                    //获取对象里的keyword的字段。来进行分组查询
                    boolQuery.filter(QueryBuilders.termQuery(("specMap." + key.substring(5) + ".keyword"), value));
                }
            }

            //5、对商品的规格进行分组查询
            String skuSpec="skuSpec";
            //                                      1、分组名称 2、由于spec的类型是text,改为keyword类型,进行统计
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));

            //6、商品的价格区间查询
            if (StringUtils.isNotEmpty(paramMap.get("price"))) {

                //对价格字符截取
                String[] prices = paramMap.get("price").split("-");

                //如果价格区间是1000-2000,就会取这之间的
                if (prices.length == 2) {
                    boolQuery.filter(QueryBuilders.rangeQuery("price").lte(Long.parseLong(prices[1])));
                }

                //只有一个价格,就会取大于等于这个价格
                boolQuery.filter(QueryBuilders.rangeQuery("price").gte(Long.parseLong(prices[0])));
            }

            //7、分页查询

            String pageNum = paramMap.get("pageNum"); //当前页
            String pageSize = paramMap.get("pageSize"); //每页显示多少条

            //给当前分页数组默认值
            if (StringUtils.isEmpty(pageNum)) {
                pageNum = "1";
            }

            if (StringUtils.isEmpty(pageSize)) {
                pageSize = "20";
            }
            //注意当前页是0开始的,需要减一
            nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));


            //8、价格排序查询
            //参数1、当前域(价格、销量)   2、排序的关键字  (升序ASC,降序DESC)
            if (StringUtils.isNotEmpty(paramMap.get("sortField")) && StringUtils.isNotEmpty(paramMap.get("sortRule"))){
                //升序
                if ("ASC".equals(paramMap.get("sortRule"))) {
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((paramMap.get("sortField"))).order(SortOrder.ASC));
                }

                //降序
                if ("DESC".equals(paramMap.get("sortRule"))) {
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((paramMap.get("sortField"))).order(SortOrder.DESC));
                }
            }

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

            //绑定条件对象
            nativeSearchQueryBuilder.withQuery(boolQuery);


            //开启查询
            /**
             * 第一个参数: 条件构建对象
             * 第二个参数: 查询操作实体类
             * 第三个参数: 查询结果操作对象
             */

            //封装查询结果
            AggregatedPage<SkuInfo> skuInfos = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {

                    ArrayList<T> list = new ArrayList<>();

                    //获取命中对象
                    SearchHits hits = searchResponse.getHits();

                    if (hits != null) {

                        //获取ES每条数据
                        for (SearchHit hit : hits) {
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);

                            //获取高亮对象
                            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                            if (highlightFields != null && highlightFields.size() > 0) {

                                //将高亮的样式替换上原来数据
                                skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
                            }

                            list.add((T) skuInfo);
                        }
                    }

                    //1、ES数据集合 2、分页总数据 3、数据的条目数 4、聚合数据
                    return new AggregatedPageImpl<>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                }
            });


            //获取品牌分组的对象
            StringTerms brandTerms = (StringTerms) skuInfos.getAggregation(skuBrand);
            //通过stream流的方式获取品牌的集合
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            //封装品牌
            resultMap.put("brandList", brandList);

            //获取规格的分组对象
            StringTerms specTerms = (StringTerms) skuInfos.getAggregation(skuSpec);
            //通过stream流的方式获取规格的集合
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            //封装分组
            Map<String, Set<String>> specMap = formartSpec(specList);
            resultMap.put("specMap", specMap);


            //11. 总条数
            resultMap.put("total", skuInfos.getTotalElements());
            //12. 总页数
            resultMap.put("totalPages", skuInfos.getTotalPages());
            //13. 查询结果集合
            resultMap.put("rows", skuInfos.getContent());
            //返回当前页
            resultMap.put("pageNum", pageNum);

            return resultMap;
        }
        return null;
    }


    /*

        [
              "{'颜色': '红色', '尺码': '150度'}",
              "{'颜色': '黑色', '尺码': '150度'}",
              "{'颜色': '黑色'}",
              "{'颜色': '红色', '尺码': '100度'}",
              "{'颜色': '红色', '尺码': '250度'}",
              "{'颜色': '红色', '尺码': '350度'}",
              "{'颜色': '黑色', '尺码': '200度'}",
              "{'颜色': '黑色', '尺码': '250度'}"
          ]

            转成页面需要的格式:
         {
             颜色:[黑色,红色],
             尺码:[100度,150度,200度,250度,350度]
         }

      */

    //将规格进行转换
    private Map<String, Set<String>> formartSpec(List<String> specList) {

        //创建一个map,封装成页面需要的格式
        Map<String, Set<String>> resultMap  = new HashMap<>();

        if (specList.size() > 0 && specList != null) {

            for (String spec : specList) {

                //将list的每条json字符串转换成map
                Map<String,String>  specMap= JSON.parseObject(spec, Map.class);

                //遍历获取所有的key (颜色,尺码)
                for (String  key : specMap.keySet()) {

                    //根据key取出规格相对应的信息 例如(颜色:[黑色,黄色])
                    Set<String> specSet  = resultMap.get(key);

                    //没有的话就创建一个,set集合
                    if (specSet == null) {
                        specSet = new HashSet<>();
                    }

                    //将这个key对应value取出,赋值给set
                    specSet.add(specMap.get(key));

                    //将这个key,所对应的set封装到map中
                    resultMap.put(key, specSet);
                }
            }
        }
        return resultMap;

    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值