1. 商品批量数据导入
创建pinyougou-solr-util(jar) ,引入pinyougou-dao 以及spring 相关依赖
工程如图;
1.1 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:solr="http://www.springframework.org/schema/data/solr"
xsi:schemaLocation="http://www.springframework.org/schema/data/solr
http://www.springframework.org/schema/data/solr/spring-solr-1.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.pinyougou.solrutil"></context:component-scan>
<!-- solr服务器地址 -->
<solr:solr-server id="solrServer" url="http://127.0.0.1:8080/solor" />
<!-- solr模板,使用solr模板可对索引库进行CRUD的操作 -->
<bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate">
<constructor-arg ref="solrServer" />
</bean>
</beans>
1.2 实现商品查询
package com.pinyougou.solrutil;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import com.pinyougou.mapper.TbItemMapper;
import com.pinyougou.pojo.TbItem;
import com.pinyougou.pojo.TbItemExample;
import com.pinyougou.pojo.TbItemExample.Criteria;
@Component
public class SolrUtil {
@Resource
private TbItemMapper itemMapper;
/**
* 批量导入数据
*/
public void importItemData(){
TbItemExample example = new TbItemExample();
Criteria criteria = example.createCriteria();
// 已审核
criteria.andStatusEqualTo("1");
List<TbItem> itemList = itemMapper.selectByExample(example);
System.out.println("===商品列表===");
for(TbItem item:itemList){
System.out.println(item.getTitle());
}
System.out.println("===结束===");
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:spring/applicationContext*.xml");
SolrUtil solrUtil = (SolrUtil) context.getBean("solrUtil");
solrUtil.importItemData();
}
}
1.3 实现批量导入
在importData方法中加入如下代码即可
注意:一定要记得commit
2. 搜索功能
2.1 基本功能-关键字搜索
2.1.1 后端
1)后端接口工程
创建pinyougou-search-interface模块(搜索服务接口),依赖pinyougou-pojo
创建com.pinyougou.search.service包,创建业务接口
@Service(timeout=3000)
public class ItemSearchServiceImpl implements ItemSearchService{
@Autowired
private SolrTemplate solrTemplate;
@Override
public Map<String, Object> search(Map searchMap) {
Map<String,Object> map=new HashMap<>();
Query query=new SimpleQuery();
//添加查询条件
Criteria criteria=new Criteria("item_keywords").is(searchMap.get("keywords"));
query.addCriteria(criteria);
ScoredPage<TbItem> page = solrTemplate.queryForPage(query, TbItem.class);
map.put("rows", page.getContent());
return map;
}
}
创建pinyougou-search-web 工程,com.pinyougou.search.controller 编写控制器类
package com.pinyougou.search.controller;
import java.util.Map;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.dubbo.config.annotation.Reference;
import com.pinyougou.search.service.ItemSearchService;
@RestController
@RequestMapping("/itemsearch")
public class ItemSearchController {
@Reference
private ItemSearchService itemSearchService;
@RequestMapping("/search")
public Map<String, Object> search(@RequestBody Map searchMap ){
return itemSearchService.search(searchMap);
}
}
2.1.2 前端
1)静态资源导入
2)service层js
app.service("searchService",function($http){
this.search = function(searchMap) {
return $http.post("itemsearch/search.do",searchMap);
}
});
3)controller层js
app.controller("searchController",function($scope,searchService){
// 搜索方法
$scope.search = function() {
searchService.search($scope.searchMap).success(function(response){
$scope.resultMap = response;
});
}
});
4)页面改造
测试:
刚进网站,列表没数据
输入华为关键词,点击搜索,如图:
2.2 搜索高亮显示
在实际开发中,我们往往希望搜索的关键词能高亮显示。
1)后端代码
修改服务层代码ItemSearchServiceImpl.java
package com.pinyougou.search.service.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.data.solr.core.SolrTemplate;
import org.springframework.data.solr.core.query.Criteria;
import org.springframework.data.solr.core.query.HighlightOptions;
import org.springframework.data.solr.core.query.HighlightQuery;
import org.springframework.data.solr.core.query.Query;
import org.springframework.data.solr.core.query.SimpleHighlightQuery;
import org.springframework.data.solr.core.query.SimpleQuery;
import org.springframework.data.solr.core.query.result.HighlightEntry;
import org.springframework.data.solr.core.query.result.HighlightEntry.Highlight;
import org.springframework.data.solr.core.query.result.HighlightPage;
import org.springframework.data.solr.core.query.result.ScoredPage;
import org.springframework.util.CollectionUtils;
import com.alibaba.dubbo.config.annotation.Service;
import com.pinyougou.pojo.TbItem;
import com.pinyougou.search.service.ItemSearchService;
/**
* Solr搜索服务层
* @author Administrator
*
*/
@Service(timeout=50000)
public class ItemSearchServiceImpl implements ItemSearchService{
@Resource
private SolrTemplate solrTemplate;
/* 搜索功能
* 注意:参数searchMap 我们定义好格式为["keywords",""]
*/
@Override
public Map<String, Object> search(Map searchMap) {
Map<String, Object> rtnMap = new HashMap<String, Object>();
HighlightQuery query = new SimpleHighlightQuery();
// 查询条件
Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
query.addCriteria(criteria);
// 设置高亮选项
HighlightOptions options = new HighlightOptions();
options.addField("item_title");
options.setSimplePrefix("<em style='color:red'>");
options.setSimplePostfix("</em>");
// 将高亮设置到查询对象上
query.setHighlightOptions(options );
// 查询获取高亮对象
HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query , TbItem.class);
// 获取高亮入口集合
List<HighlightEntry<TbItem>> entryList = page.getHighlighted();
for(HighlightEntry<TbItem> entry : entryList){
// 获取每个入口的高亮列表
List<Highlight> highlights = entry.getHighlights();
// 获取实体对象
TbItem entity = entry.getEntity();
// getEveryEntry(highlights, entity);
if(!CollectionUtils.isEmpty(highlights) && !CollectionUtils.isEmpty(highlights.get(0).getSnipplets())){
String title = highlights.get(0).getSnipplets().get(0);
entity.setTitle(title);
}
}
List<TbItem> content = page.getContent();
rtnMap.put("rows", content);
return rtnMap;
}
/**设置每一个高亮列表的每一个域的高亮小片
* @param highlights
* @param entity
*/
private void getEveryEntry(List<Highlight> highlights, TbItem entity) {
// 循环遍历每个入口的高亮列表
for(Highlight h : highlights){
// 得到高亮“小片”集合
List<String> sns = h.getSnipplets();
for(String str : sns){
entity.setTitle(str);
System.out.println(sns);
}
}
}
}
2)前端
1. 修改base.js,添加一个HTML过滤器
// 定义过滤器
app.filter("trustHtml",['$sce',function($sce){
// data 表示被过滤的内容
return function(data) {
return $sce.trustAsHtml(data);
}
}]);
2. 在页面使用过滤器
效果如图;
3. 搜索业务规则分析
业务规则:
(1)当用户输入关键字搜索后,除了显示列表结果外,还应该显示通过这个关键字搜索到的记录都有哪些商品分类。
(2)根据第一个商品分类查询对应的模板,根据模板查询出品牌列表
(3)根据第一个商品分类查询对应的模板,根据模板查询出规格列表
(4)当用户点击搜索面板的商品分类时,显示按照这个关键字查询结果的基础上,筛选此分类的结果。
(5)当用户点击搜索面板的品牌时,显示在以上结果的基础上,筛选此品牌的结果
(6)当用户点击搜索面板的规格时,显示在以上结果的基础上,筛选此规格的结果
(7)当用户点击价格区间时,显示在以上结果的基础上,按价格进行筛选的结果
(8)当用户点击搜索面板的相应条件时,隐藏已点击的条件。
实现思路
(1)搜索面板的商品分类需要使用Spring Data Solr的分组查询来实现
(2)为了能够提高查询速度,我们需要把查询面板的品牌、规格数据提前放入redis
(3)查询条件的构建、面板的隐藏需要使用angularJS来实现
(4)后端的分类、品牌、规格、价格区间查询需要使用过滤查询来实现
4. 分组查询分类列表
1)后端代码
private List searchCategoryList(Map searchMap) {
List<String> list = new ArrayList<>();
// 创建查询对象
Query query = new SimpleQuery("*:*");
// 创建一个 item_keywords 域的条件
Criteria criteria = new Criteria("item_keywords");
// 设置搜索条件
criteria.is(searchMap.get("keywords"));
/*
* 下面开始设置分组
* */
GroupOptions options = new GroupOptions();
options.addGroupByField("item_category");
query.setGroupOptions(options);
// 添加查询条件到查询对象
query.addCriteria(criteria );
// 执行分组查询 得到分组页对象
GroupPage<TbItem> page = solrTemplate.queryForGroupPage(query , TbItem.class);
// 1. 获取分组结果对象
GroupResult<TbItem> groupResult = page.getGroupResult("item_category");
// 2. 获取分组入口页对象
Page<GroupEntry<TbItem>> groupEntries = groupResult.getGroupEntries();
// 3. 获取分组入口集合
List<GroupEntry<TbItem>> entryList = groupEntries.getContent();
for(GroupEntry<TbItem> entry : entryList){
String groupValue = entry.getGroupValue();
list.add(groupValue);
}
return list;
}
2)前端代码
前端没什么修改的,直接用angular遍历一下就行了,如图;
5. 缓存品牌和规格数据
5.1 数据缓存代码实现
(1)当用户进入运营商后台的商品分类页面时,将商品分类数据放入缓存(Hash)。以分类名称作为key ,以模板ID作为值
(2)当用户进入运营商后台的模板管理页面时,分别将品牌数据和规格数据放入缓存(Hash)。以模板ID作为key,以品牌列表和规格列表作为值
1)在com.pinyougou.sellergoods.service.impl.ItemCatServiceImpl中的findByParentId方法中缓存商品分类数据
2)在com.pinyougou.sellergoods.service.impl.TypeTemplateServiceImpl中的findPage方法中缓存品牌数据与规格数据
5.2 显示品牌和规格列表
5.2.1 后端
1)在com.pinyougou.search.service.impl.ItemSearchServiceImpl类中定义查询品牌与规格的方法
/**
* 根据商品分类名称查询品牌和规格
* @param category
* @return
*/
private Map searchBrandAndSpecList(String category){
Map map = new HashMap();
// 根据商品分类名称得到模板ID
Long typeId = (Long) redisTemplate.boundHashOps("itemCat").get(category);
if(null != typeId){
// 1. 根据模板id查询品牌列表
List<Map> brandList = (List<Map>) redisTemplate.boundHashOps("brandList").get(typeId);
map.put("brandList", brandList);
// 2. 根据模板id查询规格列表
List<Map> specList = (List<Map>) redisTemplate.boundHashOps("specList").get(typeId);
map.put("specList", specList);
}
return map;
}
2)在主方法search中进行调用
5.2.2 前端
1)品牌列表展示
2)规格展示
6. 过滤条件构建
6.1 添加搜索项方法
修改pinyougou-search-web的searchController.js
$scope.searchMap={'keywords':'','category':'','brand':'','spec':{}};//搜索对象
//添加搜索项
$scope.addSearchItem=function(key,value){
if(key=='category' || key=='brand'){//如果点击的是分类或者是品牌
$scope.searchMap[key]=value;
}else{
$scope.searchMap.spec[key]=value;
}
}
6.1.1 添加搜索项
修改pinyougou-search-web 的search.html ,为搜索面板添加点击事件
点击商品分类标签
<a href="#" ng-click="addSearchItem('category',category)">{{category}}</a>
点击品牌标签
<a href="#" ng-click="addSearchItem('brand',brand.text)">{{brand.text}}</a>
点击规格标签
<a href="#" ng-click="addSearchItem(spec.text,pojo.optionName)">
{{pojo.optionName}}</a>
6.1.2 显示面包屑
修改pinyougou-search-web 的search.html,用面包屑形式显示搜索条件
<ul class="fl sui-breadcrumb">搜索条件:</ul>
<ul class="tags-choose">
<li class="tag" ng-if="searchMap.category!=''">商品分类:{{searchMap.category}}<i class="sui-icon icon-tb-close"></i></li>
<li class="tag" ng-if="searchMap.brand!=''">品牌:{{searchMap.brand}}<i class="sui-icon icon-tb-close"></i></li>
<li class="tag" ng-repeat="(key,value) in searchMap.spec">{{key}}:{{value}}<i class="sui-icon icon-tb-close"></i></li>
</ul>
6.1.3 撤销搜索项
1)撤销方法
// 撤销选中项
$scope.cancleSearchitem = function(key) {
if(key=='category' || key=='brand'){//如果点击的是分类或者是品牌
$scope.searchMap[key]="";
}else{
delete $scope.searchMap.spec[key]; // 删除为key的属性
}
}
2)HTML页面调用
pinyougou-search-web工程的search.html
<ul class="tags-choose">
<li class="tag" ng-if="searchMap.category != ''" ng-click="cancleSearchitem('category')">
商品分类:{{searchMap.category}}<i class="sui-icon icon-tb-close"></i>
</li>
<li class="tag" ng-if="searchMap.brand != ''" ng-click="cancleSearchitem('brand')">
品牌:{{searchMap.brand}}<i class="sui-icon icon-tb-close"></i>
</li>
<li class="tag" ng-repeat="(key,value) in searchMap.spec" ng-click="cancleSearchitem(key)">
{{key}}:{{value}}<i class="sui-icon icon-tb-close"></i>
</li>
</ul>
6.1.4 隐藏查询面板
修改search.html
1)隐藏分类面板
<div class="type-wrap" ng-if=" (resultMap.categoryList != null && resultMap.categoryList.length > 0) && searchMap.category == ''">
<div class="fl key">商品分类</div>
<div class="fl value">
<span ng-repeat="item in resultMap.categoryList">
<a href="#" ng-click="addSearchItem('category',item)">{{item}}</a>
</span>
</div>
<div class="fl ext"></div>
</div>
2)隐藏品牌面板
<div class="type-wrap logo" ng-if="resultMap.brandList != null && searchMap.brand == ''">
<div class="fl key brand">品牌</div>
<div class="value logos">
<ul class="logo-list">
<li ng-repeat="item in resultMap.brandList">
<a href="#" ng-click="addSearchItem('brand',item.text)">{{item.text}}</a>
</li>
</ul>
</div>
<div class="ext">
<a href="javascript:void(0);" class="sui-btn">多选</a>
<a href="javascript:void(0);">更多</a>
</div>
</div>
3)隐藏规格面板
<div class="type-wrap" ng-repeat="item in resultMap.specList" ng-if="searchMap.spec[item.text]==null">
<div class="fl key">
{{item.text}}
</div>
<div class="fl value">
<ul class="type-list">
<li ng-repeat="option in item.options">
<a href="#" ng-click="addSearchItem(item.text,option.optionName)">{{option.optionName}}</a>
</li>
</ul>
</div>
<div class="fl ext"></div>
</div>
6.2 过滤后端
6.2.1 过滤方法定义
/**
* @param searchMap:查询条件map
* @param filter:过滤的类型名称 例如:category
* @param filed:过滤的域名 例如:item_category
* @param query 查询对象
*/
private void addFilter(Map searchMap,String filter,String filed,HighlightQuery query){
if(!"".equals(searchMap.get(filter))){
FilterQuery filterQuery = new SimpleFilterQuery();
Criteria filterCriteria = new Criteria(filed).is(searchMap.get(filter));
filterQuery.addCriteria(filterCriteria);
query.addFilterQuery(filterQuery );
}
}
6.2.2 品牌、商品分类和规格过滤
// 1.2 按照商品分类过滤 选择了分类才进行筛选
addFilter(searchMap,"category","item_category",query);
// 1.3 按照品牌来过滤
addFilter(searchMap,"brand","item_brand",query);
// 1.4 按照规格过滤
Map<String, Object> specMap = (Map<String, Object>) searchMap.get("spec");
// key 就是机身内存 网络制式这样的key
for(String key : specMap.keySet()){
addFilter(specMap,key,"item_spec_"+key,query);
}
6.3 价格搜索条件过滤
价格是一个区间,需要特殊处理一下,我们可以将价格区间看成是一个用短线连接的字符串,这样处理起来会简单一点
6.3.1 前端
1)js
2)界面
6.3.2 后端
在查询列表方法后面加上如下代码:
// 1.5 按照价格过滤
String priceStr = (String) searchMap.get("price");
if(!StringUtils.isEmpty(priceStr)){
String[] priceArr = priceStr.split("-");
Criteria priceCriteria = new Criteria("item_price");
if("*".equals(priceArr[1])){
priceCriteria.greaterThanEqual(priceArr[0]);
} else {
priceCriteria.between(priceArr[0], priceArr[1], true,true);
}
FilterQuery filterQuery = new SimpleFilterQuery(priceCriteria);
query.addFilterQuery(filterQuery );
}