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

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;
    }

总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值