目标
- 实现品优购价格区间筛选功能
- 实现搜索结果分页功能
- 理解多关键字搜索
- 实现搜索结果排序功能
- 实现隐藏品牌列表功能
- 实现搜索页和首页对接功能
- 完成更新索引库的功能
1. 按价格区间筛选
1. 需求分析
点击搜索面板的价格区间,实现按价格筛选
2. 前端代码
1. 前端控制层
修改search-web的searchController.js 搜索条件的定义
$scope.searchMap = {"keywords":"","category":"","brand":"","spec":{},"price":""};// 搜索对象
修改search-web的searchController.js 添加搜索项和删除搜索项的方法
// 添加搜索项,改变searchMap的值
$scope.addSearchItem = function (key, value) {
if(key==="category" || key==="brand" || key==="price"){
$scope.searchMap[key] = value;
}else{
$scope.searchMap.spec[key] = value;
}
$scope.search();//执行搜索
};
// 撤销搜索项
$scope.removeSearchItem = function (key) {
if(key==="category" || key==="brand" || key==="price"){
$scope.searchMap[key] = "";
}else{
delete $scope.searchMap.spec[key];
}
$scope.search();//执行搜索
}
2. 页面
修改search.html,在标签上调用方法
<div class="type-wrap" ng-if="searchMap.price==''">
<div class="fl key">价格</div>
<div class="fl value">
<ul class="type-list">
<li>
<a ng-click="addSearchItem('price','0-500')">0-500元</a>
</li>
<li>
<a ng-click="addSearchItem('price','500-1000')">500-1000元</a>
</li>
<li>
<a ng-click="addSearchItem('price','1000-1500')">1000-1500元</a>
</li>
<li>
<a ng-click="addSearchItem('price','1500-2000')">1500-2000元</a>
</li>
<li>
<a ng-click="addSearchItem('price','2000-3000')">2000-3000元 </a>
</li>
<li>
<a ng-click="addSearchItem('price','3000-*')">3000元以上</a>
</li>
</ul>
</div>
<div class="fl ext">
</div>
</div>
修改search.html,增加面包屑
<li class="tag" ng-if="searchMap.price!=''" ng-click="removeSearchItem('price')">品牌:{{searchMap.price}}<i class="sui-icon icon-tb-close"></i></li>
2. 后端代码
修改search-service的ItemSearchServiceImpl.java
// 1.5 价格区间过滤
if (!"".equals(searchMap.get("price"))) {
String[] price = ((String) searchMap.get("price")).split("-");
if(!price[0].equals("0")){// 如果最低价格不是0
FilterQuery filterQuery = new SimpleFilterQuery();
Criteria filterCriteria = new Criteria("item_price").greaterThanEqual(price[0]);
filterQuery.addCriteria(filterCriteria);
query.addFilterQuery(filterQuery);
}
if(!"*".equals(price[1])){
FilterQuery filterQuery = new SimpleFilterQuery();
Criteria filterCriteria = new Criteria("item_price").lessThanEqual(price[1]);
filterQuery.addCriteria(filterCriteria);
query.addFilterQuery(filterQuery);
}
}
2. 搜索结果分页
1. 需求分析
在上述功能基础上实现分页查询
思路:
如果总页数小于等于5页,显示全部页码
如果总页数大于5页,
以当前页为中心的5个页码
eg. 8 9 10 11 12
如果当前页页码小于等于3
显示前5页
1 2 3 4 5
如果当前页页码大于总页数-2 eg.100页 当前是99
显示后5页
96 97 98 99 100
2. 后端代码
// 1.6 分页查询
Integer pageNo = Integer.valueOf(searchMap.get("pageNo") + "");//提取页码
if(pageNo==null){
pageNo=1;//默认第一页
}
Integer pageSize = (Integer) searchMap.get("pageSize");// 每页记录数
if(pageSize==null){
pageSize=20; //默认每页20条记录
}
query.setOffset((pageNo-1)*pageSize);//开始索引
query.setRows(pageSize);//每页记录数
// ************** 获取高亮结果集 ***************
// 高亮页对象
// ....
map.put("rows",page.getContent());
map.put("totalPages",page.getTotalPages());//返回总页数
map.put("total",page.getTotalElements());//返回总记录数
return map;
3. 前端代码
1. 构建分页标签
需求:
- 如果需要修改默认页码和每页的记录数,可以修改searchController.js的searchMap,为搜索对象添加属性
$scope.searchMap = {"keywords":"","category":"","brand":"","spec":{},"price":"","pageNo":1,"pageSize":40};// 搜索对象
- 修改searchController.js 实现页码的构建
// 分页标签的构建
buildPageLabel = function(){
$scope.pageLabel = [];//新增分页栏属性
var maxPageNo = $scope.resultMap.totalPages;// 得到最后页码
var firstPage = 1; // 显示的开始页码
var lastPage = maxPageNo;// 显示的截止页码
if($scope.resultMap.totalPages>5){//如果总页数大于5页
if($scope.searchMap.pageNo<=3){ // 当前页小于等于3
lastPage = 5;//前5页
}else if($scope.searchMap.pageNo>=lastPage-2){//如果当前页大于等于最大页码-2
firstPage=maxPageNo-4;//后5页
}else{// 显示以当前页为中心的5页
firstPage = $scope.searchMap.pageNo-2;
lastPage = $scope.searchMap +2;
}
}
// 循环产生页码标签
for(var i=firstPage;i<=lastPage;i++){
$scope.pageLabel.push(i);
}
};
- 在查询后调用该方法
// 搜索
$scope.search = function () {
searchService.search($scope.searchMap).success(
function (response) {
$scope.resultMap = response; // 搜索返回的结果
buildPageLabel();//页码的构建
}
);
};
- 修改search.html,循环产生页码
<li ng-repeat="page in pageLabel">
<a href="#">{{page}}</a>
</li>
...
<div><span>共{{resultMap.totalPages}}页 </span><span>
- 显示总条数
在面包屑后边加搜索条数
<ul class="fl sui-breadcrumb">搜索条件</ul>
<ul class="tags-choose">
<li class="tag" ng-if="searchMap.category!=''" ng-click="removeSearchItem('category')">商品分类:{{searchMap.category}}<i class="sui-icon icon-tb-close"></i></li>
...
搜索结果:{{resultMap.total}}条记录
</ul>
2. 提交页码查询
- 在searchController.js增加方法,修改页码执行查询
// 根据页码查询,点击页码能够查询
$scope.queryByPage = function(pageNo){
// 页码验证
if(pageNo<1 || pageNo>$scope.resultMap.totalPages){
return;
}
$scope.searchMap.pageNo = pageNo;
$scope.search();
};
- 修改页码调用方法
<div class="fr page">
<div class="sui-pagination pagination-large">
<ul>
<li class="prev disabled">
<a href="#" ng-click="queryByPage(searchMap.pageNo-1)">«</a>
</li>
<!--<li class="active">
<a href="#">1</a>
</li>-->
<li ng-repeat="page in pageLabel">
<a href="#" ng-click="queryByPage(page)">{{page}}</a>
</li>
<li class="dotted"><span>...</span></li>
<li class="next">
<a href="#" ng-click="queryByPage(searchMap.pageNo+1)">»</a>
</li>
</ul>
<div><span>共{{resultMap.totalPages}}页 </span><span>
到第
<input type="text" class="page-num" ng-model="searchMap.pageNo">
页 <button class="page-confirm" ng-click="queryByPage(searchMap.pageNo)">确定</button></span></div>
</div>
- 修改search方法,在执行查询前,转换为int类型,否则提交到后端可能变成字符串
// searchMap是传往后端的,resultMap是接收后端传回的数据,返回有rows内容 totalPages总页数 total总记录数
// 搜索
$scope.search = function () {
$scope.searchMap.pageNo = parseInt($scope.searchMap.pageNo);
//...
3. 显示省略号
// 分页标签的构建
buildPageLabel = function(){
$scope.pageLabel = [];//新增分页栏属性
var firstPage = 1; // 显示的开始页码
var lastPage = $scope.resultMap.totalPages;// 显示的截止页码
$scope.firstDot = true;//前面有省略号
$scope.lastDot = true;//后面有省略号
if($scope.resultMap.totalPages>5){//如果总页数大于5页
if($scope.searchMap.pageNo<=3){ // 当前页小于等于3
lastPage = 5;//前5页
$scope.firstDot = false; // 前面没有省略号
}else if($scope.searchMap.pageNo>=$scope.resultMap.totalPages-2){//如果当前页大于等于最大页码-2
firstPage=$scope.resultMap.totalPages-4;//后5页
$scope.lastDot = false;//后面没有省略号
}else{// 显示以当前页为中心的5页
firstPage = $scope.searchMap.pageNo-2;
lastPage = $scope.searchMap.pageNo +2;
}
}else{//总页数小于5,前后都没有省略号
$scope.firstDot = false;
$scope.lastDot = false;
}
// 循环产生页码标签
for(var i=firstPage;i<=lastPage;i++){
$scope.pageLabel.push(i);
}
};
修改页面:页码前的省略号,页码后的省略号
<li class="dotted" ng-if="firstDot==true"><span>...</span></li>
<li ng-repeat="page in pageLabel" class="">
<a href="#" ng-click="queryByPage(page)">{{page}}</a>
</li>
<li class="dotted" ng-if="lastDot==true"><span>...</span></li>
4. 页码不可用样式
// 判断当前页为第一页
$scope.isFirstPage = function(){
if($scope.searchMap.pageNo===1){
return true;
}else{
return false;
}
};
// 判断当前页是否为最后一页
$scope.isLastPage = function(){
if($scope.searchMap.pageNo===$scope.resultMap.totalPages){
return true;
}else{
return false;
}
};
修改页面
<ul>
<li class="prev {{isFirstPage()?'disabled':''}}">
<a href="#" ng-click="queryByPage(searchMap.pageNo-1)">«</a>
</li>
<!--<li class="active">
<a href="#">1</a>
</li>-->
<li class="dotted" ng-if="firstDot==true"><span>...</span></li>
<li ng-repeat="page in pageLabel" class="page==searchMap.pageNo?'active':''">
<a href="#" ng-click="queryByPage(page)">{{page}}</a>
</li>
<li class="dotted" ng-if="lastDot==true"><span>...</span></li>
<li class="next {{isLastPage()?'disabled':''}}">
<a href="#" ng-click="queryByPage(searchMap.pageNo+1)">»</a>
</li>
</ul>
5. 搜索起始页码处理
搜索“手机”后,得到页数是19,点击18页查询,再根据“三星”关键字查询,发现没有结果显示,需要每次查询关键字时重置页码为1
<button ng-click="searchMap.pageNo=1;search()" class="sui-btn btn-xlarge btn-danger" type="button">搜索</button>
3. 多关键字搜索
1. 多关键字搜索规则
如果输入的关键字时一个复合的词组(如三星手机),那solr如何进行搜索呢?
测试发现,solr在搜索时是将关键字进行分词,然后按照或的关系进行搜索。
为什么不是并的关系而是或的关系?
如果你是电商网站的运营者,肯定希望用户搜到的数据更多,如果用并,搜索可能极少甚至没有。另外还很智能的排序策略,按照关键字匹配度进行排序,如果某些记录同时包含三星和手机,这部分数据会排在前面显示。
2. 多关键字搜索空格处理
有些用户会在关键字中间习惯性输入一些空格,测试输入“三星 手机”结果并没有查询到任何结果。因此还要对空格进行处理,删除关键字中的空格。
修改search-service的ItemSearchServiceImpl.java
@Override
public Map search(Map searchMap) {
Map map = new HashMap();
// 空格处理
String keywords = (String) searchMap.get("keywords");
searchMap.put("keywords",keywords.replace(" ",""));
4. 排序
1. 价格排序
按照价格排序(升降可切换)
1. 后端代码
修改search-service的ItemSearchServiceImpl.java 添加排序代码
// 1.7 排序
String sortValue = (String) searchMap.get("sort");// ASC DESC
String sortField = (String) searchMap.get("sortField");// 排序的关键字
Sort sort = null;
if (sortValue!=null && !sortField.equals("")) {
if (sortValue.equals("ASC")) {
sort = new Sort(Sort.Direction.ASC,"item_"+sortField);
}
if(sortValue.equals("DESC")){
sort = new Sort(Sort.Direction.DESC,"item_"+sortField);
}
}
query.addSort(sort);
2. 前端代码
修改searchController.js的searchMap,增加排序
$scope.searchMap = {"keywords":"","category":"","brand":"","spec":{},"price":"","pageNo":1,"pageSize":40,"sort":"","sortField":""};// 搜索对象
修改searchController.js,增加方法实现查询
// 排序查询
$scope.sortSearch = function (sortField, sort) {
$scope.searchMap.sortField = sortField;
$scope.searchMap.sort = sort;
$scope.search();
}
修改页面
<ul class="sui-nav">
<li class="active">
<a href="#" ng-click="sortSearch('','')">综合</a>
</li>
<li>
<a href="#">销量</a>
</li>
<li>
<a href="#">新品</a>
</li>
<li>
<a href="#">评价</a>
</li>
<li>
<a href="#" ng-click="sortSearch('price','ASC')">价格↑</a>
</li>
<li>
<a href="#" ng-click="sortSearch('price','DESC')">价格↓</a>
</li>
</ul>
2. 按上架时间排序
1. 增加域定义
修改solrhome的schema.xml添加域定义
<field name="item_updateTime" type="date" indexed="true" stored="true" />
2. 修改实体类
为updateTime属性添加注解
@Field("item_updateTime")
private Date updateTime;
3. 重新运行导入程序
重启solr
安装pinyougou-pojo
重新运行
4. 修改页面
<li>
<a href="#" ng-click="sortSearch('updateTime','DESC')">新品</a>
</li>
3. 按销量排序(实现思路)
销量需要订单,还没做,先说整体思路
- 增加域item_saleCount 用于存储每个SKU的销量数据
- 编写定时器程序,用于更新每个SKU的销量数据(查询近一个月的销量数据,不是累积数据,防止新品排不上)
- 定时器每天只需执行一次,一般凌晨开始执行(便于维护)
定时器可以使用spring task技术实现,专门做任务调用。
4. 按评价排序(思路)
和销量类似,有个细节需要注意:
评价分好评、中评、差评,不能简单将评论数相加,而是进行加权统计。如好评权重为3,中评为1,差评权重为-3,最终得到综合评分。
5. 隐藏品牌列表
1. 需求分析
需求:如果用户输入的是品牌的关键字,则隐藏品牌列表
2. 思路:
关键字:三星
品牌列表:
循环品牌列表
其中有一个品牌时关键字的子字符串,就认为关键字是品牌
{id:11,text:''}
3. 代码实现
- 修改searchController.js
// 判断关键字是否是品牌
$scope.keywordsIsBrand=function(){
for(var i=0;i<$scope.resultMap.brandList.length;i++){
if($scope.searchMap.keywords.indexOf($scope.resultMap.brandList[i].text)>=0){//如果包含
return true;
}
}
return false;
}
- 修改页面
<div class="type-wrap logo" ng-if="resultMap.brandList!=null && searchMap.brand=='' && keywordsIsBrand()==false">
<div class="fl key brand">品牌</div>
6. 搜索页与首页对接
1. 需求分析
用户在首页的搜索框输入关键字,点击搜索后自动跳转到搜索页查询
2. 实现思路:
传递参数(关键字)
首页把关键字传递给搜索页
搜索页接收关键字并自动查询
3. 代码实现
1. 首页传递关键字
修改portal-web的contentController.js
// 搜索跳转
$scope.search = function () {
location.href = "http://localhost:9104/search.html#?keywords="+$scope.keywords;
}
修改protal-web的index.html
<input type="text" ng-model="keywords" id="autocomplete" type="text" class="input-error input-xxlarge" />
<button ng-click="search()" class="sui-btn btn-xlarge btn-danger" type="button">搜索</button>
2. 搜索页接收关键字
修改search-web的searchController.js
添加location服务用于接收参数
app.controller("searchController",function ($scope,$location, searchService) {}
// 加载查询字符串
$scope.loadkeywords = function () {
$scope.searchMap.keywords = $location.search()['keywords'];
$scope.search();
}
在搜索页面添加初始化
<body ng-app="pinyougou" ng-controller="searchController" ng-init="loadkeywords()">
7. 更新索引库
1. 需求分析
在进行商品审核之后更新到solr索引库,在商品删除后删除solr索引库中相应的记录。
思路:
- 商家商品服务 查询方法:根据SPU的id集合来查询SKU列表(状态已审核)
- 搜索服务 批量导入方法:接收参数为SKU列表
- 运营商后台 调用上面的两个方法
2. 查询审核商品列表(SKU)
1. 服务接口层
修改sellergoods-interface的GoodsService.java,新增方法
// 根据商品id和状态查询item表信息
public List<TbItem> findItemListByGoodsIdAndStatus(Long[] goodsIds,String status);
2. 服务实现层
修改sellergoods-service的GoodsServiceImpl.java
@Override
public void updateStatus(Long[] ids, String status) {
for (Long id : ids) {
TbGoods goods = goodsMapper.selectByPrimaryKey(id);
goods.setAuditStatus(status);
goodsMapper.updateByPrimaryKey(goods);
}
}
3. 更新到索引库
1. 服务接口层
修改search-interface的ItemSearchService.java
// 导入数据
public void importList(List list);
2. 服务实现层
修改search-service的ItemSearchServiceImpl.java
@Override
public void importList(List list) {
solrTemplate.saveBeans(list);
solrTemplate.commit();
}
3. 控制层
- manager-web工程引入依赖search-interface
- 修改manager-web的GoodsController.java
@RequestMapping("/updateStatus")
public Result updateStatus(Long[] ids,String status){
try {
goodsService.updateStatus(ids,status);
// 按照SPU的id查询 SKU列表(状态为1)
if("1".equals(status)){
List<TbItem> itemList = goodsService.findItemListByGoodsIdAndStatus(ids, status);
if (itemList.size()>0) {
itemSearchService.importList(itemList);
}else{
System.out.println("没有明细数据");
}
}
return new Result(true,"成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"失败");
}
}
4. 商品删除同步索引数据
1. 服务接口层
修改search-interface的ItemSearchService.java
// 删除数据
public void deleteByGoodsId(List goodsIdList);
2. 服务实现层
@Override
public void deleteByGoodsId(List goodsIdList) {
Query query = new SimpleQuery("*:*");
Criteria criteria = new Criteria("item_goodsid").in(goodsIdList);
query.addCriteria(criteria);
solrTemplate.delete(query);
solrTemplate.commit();
}
3. 控制层
修改manager-web的GoodsController.java
@RequestMapping("/delete")
public Result delete(Long [] ids){
try {
goodsService.delete(ids);
itemSearchService.deleteByGoodsId(Arrays.asList(ids));
return new Result(true, "删除成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false, "删除失败");
}
}