乐优商城(08)--搜索过滤

本文详细介绍了乐优商城的搜索过滤功能实现,包括过滤功能分析、分类和品牌过滤的生成、规格参数过滤的创建,以及过滤条件的筛选和页面展示。重点讲述了如何聚合商品分类和品牌,扩展返回结果,页面数据的渲染,以及如何处理规格参数过滤和页面展示选择的过滤项。
摘要由CSDN通过智能技术生成

乐优商城(08)–搜索过滤

一、过滤功能分析

首先看下页面要实现的效果:

在这里插入图片描述

整个过滤部分有3块:

  • 顶部的导航,已经选择的过滤条件展示:

    • 商品分类面包屑,根据用户选择的商品分类变化
    • 其它已选择过滤参数
  • 过滤条件展示,又包含3部分

    • 商品分类展示
    • 品牌展示
    • 其它规格参数
  • 展开或收起的过滤条件的按钮

顶部导航要展示的内容跟用户选择的过滤条件有关。

  • 比如用户选择了某个商品分类,则面包屑中才会展示具体的分类

  • 比如用户选择了某个品牌,列表中才会有品牌信息。

所以,这部分需要依赖第二部分:过滤条件的展示和选择。

展开或收起的按钮是否显示,取决于过滤条件有多少,如果很少,那么就没必要展示。所以也是跟第二部分的过滤条件有关。

所以重中之重就是第二部分:过滤条件展示

二、生成分类和品牌过滤

在数据库中已经有所有的分类和品牌信息,但是在这个位置,不是把所有的分类和品牌信息都展示出来。因为用户搜索的条件会对商品进行过滤,而在搜索结果中,不一定包含所有的分类和品牌,直接展示出所有商品分类,让用户选择显然是不合适的。无论是分类信息,还是品牌信息,都应该从搜索的结果商品中进行聚合得到。

2.1、扩展返回的结果

原来,返回的结果是PageResult对象,里面只有total、totalPage、items3个属性。但是现在要对商品分类和品牌进行聚合,数据显然不够用,所以需要对返回的结果进行扩展,添加分类和品牌的数据。

那么问题来了:以什么格式返回呢?

以京东搜索页面为例:

在这里插入图片描述

分类:页面显示了分类名称,但背后肯定要保存id信息。所以至少要有id和name

品牌:页面展示的有logo,有文字,当然肯定有id,基本上是品牌的完整数据

需要新建一个类,继承PageResult,然后扩展两个新的属性:分类集合和品牌集合。

package com.leyou.search.vo;

import com.leyou.common.pojo.PageResult;
import com.leyou.item.pojo.Brand;
import com.leyou.search.pojo.Goods;

import java.util.List;
import java.util.Map;

/**
 * 继承 PageResult 并扩展新的字段
 */
public class SearchResult extends PageResult<Goods> {

    private List<Map<String, Object>> categories;       //分类数据
    private List<Brand> brands;                         //品牌数据
    private List<Map<String, Object>> specs;            //参数数据

    public SearchResult() {
    }

    public SearchResult(List<Map<String, Object>> categories, List<Brand> brands,List<Map<String, Object>> specs) {
        this.categories = categories;
        this.brands = brands;
        this.specs = specs;
    }

    public SearchResult(List<Goods> items, Long total, List<Map<String, Object>> categories, List<Brand> brands,List<Map<String, Object>> specs) {
        super(total,items);
        this.categories = categories;
        this.brands = brands;
        this.specs = specs;
    }

    public SearchResult(Long total,Integer totalPage,List<Goods> items, List<Map<String, Object>> categories, List<Brand> brands,List<Map<String, Object>> specs) {
        super(total,totalPage,items);
        this.categories = categories;
        this.brands = brands;
        this.specs = specs;
    }

    public List<Map<String, Object>> getCategories() {
        return categories;
    }

