美文网首页
乐优商城学习笔记十七-搜索过滤(二)

乐优商城学习笔记十七-搜索过滤(二)

作者: smallmartial | 来源:发表于2019-04-20 14:59 被阅读0次

    title: 乐优商城学习笔记十七-搜索过滤(二)
    date: 2019-04-20 14:37:56
    tags:
    - 乐优商城
    - java
    - springboot
    categories:
    - 乐优商城


    3.生成规格参数过滤

    3.1.谋而后动

    有四个问题需要先思考清楚:

    • 什么时候显示规格参数过滤?
    • 如何知道哪些规格需要过滤?
    • 要过滤的参数,其可选值是如何获取的?
    • 规格过滤的可选值,其数据格式怎样的?

    什么情况下显示有关规格参数的过滤?

    如果用户尚未选择商品分类,或者聚合得到的分类数大于1,那么就没必要进行规格参数的聚合。因为不同分类的商品,其规格是不同的。

    因此,我们在后台需要对聚合得到的商品分类数量进行判断,如果等于1,我们才继续进行规格参数的聚合

    如何知道哪些规格需要过滤?

    我们不能把数据库中的所有规格参数都拿来过滤。因为并不是所有的规格参数都可以用来过滤,参数的值是不确定的。

    值的庆幸的是,我们在设计规格参数时,已经标记了某些规格可搜索,某些不可搜索。

    因此,一旦商品分类确定,我们就可以根据商品分类查询到其对应的规格,从而知道哪些规格要进行搜索。

    要过滤的参数,其可选值是如何获取的?

    虽然数据库中有所有的规格参数,但是不能把一切数据都用来供用户选择。

    与商品分类和品牌一样,应该是从用户搜索得到的结果中聚合,得到与结果品牌的规格参数可选值。

    规格过滤的可选值,其数据格式怎样的?

    我们直接看页面效果:

    1526805322441

    我们之前存储时已经将数据分段,恰好符合这里的需求

    3.3.实战

    接下来,我们就用代码实现刚才的思路。

    总结一下,应该是以下几步:

    • 1)用户搜索得到商品,并聚合出商品分类
    • 2)判断分类数量是否等于1,如果是则进行规格参数聚合
    • 3)先根据分类,查找可以用来搜索的规格
    • 4)对规格参数进行聚合
    • 5)将规格参数聚合结果整理后返回

    3.3.1.扩展返回结果

    返回结果中需要增加新数据,用来保存规格参数过滤条件。这里与前面的品牌和分类过滤的json结构类似:

    [
        {
            "k":"规格参数名",
            "options":["规格参数值","规格参数值"]
        }
    ]
    

    因此,在java中我们用List<Map<String,Object>>来表示。

    /**
     * @Author smallmartial
     * @Date 2019/4/19
     * @Email smallmarital@qq.com
     */
    @Data
    public class SearchResult extends PageResult<Goods> {
    
        private List<Category> categories;//分类过滤条件
    
        private List<Brand> brands;//品牌过滤条件
    
        private List<Map<String,Object>> specs; // 规格参数过滤条件
    
        public SearchResult(){}
    
        public SearchResult(Long total, Integer totalPage, List<Goods> item, List<Category> categories, List<Brand> brands, List<Map<String, Object>> specs) {
            super(total, totalPage, item);
            this.categories = categories;
            this.brands = brands;
            this.specs = specs;
        }
    }
    

    3.3.2.判断是否需要聚合

    首先,在聚合得到商品分类后,判断分类的个数,如果是1个则进行规格聚合:

        if (categories !=null && categories.size() == 1){
             specs = buildSpecificationAgg(categories.get(0).getId(),basicQuery);
    
            }
    

    我们将聚合的代码抽取到了一个buildSpecificationAgg方法中。

    3.3.3.获取需要聚合的规格参数

    然后,我们需要根据商品分类,查询所有可用于搜索的规格参数:

            List<SpecParam> params = specificationClient.querySpecSpecParam(null, cid, true, null);
    
    

    要注意的是,这里我们需要根据id查询规格,而规格参数接口需要从商品微服务提供

    商品微服务:ly-item-interface中提供接口:

    @RequestMapping("spec")
    public interface SpecificationApi {
    
        @GetMapping("/params")
        List<SpecParam> querySpecParam(SpecParam specParam);
    }
    

    搜索服务中调用:

    @FeignClient("item-service")
    public interface SpecificationClient extends SpecificationApi {
    }
    

    3.3.4.聚合规格参数

    因为规格参数保存时不做分词,因此其名称会自动带上一个.keyword后缀:

            for (SpecParam param : params) {
                String name = param.getName();
                queryBuilder.addAggregation(AggregationBuilders.terms(name)
                        .field("specs."+name+".keyword"));
            }
    

    3.3.5.解析聚合结果

            //解析结果
           Aggregations aggs = result.getAggregations();
            for (SpecParam param : params) {
                //规格参数名
                Map<String,Object> map = new HashMap();
    
                //准备map
                String name = param.getName();
                map.put("k",name);
    
                StringTerms terms = (StringTerms) aggs.get(name);
                map.put("options",terms.getBuckets().stream().map(b -> b.getKeyAsString()).collect(Collectors.toList()));
                specs.add(map);
            }
    
    

    3.3.6.最终的代码

     /**
         * 聚合规格参数查询
         * @param cid
         * @param basicQuery
         * @return
         */
        private List<Map<String,Object>> buildSpecificationAgg(Long cid, QueryBuilder basicQuery) {
            List<Map<String, Object>> specs = new ArrayList<>();
            //查询所需要的结果
            List<SpecParam> params = specificationClient.querySpecSpecParam(null, cid, true, null);
            //聚合
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            //带上查询条件
            queryBuilder.withQuery(basicQuery);
            for (SpecParam param : params) {
                String name = param.getName();
                queryBuilder.addAggregation(AggregationBuilders.terms(name)
                        .field("specs."+name+".keyword"));
            }
            //获取结果
            AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);
            // 查询
            //解析结果
           Aggregations aggs = result.getAggregations();
            for (SpecParam param : params) {
                //规格参数名
                Map<String,Object> map = new HashMap();
    
                //准备map
                String name = param.getName();
                map.put("k",name);
                
                StringTerms terms = (StringTerms) aggs.get(name);
                map.put("options",terms.getBuckets().stream().map(b -> b.getKeyAsString()).collect(Collectors.toList()));
                specs.add(map);
            }
    
            return specs;
        }
    
    

    结果


    1526836458716

    3.4.2.展示或收起过滤条件

    是不是感觉显示的太多了,我们可以通过按钮点击来展开和隐藏部分内容:

    1526836575516

    我们在data中定义变量,记录展开或隐藏的状态:

    1526837203921

    然后在按钮绑定点击事件,以改变show的取值:

    1526837300139

    在展示规格时,对show进行判断:

    1528416266890

    OK!

    4.过滤条件的筛选

    当我们点击页面的过滤项,要做哪些事情?

    • 把过滤条件保存在search对象中(watch监控到search变化后就会发送到后台)
    • 在页面顶部展示已选择的过滤项
    • 把商品分类展示到顶部面包屑

    4.1.保存过滤项

    4.1.1.定义属性

    我们把已选择的过滤项保存在search中:

    1526902381310

    要注意,在created构造函数中会对search进行初始化,所以要在构造函数中对filter进行初始化:

    1526902467385

    search.filter是一个对象,结构:

    {
        "过滤项名":"过滤项值"
    }
    

    4.1.2.绑定点击事件

    给所有的过滤项绑定点击事件:

    1526902638566

    要注意,点击事件传2个参数:

    • k:过滤项的key
    • option:当前过滤项对象

    在点击事件中,保存过滤项到selectedFilter

    selectFilter(k, o){
        const obj = {};
        Object.assign(obj, this.search);
        if(k === 'cid3' || k === 'brandId'){
            o = o.id;
        }
        obj.filter[k] = o;
        this.search = obj;
    }
    

    另外,这里search对象中嵌套了filter对象,请求参数格式化时需要进行特殊处理,修改common.js中的一段代码:

    1530442052516

    我们刷新页面,点击后通过浏览器功能查看search.filter的属性变化:

    1526904752818

    4.2.1.拓展请求对象

    我们需要在请求类:SearchRequest中添加属性,接收过滤属性。过滤属性都是键值对格式,但是key不确定,所以用一个map来接收即可。

    1526910290497

    4.2.2.添加过滤条件

    目前,我们的基本查询是这样的:

        private QueryBuilder buildBasicQuery(SearchRequest request) {
            BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
            // 基本查询条件
            queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
            // 过滤条件构建器
            BoolQueryBuilder filterQueryBuilder = QueryBuilders.boolQuery();
            // 整理过滤条件
            Map<String, String> filter = request.getFilter();
            for (Map.Entry<String, String> entry : filter.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                // 商品分类和品牌要特殊处理
                if (key != "cid3" && key != "brandId") {
                    key = "specs." + key + ".keyword";
                }
                // 字符串类型,进行term查询
                filterQueryBuilder.must(QueryBuilders.termQuery(key, value));
            }
            // 添加过滤条件
            queryBuilder.filter(filterQueryBuilder);
            return queryBuilder;
        }
    

    总结

    页面过滤部分功能未能实现,点击品牌分类无法查询。

    相关文章

      网友评论

          本文标题:乐优商城学习笔记十七-搜索过滤(二)

          本文链接:https://www.haomeiwen.com/subject/dztlgqtx.html