品优购08——搜索解决方案solr01

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 );
}
发布了205 篇原创文章 · 获赞 9 · 访问量 7922

猜你喜欢

转载自blog.csdn.net/weixin_43318134/article/details/104229395
今日推荐