    public void setCategories(List<Map<String, Object>> categories) {
        this.categories = categories;
    }

    public List<Brand> getBrands() {
        return brands;
    }

    public void setBrands(List<Brand> brands) {
        this.brands = brands;
    }

    public List<Map<String, Object>> getSpecs() {
        return specs;
    }

    public void setSpecs(List<Map<String, Object>> specs) {
        this.specs = specs;
    }
}

2.2、聚合商品分类和品牌

修改搜索的业务逻辑,对分类和品牌聚合。

因为索引库中只有id,所以根据id聚合,然后再根据id去查询完整数据。

所以,商品微服务需要提供一个接口:根据品牌id集合,批量查询品牌。

2.2.1、修改查询品牌接口

BrandController

/**
 * 根据品牌id查询品牌集合
 * @param ids
 * @return
 */
@GetMapping("{ids}")
public ResponseEntity<List<Brand>> queryBrandByIds(@PathVariable("ids") List<Long> ids){
    List<Brand> brands = this.brandService.queryBrandByIds(ids);
    if (CollectionUtils.isEmpty(brands)){
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(brands);
}

BrandService

/**
 * 根据品牌id查询品牌集合
 * @param ids
 * @return
 */
List<Brand> queryBrandByIds(List<Long> ids);

实现类:

/**
 * 根据品牌id查询品牌集合
 *
 * @param ids
 * @return
 */
@Override
public List<Brand> queryBrandByIds(List<Long> ids) {
    return this.brandMapper.selectByIdList(ids);
}

BrandMapper

public interface BrandMapper extends Mapper<Brand>, SelectByIdListMapper<Brand,Long> {}

BrandApi

/**
 * 根据品牌id查询品牌集合
 * @param ids
 * @return
 */
@GetMapping("{ids}")
List<Brand> queryBrandByIds(@PathVariable("ids") List<Long> ids);
2.2.2、搜索功能改造
/**
 * 搜索商品
 *
 * @param searchRequest
 * @return
 */
@Override
public SearchResult search(SearchRequest searchRequest) {
    String key = searchRequest.getKey();
    //查询条件如果为空,不让查询
    if (StringUtils.isBlank(key)) return null;
    //创建自定义查询条件构造器
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    //将初步查询的结果进行提取
    QueryBuilder basicQuery = QueryBuilders.matchQuery("all", key).operator(Operator.AND);
    //对结果进行过滤,只获取"id","skus","subTitle"这三个字段的数据并过滤空数据
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"},null));
    //分页条件
    int page = searchRequest.getPage();
    int size = searchRequest.getSize();
    //将分页条件加入查询器
    queryBuilder.withPageable(PageRequest.of(page - 1,size));
    String categoryAggName = "categories";
    String brandAggName = "brands";
    //添加聚合条件  聚合字段为cid3的数据并命名为categories
    queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
    //添加聚合条件  聚合字段为brandId的数据并命名为brands
    queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

    //排序
    String sortBy = searchRequest.getSortBy();  //得到排序字段
    Boolean descending = searchRequest.getDescending(); //是否降序
    //如果不为空  则进行排序
    if (StringUtils.isNotBlank(sortBy)){
        queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(descending ? SortOrder.DESC : SortOrder.ASC));
    }
    //执行查询  获取数据结果  强转为AggregatedPage<Goods>
    AggregatedPage<Goods> pageInfo = (AggregatedPage<Goods>)this.goodsRepository.search(queryBuilder.build());
    //解析聚合结果
    List<Map<String, Object>> categories = getCategoryAggResult(pageInfo.getAggregation(categoryAggName));
    List<Brand> brands = getBrandAggResult(pageInfo.getAggregation(brandAggName));
    // 封装结果并返回
    return new SearchResult(pageInfo.getTotalElements(),pageInfo.getTotalPages(),pageInfo.getContent(),categories,brands);
}

对商品分类和品牌的聚合结果的解析提取为一个单独的函数:

