目录
四、过滤条件的筛选
4.1 保存过滤项
4.1.1 定义属性
把已选择的过滤项保存在search中:
需要注意的是,在created构造函数中会对search进行初始化,所以要在构造函数中对filter进行初始化:
search.filter是一个对象,结构如下:
{
"过滤项名":"过滤项值"
}
4.1.2 绑定点击事件
要注意,点击事件传2个参数:
-
k:过滤项的key
-
option:当前过滤项对象
在点击事件中,保存过滤项到selectedFilter
:
另外,这里search对象中嵌套了filter对象,请求参数格式化时需要进行特殊处理,修改common.js中的一段代码:
刷新页面,点击后查看search.filter
的属性变化:
并且,此时浏览器地址也发生了变化:
网络请求也正常发出:
4.2 后台过滤条件
4.2.1 拓展请求对象
需要在请求类:SearchRequest
中添加属性,接收过滤属性。过滤属性都是键值对格式,但是key不确定,所以用一个map来接收即可 。
4.2.2 添加过滤条件
目前,基本查询是这样的:
现在,要把页面传递的过滤条件也传入进去。
因此不能在使用普通的查询,而是要用到BooleanQuery,基本结构是这样的:
GET /leyou/_search
{
"query":{
"bool":{
"must":{ "match": { "title": "小米手机",operator:"and"}},
"filter":{
"range":{"price":{"gt":2000.00,"lt":3800.00}}
}
}
}
}
bool查询可以
把各种其它查询通过must
(与)、must_not
(非)、should
(或)的方式进行组合 。
所以要对原来的基本查询进行构造:
/**
* 构建带过滤条件的基本查询
* @param searchRequest
* @return
*/
private QueryBuilder buildBasicQueryWithFilter(SearchRequest searchRequest) {
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
//基本查询条件
queryBuilder.must(QueryBuilders.matchQuery("all",searchRequest.getKey()).operator(Operator.AND));
//过滤条件构造器
BoolQueryBuilder filterQueryBuilder = QueryBuilders.boolQuery();
//整理过滤条件
Map<String,String> filter = searchRequest.getFilter();
for (Map.Entry<String,String> entry : filter.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
String regex = "^(\\d+\\.?\\d*)-(\\d+\\.?\\d*)$";
if (StringUtils.isNotBlank(key)) {
if (value.matches(regex)) {
Double[] nums = NumberUtils.searchNumber(value, regex);
//数值类型进行范围查询 lt:小于 gte:大于等于
filterQueryBuilder.must(QueryBuilders.rangeQuery("specs." + key).gte(nums[0]).lt(nums[1]));
} else {
//商品分类和品牌要特殊处理
if (key != "cid3" && key != "brandId") {
key = "specs." + key + ".keyword";
}
//字符串类型,进行term查询
filterQueryBuilder.must(QueryBuilders.termQuery(key, value));
}
} else {
break;
}
}
//添加过滤条件
queryBuilder.filter(filterQueryBuilder);
return queryBuilder;
}
range
查询允许以下字符:
操作符 说明 gt 大于 gte 大于等于 lt 小于 lte 小于等于
4.3 页面测试
先不点击过滤条件,直接搜索手机:
然后点击点击一个过滤条件:
查询结果如下:
五、页面展示选择的过滤项
5.1 商品分类面包屑
当用户选择一个商品分类以后,应该在过滤模块的上方展示一个面包屑,把三级商品分类都显示出来。
用户选择的商品分类就存放在search.filter
中,但是里面只有第三级分类的id:cid3
需要根据它查询出所有三级分类的id及名称
5.1.1 提供查询分类接口
CategoryController
/**
* 根据分类id集合查询分类名称
* @param id
* @return
*/
@GetMapping("all/level/{cid3}")
public ResponseEntity<List<Category>> queryAllCategoryLevelByCid3(@PathVariable("cid3")Long id){
List<Category> list = categoryService.queryAllCategoryLevelByCid3(id);
if (list == null || list.size() < 1){
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}else {
return ResponseEntity.ok(list);
}
}
CategoryService
CategoryServiceImpl
/**
* 根据cid3查询其所有层级分类
* @param id
* @return
*/
@Override
public List<Category> queryAllCategoryLevelByCid3(Long id) {
List<Category> categoryList = new ArrayList<>();
Category category = this.categoryMapper.selectByPrimaryKey(id);
while (category.getParentId() != 0){
categoryList.add(category);
category = this.categoryMapper.selectByPrimaryKey(category.getParentId());
}
categoryList.add(category);
return categoryList;
}
接口测试
5.1.2 页面展示面包屑
后台提供了接口,下面的问题是,在哪里去查询接口?
如果是用户点击以后,那么页面就会重新发起请求,页面刷新,那么渲染的结果就没了。
因此,应该是在页面重新加载完毕后,此时因为过滤条件中加入了商品分类的条件,所以查询的结果中只有1个分类。
判断商品分类是否只有1个,如果是,则查询三级商品分类,添加到面包屑即可。
渲染:
刷新页面:
5.2 其它过滤项
所有已选择过滤项都保存在search.filter
中,因此在页面遍历并展示即可。
但这里有个问题,filter中数据的格式:
基本有四类数据:
-
商品分类:这个不需要展示,分类展示在面包屑位置
-
品牌:这个要展示,但是其key和值不合适,不能显示一个id在页面。需要找到其name值
-
数值类型规格:这个展示的时候,需要把单位查询出来
-
非数值类型规格:这个直接展示其值即可
5.2.1 页面处理
<!--已选择过滤项-->
<ul class="tags-choose">
<li class="tag" v-for="(v,k) in search.filter" v-if="k !== 'cid3'" :key="k">
{{k === 'brandId' ? '品牌' : k}}:<span style="color:red;">{{getFilterValue(k,v)}}</span>
<span></span>
<i class="sui-icon icon-tb-close"></i>
</li>
</ul>
-
判断如果
k === 'cid3'
说明是商品分类,直接忽略 -
判断
k === 'brandId'
说明是品牌,页面显示品牌,其它规格则直接显示k
的值 -
值的处理比较复杂,我们用一个方法
getFilterValue(k,v)
来处理,调用时把k
和v
都传递
5.2.2 测试
5.3 隐藏已经选择的过滤项
现在,我们已经实现了已选择过滤项的展示,但是你会发现一个问题:
已经选择的过滤项,在过滤列表中依然存在:
这些已经选择的过滤项,应该从列表中移除。
用户选择的项保存在search.filter
中:
可以通过编写一个计算属性,把filters中的 已经被选择的key过滤掉,而且当只剩下一个可选项时也过滤掉。因为如果只剩下一个可选项就没有展示的必要了。
刷新页面:
六、取消过滤项
可以够看到,每个过滤项后面都有一个小叉,当点击后,应该取消对应条件的过滤。
思路非常简单:
-
给小叉绑定点击事件
-
点击后把过滤项从
search.filter
中移除,页面会自动刷新,OK
绑定点击事件
绑定点击事件时,把k传递过去,方便删除
删除过滤项
七、优化
7.1 价格筛选
7.1.1 前端页面
价格筛选的范围直接指定
由计算属性构造价格区间
然后进行遍历,并添加点击事件:
点击事件如下:
给fiter过滤参数添加价格过滤字段
刷新页面:
7.1.2 后端接口
因为搜索过滤是在buildBasicQueryWithFilter函数中进行的,所以在构建过程中加上价格过滤即可。
因为传入后台的是字符串,所以需要处理一下:
- 先判断是否包含”元以上“,即为最后一个价格区间
- 如果不是,去掉最后的”元“,再以”-“分割字符串,得到区间,然后进行范围查询
- 如果是,那么去掉最后的”元以上“,然后搜索大于最低价钱的商品
处理完成后构造范围查询:
数据库中价格的单位是分,所以在查询时要乘100
7.2 图片延迟加载
使用vue插件vue-layzed。
CDN引入,然后进行配置。
将商品渲染时img的src改为v-lazy即可:
需要准备一下图片加载中和加载失败的CSS:
加载失败的图片:
加载中的图片:
有关vue-lazyload的更多内容,请参考《vue-layload图片延迟加载》
7.3 搜索系统需要优化的点(待完成)
-
查询规格参数部分可以添加缓存
-
聚合计算interval变化频率极低,所以可以设计为定时任务计算(周期为天),然后缓存起来。
-
商品图片应该采用缩略图,减少流量,提高页面加载速度
-
图片还可以采用CDN服务器
-
sku信息应该在页面异步加载,而不是放到索引库