今日目标:
(1)实现品优购搜索结果高亮显示功能
(2)理解品优购搜索的业务规则和实现思路
(3)完成查询分类列表的功能
(4)完成缓存品牌和规格数据的功能
(5)完成显示品牌和规格数据的功能
(6)完成过滤条件构建的功能
(7)完成过滤查询
目录
1、搜索结果高亮显示
1.1 后端(search-service)
1.2 前端(search-web)
2、搜索业务规则分析
2.1 需求分析
2.2 实现思路
3、查询分类列表
3.1 服务层实现(search-service)
3.2 前端
4、缓存规格和品牌数据
4.1 需求分析
4.2 将分类数据放入缓存
4.2 缓存规格和品牌数据
4.3 为什么放在findByParentId和findPage中
5、展示品牌和规格数据
5.1 后端
5.2 前端
6、过滤条件构建
6.1 需求分析
6.2 新增搜索条件到searchMap中
6.3 撤销搜索条件
7、过滤查询
7.1 服务层实现(search-service)
(1)修改SearchServiceImpl中的search方法
@Override
public Map search(Map searchMap) {
// 构造高亮查询对象
HighlightQuery query = new SimpleHighlightQuery();
// 创建高亮查询设置对象,并设置要高亮显示的域
HighlightOptions highlightOptions = new HighlightOptions();
/*
* 设置高亮相关属性
* addField:设置需要高亮显示的域,可以设置多个,获取高亮数据时通过下标获取
* setSimplePrefix:设置高亮前缀
* setSimplePostfix:设置高亮后缀
*/
highlightOptions.addField("item_title");
highlightOptions.setSimplePrefix("");
highlightOptions.setSimplePostfix("");
// 将高亮设置到查询对象中
query.setHighlightOptions(highlightOptions);
//按照关键字查询
Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
query.addCriteria(criteria);
// 执行查询,注意:这里面的结果集中的getContent返回的结果集中,并没有带上高亮,需要自行处理
HighlightPage pageInfo = solrTemplate.queryForHighlightPage(query, TbItem.class);
// 从结果集中取出数据,最先遍历高亮结果入口集
for (HighlightEntry entry : pageInfo.getHighlighted()) {
// 健壮性判断
if (entry.getHighlights().size() > 0 && entry.getHighlights().get(0).getSnipplets().size() > 0) {
// 通过高亮域下标从高亮结果入口集合中再获取高亮数据
String title = entry.getHighlights().get(0).getSnipplets().get(0);
// 从entry中获取当前遍历到的item,并将高亮数据设置到标题
TbItem item = entry.getEntity();
item.setTitle(title);
}
}
// 返回结果
Map resultMap = new HashMap();
resultMap.put("rows", pageInfo.getContent());
return resultMap;
}
(2)效果
原因:因为angular js的安全机制,为了防止html攻击,所以我们还需要修改前端,如果是传统的jsp的话,这里其实就已经完成了页面高亮显示了。
(1)解决html原样输出
我们测试后发现高亮显示的html代码原样输出,这是angularJS为了防止html攻击采取的安全机制。我们如何在页面上显示html的结果呢?我们会用到$sce服务的trustAsHtml方法来实现转换。
(2)修改base.js,加入过滤器,并注入$sce服务
// 自定义模块
var app = angular.module('pinyougou', []);
// 定义过滤器
app.filter('trustHtml',['$sce',function ($sce) {
// data为要过滤的内容
return function (data) {
// 使用$sce的trustAsHtml方法信任该html内容
return $sce.trustAsHtml(data);
}
}]);
(3)页面修改绑定变量
(4)效果
我们今天要完成的目标是在关键字搜索的基础上添加面板搜索功能。
面板上有商品分类、品牌、各种规格和价格区间等条件
业务规则:
- 当用户输入关键字搜索后,除了显示列表结果外,还应该显示通过这个关键字搜索到的记录都有哪些商品分类。
- 根据第一个商品分类查询对应的模板,根据模板查询出品牌列表
- 根据第一个商品分类查询对应的模板,根据模板查询出规格列表
- 当用户点击搜索面板的商品分类时,显示按照这个关键字查询结果的基础上,筛选此分类的结果。
- 当用户点击搜索面板的品牌时,显示在以上结果的基础上,筛选此品牌的结果
- 当用户点击搜索面板的规格时,显示在以上结果的基础上,筛选此规格的结果
- 当用户点击价格区间时,显示在以上结果的基础上,按价格进行筛选的结果
- 当用户点击搜索面板的相应条件时,隐藏已点击的条件。
- 搜索面板的商品分类需要使用Spring Data Solr的分组查询来实现
- 为了能够提高查询速度,我们需要把查询面板的品牌、规格数据提前放入redis
- 查询条件的构建、面板的隐藏需要使用angularJS来实现
- 后端的分类、品牌、规格、价格区间查询需要使用过滤查询来实现
(1)在ItemSearchServiceImpl新增方法
/**
* 查询分类列表
*
* @param searchMap 查询条件
* @return java.util.List
*/
private List searchCategoryList(Map searchMap) {
// 创建查询对象
Query query = new SimpleQuery("*:*");
// 按item_keywords查询
Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
query.addCriteria(criteria);
/*
* 创建分组选项对象
* addGroupByField:设置分组的域
*/
GroupOptions groupOptions = new GroupOptions();
groupOptions.addGroupByField("item_category");
// 将分组选项设置到查询对象中
query.setGroupOptions(groupOptions);
// 执行查询
GroupPage pageInfo = solrTemplate.queryForGroupPage(query, TbItem.class);
// 从结果集中获取数据,参数为分组域中的一个
GroupResult groupResult = pageInfo.getGroupResult("item_category");
// 获取分组页对象
Page> groupEntries = groupResult.getGroupEntries();
// 从分组页对象中获取分组入口集合
List> content = groupEntries.getContent();
// 遍历集合获取分组数据,并设置到返回结果集合中
List resultList = new ArrayList<>();
for (GroupEntry entry : content) {
resultList.add(entry.getGroupValue());
}
return resultList;
}
(2)在search方法中调用
(1)在页面的分类显示处绑定变量
(2)效果
将商品分类数据、品牌数据、和规格数据都放入Redis存储。
- 当用户进入运营商后台的商品分类页面时,将商品分类数据放入缓存(Hash)。以分类名称作为key ,以模板ID作为值
- 当用户进入运营商后台的模板管理页面时,分别将品牌数据和规格数据放入缓存(Hash)。以模板ID作为key,以品牌列表和规格列表作为值。
在ItemCatServiceImpl中添加私有方法(sellergoods-service),并在findByParentId中执行该方法
/**
* 将分类列表放入缓存
*/
private void saveToRedis() {
// 查询全部分类
List itemCatList = findAll();
// 遍历列表
for (TbItemCat itemCat : itemCatList) {
// 放入缓存,大键:itemCat 小键:分类名称
redisTemplate.boundHashOps("itemCat").put(itemCat.getName(), itemCat.getTypeId());
}
}
在TypeTemplateServiceImpl中新增方法(sellergoods-service),在findPage中执行
/**
* 将品牌和规格数据放入缓存
*/
private void saveToRedis() {
// 查询全部的模板数据
List typeTemplateList = findAll();
// 遍历
for (TbTypeTemplate template : typeTemplateList) {
// 获取其品牌数据, 并放入缓存
List
(1)首先我们为什么要将这些数据放入缓存?因为我们在前端的搜索页面会用到这些数据,而且前端访问的压力是很大的,所以我们需要将这些数据放入缓存,减轻数据库的压力
(2)为什么放在这两个方法中?这两个方法是在运营商后台调用的,访问压力相较与前端来说,访问的频率要小得多;再一个就是关于更新缓存的问题,在增删改的时候,我们都需要对缓存进行更新。按一般的思路,我们可以将更新缓存的操作放入增删改方法中,这我们放入这两个方法分原因是,每当我们进行了增删改查操作过后,我们都会调用这两个方法,重新加载数据到运营商后台中,意思就是每次数据改动这个方法都会被执行,所以我们将方法放在这两个方法中,只要数据更新,缓存就会被刷新,这样就减少一定的代码书写,当然也可以按照正常的把更新缓存操作放入增删改方法中。
(1)服务层实现(search-service),在ItemSearchServiceImpl中新增方法
/**
* 通过分类名称获取品牌和规格列表
*
* @param category 分类名称
* @return java.util.Map
*/
private Map searchBrandAndSpec(String category) {
// 使用redis查询分类id
Long categoryId = (Long) redisTemplate.boundHashOps("itemCat").get(category);
// 通过分类id从redis中查询品牌列表和规格列表
List brandList = (List) redisTemplate.boundHashOps("brandList").get(category);
List specList = (List) redisTemplate.boundHashOps("specList").get(category);
// 将数据放入返回结果中
Map resultMap = new HashMap<>();
resultMap.put("brandList",brandList);
resultMap.put("specList",specList);
return resultMap;
}
(2)在search方法中进行调用,并将返回的数据保存到返回结果集中
(1)页面绑定变量
(2)效果
点击搜索面板上的分类、品牌和规格,实现查询条件的构建。查询条件以面包屑的形式显示。
当面包屑显示分类、品牌和规格时,要同时隐藏搜索面板对应的区域。
用户可以点击面包屑上的X 撤销查询条件。撤销后显示搜索面包相应的区域。
(1)在searchController.js中定义初始化查询条件变量
// 初始化搜索对象,包括分类名称、品牌、规格选项
$scope.searchMap = {'keywords':'','category':'','brand':'','spec':{}};
(2)在searchController.js中新增方法
// 添加搜索选项
$scope.addSearchOptions = function (key, value) {
// 判断key值
if (key == 'category' || key == 'brand') {// 分类名称和品牌
// 将查询条件值放入对应的key
$scope.searchMap[key] = value;
} else {// 规格选项
$scope.searchMap.spec[key] = value;
}
}
(3)修改页面,绑定单击事件
(4)绑定面包屑导航栏的变量
(5)效果
(1)在searchController.js中新增方法
// 移除搜索选项
$scope.removeSearchOptions = function (key) {
// 判断key值
if (key == 'category' || key == 'brand') {// 分类名称和品牌
// 设置为空字符串
$scope.searchMap[key] = '';
} else {// 规格选项
// 使用delete关键字移除该key
delete $scope.searchMap.spec[key];
}
}
(2)页面绑定单击事件
(3)在执行添加查询条件和移除查询条件的方法中,调用搜索方法
(1)新增构建过滤查询条件的方法
/**
* 根据搜索条件集设置查询过滤条件,并将查询过滤条件设置给查询对象
*
* @param searchMap 搜索条件集
* @param query 查询对象
* @return org.springframework.data.solr.core.query.Query
*/
private void setFilterOptions(Map searchMap, Query query) {
// 设置分类过滤查询
if (!"".equals(searchMap.get("category"))) {// 该条件不为空串
// 创建查询过滤对象
FilterQuery filterQuery = new SimpleFilterQuery();
// 设置过滤域,并设置条件
Criteria criteria = new Criteria("item_category").is(searchMap.get("category"));
// 将条件设置会查询过滤对象
filterQuery.addCriteria(criteria);
// 设置到查询对象中
query.addFilterQuery(filterQuery);
}
// 设置品牌过滤查询
if (!"".equals(searchMap.get("brand"))) {// 该条件不为空串
// 创建查询过滤对象
FilterQuery filterQuery = new SimpleFilterQuery();
// 设置过滤域,并设置条件
Criteria criteria = new Criteria("item_brand").is(searchMap.get("brand"));
// 将条件设置会查询过滤对象
filterQuery.addCriteria(criteria);
// 设置到查询对象中
query.addFilterQuery(filterQuery);
}
// 设置规格过滤查询
if (searchMap.get("spec") != null) {// 该条件不为空
// 设置过滤域,并设置条件
Map map = (Map) searchMap.get("spec");
for (String key : map.keySet()) {
Criteria criteria = new Criteria("item_spec_" + key).is(map.get(key));
// 通过过滤条件创建查询过滤对象
FilterQuery filterQuery = new SimpleFilterQuery(criteria);
// 设置到查询对象中
query.addFilterQuery(filterQuery);
}
}
}
(2)选择商品分类的时候,动态加载品牌和规格选项,修改服务层实现的search方法