/**
 * 解析品牌聚合结果集,即根据id获取品牌对象
 * @param aggregation
 * @return
 */
private List<Brand> getBrandAggResult(Aggregation aggregation) {
    Terms terms = (Terms) aggregation;
    List<Long> bids = new ArrayList<>();
    for (Terms.Bucket bucket : terms.getBuckets()) {
        bids.add(bucket.getKeyAsNumber().longValue());
    }
    //根据品牌id查询品牌
    return this.brandClient.queryBrandByIds(bids);
}

/**
 * 解析分类聚合结果集
 * @param aggregation
 * @return
 */
private List<Map<String, Object>> getCategoryAggResult(Aggregation aggregation) {
    Terms terms = (Terms) aggregation;
    return terms.getBuckets().stream().map(bucket -> {
        //初始化一个map
        Map<String, Object> map = new HashMap<>();
        //获取每个桶结果的分类id
        Long id = bucket.getKeyAsNumber().longValue();
        //根据分类id查询出分类名称
        List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(id));
        //将结果传入map中
        map.put("id",id);
        map.put("name",names.get(0));
        return map;
    }).collect(Collectors.toList());
}

数据响应:

在这里插入图片描述

2.3、页面渲染数据

来看下页面的展示效果:

在这里插入图片描述

虽然分类、品牌内容都不太一样,但是结构相似,都是key和value的结构。

而且页面结构也极为类似:

在这里插入图片描述

所以,可以把所有的过滤条件放入一个数组中,然后在页面利用v-for遍历一次生成。

其基本结构是这样的:

[
    {
        k:"过滤字段名",
        options:[{/*过滤字段值对象*/},{/*过滤字段值对象*/}]
    }
]

先在data中定义数组:filters,等待组装过滤参数:

在这里插入图片描述

然后在查询搜索结果的回调函数中,对过滤参数进行封装:

在这里插入图片描述

然后刷新页面,通过浏览器插件VUE,查看封装的结果。

数据渲染

仔细观察,可以注意到,虽然页面元素是一样的,但是品牌会比其它搜索条件多出一些样式,因为品牌是以图片展示。需要进行特殊处理。数据展示是一致的,采用v-for处理:

<div class="type-wrap" v-for="(f,index) in filters" :key="index" v-if="f.k !== '品牌'"
     v-show="index < 5||show">
    <div class="fl key">{{f.k}}</div>
    <div class="fl value">
        <ul class="type-list">
            <li v-for="(o,j) in f.options" :key="j" @click="selectFilter(f.k,o)">
                <a>{{o.name}}</a>
            </li>
        </ul>
    </div>
    <div class="fl ext"></div>
</div>
<div class="type-wrap logo" v-else>
    <div class="fl key brand">{{f.k}}</div>
    <div class="value logos">
        <ul class="logo-list">
            <li v-for="(o,j) in f.options" :key="j" v-if="o.image" @click="selectFilter(f.k,o)"><img
                    :src="o.image"/></li>
            <li style="text-align: center" v-else @click="selectFilter(f.k,o)">
                <a href="#" style="line-height: 30px; font-size: 12px">{{o.name}}</a>
            </li>
        </ul>
    </div>
    <div class="fl ext">
        <a href="javascript:void(0);" class="sui-btn">多选</a>
    </div>
</div>

三、生成规格参数过滤

3.1、分析

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

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

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

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

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

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

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

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

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

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

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

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

直接看页面效果:

在这里插入图片描述

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

3.2、实现

总结一下思路:

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

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

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

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

在这里插入图片描述

构造函数如下:

public SearchResult(Long total,Integer totalPage,List<Goods> items, List<Map<String, Object>> categories, List<Brand> brands,List<Map<String, Object>> specs) {
    super(total,totalPage,items);
    this.categories = categories;
    this.brands = brands;
    this.specs = specs;
}
3.2.2.判断是否需要聚合

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

在这里插入图片描述

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

/**
 * 解析参数聚合结果集
 * @param cid
 * @param basicQuery
 * @return
 */
private List<Map<String, Object>> getParamAggResult(Long cid, QueryBuilder basicQuery) {
    //创建自定义查询条件构造器
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    //基础查询即查询的全部数据
    queryBuilder.withQuery(basicQuery);
    //查询出要聚合的规格参数
    List<SpecParam> params = this.specificationClient.queryParams(null, cid, null, true);
    //将参数进行映射,添加到聚合结果集中
    params.forEach(param ->{
        queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs."+param.getName()+".keyword"));
    });
    //对结果进行过滤,排除空数据
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
    //执行查询,获取结果
    AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>)this.goodsRepository.search(queryBuilder.build());
    //初始化一个集合,用于存结果集
    List<Map<String, Object>> result = new ArrayList<>();
    // 解析聚合查询的结果集
    Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
    for (Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()) {
        Map<String, Object> map = new HashMap<>();
        // 放入规格参数名
        map.put("k", entry.getKey());
        // 收集规格参数值
        List<Object> options = new ArrayList<>();
        // 解析每个聚合
        Terms terms = (Terms)entry.getValue();
        // 遍历每个聚合中桶,把桶中key放入收集规格参数的集合中
        terms.getBuckets().forEach(bucket -> options.add(bucket.getKeyAsString()));
        map.put("options", options);
        result.add(map);
    }
    return result;
}

测试结果

在这里插入图片描述

3.3、页面渲染

3.3.1、渲染规格过滤条件

首先把后台传递过来的specs添加到filters数组:

要注意:分类、品牌的option选项是对象,里面有name属性,而specs中的option是简单的字符串,所以需要进行封装,变为相同的结构:

在这里插入图片描述

最后的结果:

在这里插入图片描述

3.3.2、展示或收起过滤条件

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

默认显示五个:分类、品牌、后置摄像头、CPU核数、CPU品牌

四、过滤条件的筛选

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

4.1、保存过滤项

定义属性

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

在这里插入图片描述

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

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

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

在这里插入图片描述

绑定点击事件

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

在这里插入图片描述

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

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

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

并且,此时浏览器地址也发生了变化:

在这里插入图片描述

网络请求也正常发出:

在这里插入图片描述

4.2、后台添加过滤条件

拓展请求对象

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

在这里插入图片描述

不要忘记get和set

添加过滤条件

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

//将初步查询的结果进行提取
QueryBuilder basicQuery = QueryBuilders.matchQuery("all", key).operator(Operator.AND);

现在,要把页面传递的过滤条件也传入进去。

因此不能在使用普通的查询,而是要用到BooleanQuery,基本结构是这样的:

GET /goods/_search
{
    "query":{
        "bool":{
        	"must":{ "match": { "title": "小米手机",operator:"and"}},
        	"filter":{
                "range":{"price":{"gt":2000.00,"lt":3800.00}}
        	}
        }
    }
}

bool查询可以把各种其它查询通过must(与)、must_not(非)、should(或)的方式进行组合 。

所以要对原来的基本查询进行构造:

/**
 * 构建bool查询构建器
 * @param searchRequest
 * @return
 */
private BoolQueryBuilder buildBoolQueryBuilder(SearchRequest searchRequest) {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    // 添加基本查询条件
    boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND));
    //如果过滤字段为空  则直接返回
    if (CollectionUtils.isEmpty(searchRequest.getFilter())){
        return boolQueryBuilder;
    }
    //遍历过滤map中的每一项
    for (Map.Entry<String, String> entry : searchRequest.getFilter().entrySet()) {
        String key = entry.getKey();
        if (StringUtils.equals("分类",key)){ // 如果是"分类",过滤字段名:cid3
            key = "cid3";
        }else if (StringUtils.equals("品牌",key)){ // 如果过滤条件是"品牌", 过滤的字段名:brandId
            key = "brandId";
        }else { //如果是规格参数名,过滤字段名:specs.key.keyword
            key = "specs." + key + ".keyword";
        }
        // 添加过滤条件
        boolQueryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));
    }
    return boolQueryBuilder;

}

对原来的基本查询进行改造:(SearchService中的search方法)

在这里插入图片描述

在这里插入图片描述

range查询允许以下字符:

操作符说明
gt大于
gte大于等于
lt小于
lte小于等于

页面测试

先不点击过滤条件,直接搜索手机:

在这里插入图片描述

总共181条

接下来,点击一个过滤条件:

在这里插入图片描述

在这里插入图片描述

总共117条

五、页面展示选择的过滤项

5.1 商品分类面包屑

当用户选择一个商品分类以后,应该在过滤模块的上方展示一个面包屑,把三级商品分类名称都显示出来。

在这里插入图片描述

用户选择的商品分类就存放在search.filter中,但是里面只有第三级分类的id:cid3

需要根据它查询出所有三级分类的id及名称

CategoryController

/**
 * 根据3级分类id,查询1~3级的分类
 *
 * @param cid
 * @return
 */
@Override
public List<Category> queryAllByCid3(Long cid) {
    Category c3 = this.categoryMapper.selectByPrimaryKey(cid);
    Category c2 = this.categoryMapper.selectByPrimaryKey(c3.getParentId());
    Category c1 = this.categoryMapper.selectByPrimaryKey(c2.getParentId());
    return Arrays.asList(c1,c2,c3);
}

CategoryService

/**
 * 根据3级分类id,查询1~3级的分类
 * @param cid
 * @return
 */
List<Category> queryAllByCid3(Long cid);

实现类:

/**
 * 根据3级分类id,查询1~3级的分类
 *
 * @param cid
 * @return
 */
@Override
public List<Category> queryAllByCid3(Long cid) {
    Category c3 = this.categoryMapper.selectByPrimaryKey(cid);
    Category c2 = this.categoryMapper.selectByPrimaryKey(c3.getParentId());
    Category c1 = this.categoryMapper.selectByPrimaryKey(c2.getParentId());
    return Arrays.asList(c1,c2,c3);
}

5.2、页面展示面包屑

后台提供了接口,下面的问题是,在哪里去查询接口?

如果是用户点击以后,那么页面就会重新发起请求,页面刷新,那么渲染的结果就没了。

因此,应该是在页面重新加载完毕后,此时因为过滤条件中加入了商品分类的条件,所以查询的结果中只有1个分类。

判断商品分类是否只有1个,如果是,则查询三级商品分类,添加到面包屑即可。

在这里插入图片描述

渲染:

在这里插入图片描述

刷新页面查看:

在这里插入图片描述

5.3、其他过滤项

所有已选择过滤项都保存在search.filter中,因此在页面遍历并展示即可。

但是存在一个问题:

在这里插入图片描述

显示品牌显示的是品牌id,这明显不合适,需要展示其品牌名称

页面处理

。。。
(尝试无果,太菜了。。求大佬告知)

5.4、隐藏已经选择的过滤项

现在,已经实现了已选择过滤项的展示,但是会发现一个问题:已经选择的过滤项,在过滤列表中依然存在:

在这里插入图片描述

这些已经选择的过滤项,应该从列表中移除。

用户选择的项保存在search.filter中:

在这里插入图片描述

可以通过编写一个计算属性,把filters中的 已经被选择的key过滤掉,而且当只剩下一个可选项时也过滤掉。因为如果只剩下一个可选项就没有展示的必要了。

在这里插入图片描述

在这里插入图片描述

刷新页面,选中过滤项

5.5、取消过滤项

可以够看到,每个过滤项后面都有一个小叉,当点击后,应该取消对应条件的过滤。

思路非常简单:

  • 给小叉绑定点击事件
  • 点击后把过滤项从search.filter中移除,页面会自动刷新,OK

绑定点击事件:

在这里插入图片描述

绑定点击事件时,把k传递过去,方便删除

删除过滤项

deleteFilter(k) {
    //从filter中复制属性到obj
    const {...obj} = this.search.filter;
    delete obj[k];
    //添加到search.filter中
    this.search.filter = obj;
}

5.6、价格过滤

页面效果:

在这里插入图片描述

5.6.1、前端页面

在这里插入图片描述

这里绑定两个数据模型,记录用户输入的价格区间:

在这里插入图片描述

此外,有个点击事件searchPrice

在这里插入图片描述

将用户输入的价格区间放进过滤数据模型中,在输入栏输入数据,刷新页面:

在这里插入图片描述

5.6.2、后端实现

因为搜索过滤是在``buildBoolQueryBuilder`函数中进行的,所以在构建过程中加上价格过滤即可。

因为传入后台的是字符串,所以需要处理一下:

  • 如果key是price,那么需要将val以”-“分割字符串,处理为字符串数组
  • 然后将字符串数组的两个数进行大小比较,小的为num1,大的为num2
/**
 * 构建bool查询构建器
 * @param searchRequest
 * @return
 */
private BoolQueryBuilder buildBoolQueryBuilder(SearchRequest searchRequest) {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    // 添加基本查询条件
    boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND));
    //如果过滤字段为空  则直接返回
    if (CollectionUtils.isEmpty(searchRequest.getFilter())){
        return boolQueryBuilder;
    }
    //遍历过滤map中的每一项
    for (Map.Entry<String, String> entry : searchRequest.getFilter().entrySet()) {
        String key = entry.getKey();
        if (StringUtils.equals("price",key)) {
            String value = entry.getValue();
            String[] nums = value.split("-");//分割数组,得到要范围查询的区间
            double num1, num2;//将两个数的数值大小进行比较
            if (Double.parseDouble(nums[0]) > Double.parseDouble(nums[1])) {
                num1 = Double.parseDouble(nums[1]);
                num2 = Double.parseDouble(nums[0]);
            } else {
                num1 = Double.parseDouble(nums[0]);
                num2 = Double.parseDouble(nums[1]);
            }
            //构建范围查询条件
            boolQueryBuilder.must(QueryBuilders.rangeQuery(key).gte(num1 * 100).lt(num2 * 100));
        }else {
            if (StringUtils.equals("分类",key)){ // 如果是"分类",过滤字段名:cid3
                key = "cid3";
            }else if (StringUtils.equals("品牌",key)){ // 如果过滤条件是"品牌", 过滤的字段名:brandId
                key = "brandId";
            } else { //如果是规格参数名,过滤字段名:specs.key.keyword
                key = "specs." + key + ".keyword";
            }
            // 添加过滤条件
            boolQueryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));
        }
    }
    return boolQueryBuilder;

}

注意:数据库中价格的单位是分,所以在查询时要乘100

5.6.3、补充

当点击价格输入栏时,会残留符号,用户体验不够良好,添加点击事件:

在这里插入图片描述

在这里插入图片描述

5.7、图片懒加载

使用vue插件vue-layzed。CDN引入,然后进行配置

在这里插入图片描述

将商品渲染时img的src改为v-lazy即可:

在这里插入图片描述

加载失败图片:

在这里插入图片描述

加载中动图:

在这里插入图片描述

5.8、搜索系统需要优化的点(待完成)

  • 查询规格参数部分可以添加缓存

  • 聚合计算interval变化频率极低,所以可以设计为定时任务计算(周期为天),然后缓存起来。

  • 商品图片应该采用缩略图,减少流量,提高页面加载速度

  • 图片还可以采用CDN服务器

  • sku信息应该在页面异步加载,而不是放到索引库

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值