10. SpringDataSolr-----实例(搜索加强)

1. 搜索业务规则分析

  1. 需求分析
    我们今天要完成的目标是在关键字搜索的基础上添加面板搜索功能。
    面板上有1、商品分类、2、品牌、3、各种规格和价格区间(暂时不写)等条件
    10. SpringDataSolr-----实例(搜索加强)_第1张图片

  2. 业务规则:
    (1)当用户输入关键字搜索后,除了显示列表结果外,还应该显示通过这个关键字搜索到的记录都有哪些商品分类。
    (2)根据第一个商品分类查询对应的模板,根据模板查询出品牌列表
    (3)根据第一个商品分类查询对应的模板,根据模板查询出规格列表
    (4)当用户点击搜索面板的商品分类时,显示按照这个关键字查询结果的基础上,筛选此分类的结果。
    (5)当用户点击搜索面板的品牌时,显示在以上结果的基础上,筛选此品牌的结果
    (6)当用户点击搜索面板的规格时,显示在以上结果的基础上,筛选此规格的结果
    (7)当用户点击价格区间时,显示在以上结果的基础上,按价格进行筛选的结果
    (8)当用户点击搜索面板的相应条件时,隐藏已点击的条件。

  3. 实现思路
    (1)搜索面板的商品分类需要使用Spring Data Solr的分组查询来实现
    (2)为了能够提高查询速度,我们需要把查询面板的品牌、规格数据提前放入redis
    (3)查询条件的构建、面板的隐藏需要使用angularJS来实现
    (4)后端的分类、品牌、规格、价格区间查询需要使用过滤查询来实现

2. 完成业务规划(1)----查询分类列表

目标:(1)当用户输入关键字搜索后,除了显示列表结果外,还应该显示通过这个关键字搜索到的记录都有哪些商品分类。(也就是实现上面图片红框中1的那部分,动态显示)
实现:(1)搜索面板的商品分类需要使用Spring Data Solr的分组查询来实现

  1. 后端代码(service层)
	/**
	 * 查询分类列表  
	 * @param searchMap
	 * @return
	 */
	private  List searchCategoryList(Map searchMap){
		List<String> list=new ArrayList();	
		Query query=new SimpleQuery();		
		//按照关键字查询
		Criteria criteria=new Criteria("item_keywords").is(searchMap.get("keywords"));
		query.addCriteria(criteria);
		//设置分组选项
		GroupOptions groupOptions=new GroupOptions().addGroupByField("item_category");
		query.setGroupOptions(groupOptions);
		//得到分组页
		GroupPage<TbItem> page = solrTemplate.queryForGroupPage(query, TbItem.class);
		//根据列得到分组结果集
		GroupResult<TbItem> groupResult = page.getGroupResult("item_category");
		//得到分组结果入口页
		Page<GroupEntry<TbItem>> groupEntries = groupResult.getGroupEntries();
		//得到分组入口集合
		List<GroupEntry<TbItem>> content = groupEntries.getContent();
		for(GroupEntry<TbItem> entry:content){
			list.add(entry.getGroupValue());//将分组结果的名称封装到返回值中	
		}
		return list;
	}

search方法调用

	@Override
	public Map<String, Object> search(Map searchMap) {
		Map<String,Object> map=new HashMap<>();		
		//1.按关键字查询(高亮显示)(一步一步完善)(括号中间的方法是高亮显示查询列表)
		map.putAll(searchList(searchMap));
		//2.根据关键字查询商品分类
		List categoryList = searchCategoryList(searchMap);
		map.put("categoryList",categoryList);	//把查询到的分组结果集存储到map集合中,到时候前端就可以直接取出来用了	
		return map;
	}
  1. 前端代码(html)
<div class="type-wrap" ng-if="resultMap.categoryList!=null">
	<div class="fl key">商品分类</div>
	<div class="fl value">
		<span ng-repeat="category in resultMap.categoryList">
			<a href="#">{{category}}</a><!-- 输出分组列 -->
		</span>
	</div>
	<div class="fl ext"></div>
</div>




3. 缓存品牌和规格数据

目标:为了能够提高查询速度,把分类、品牌和规格放入redis缓存中,这样,每次查询之后
实现:

  1. 后端(Service层)----缓存商品分类数据(商品分类是一个表存储的,品牌和规格是一个表储存,所以这里是在两个不同的service层中)
	@Autowired
	private RedisTemplate redisTemplate;
	@Autowired
	private TbItemCatMapper itemCatMapper;

	/**
	 * 查询全部
	 */
	@Override
	public List<TbItemCat> findAll() {
		return itemCatMapper.selectByExample(null);
	}
	/**
	 * 根据上级ID查询列表(这个是三级联动下拉列表的方法,第一次运行的时候会传入0)(根据本工程来说,这个方法是每次增删改之后都要运行的方法,所以写在这里很合适)
	 */
	@Override
	public List<TbItemCat> findByParentId(Long parentId) {		
		TbItemCatExample example1=new TbItemCatExample();
		Criteria criteria1 = example1.createCriteria();
		criteria1.andParentIdEqualTo(parentId);
		//每次执行查询的时候,一次性读取缓存进行存储 (因为每次增删改都要执行此方法)
		List<TbItemCat> list = findAll();//后面这个是查询搜索的方法
		for(TbItemCat itemCat:list){
			redisTemplate.boundHashOps("itemCat").put(itemCat.getName(), itemCat.getTypeId());
		}
		System.out.println("更新缓存:商品分类表");
		return  itemCatMapper.selectByExample(example1);		
	}
  1. 后端(service层)------缓存品牌和规格列表数据
	@Autowired
	private RedisTemplate redisTemplate;
	
	/**
	 * 将数据存入缓存
	 */
	private void saveToRedis(){
		//获取模板数据
		List<TbTypeTemplate> typeTemplateList = findAll();
		//循环模板
		for(TbTypeTemplate typeTemplate :typeTemplateList){				
			//存储品牌列表		
			List<Map> brandList = JSON.parseArray(typeTemplate.getBrandIds(), Map.class);			
			redisTemplate.boundHashOps("brandList").put(typeTemplate.getId(), brandList);
			//存储规格列表
			List<Map> specList = findSpecList(typeTemplate.getId());//根据模板ID查询规格列表
			redisTemplate.boundHashOps("specList").put(typeTemplate.getId(), specList);		
		}
	}
  1. 调用saveToRedis()方法
    在查询分页方法(findPage) 时调用此方法 ---------这样在增删改后会自动调用该方法.(也可以直接写在这个方法中,但是太长了,所以提取出来了,根据本工程来说,这个方法也是增删改之后必定要运行的方法)
public PageResult findPage(TbTypeTemplate typeTemplate, int pageNum, int pageSize) {
		......	
		saveToRedis();//存入数据到缓存
		return new PageResult(page.getTotal(), page.getResult());
}

4. 显示品牌和规格数据(价格是固定的所以不需要)

目标:(2)根据第一个商品分类查询对应的模板,根据模板查询出品牌列表
          (3)根据第一个商品分类查询对应的模板,根据模板查询出规格列表
实现:

  1. 后端(service层)
    调用searchBrandAndSpecList()方法
	@Autowired
	private RedisTemplate redisTemplate;
	
	/**
	 * 查询品牌和规格列表
	 * @param category 分类名称
	 * @return
	 */
	private Map searchBrandAndSpecList(String category){
		Map map=new HashMap();		
		Long typeId = (Long) redisTemplate.boundHashOps("itemCat").get(category);//获取模板ID
		if(typeId!=null){
			//根据模板ID查询品牌列表 
			List brandList = (List) redisTemplate.boundHashOps("brandList").get(typeId);
			map.put("brandList", brandList);//返回值添加品牌列表
			//根据模板ID查询规格列表
			List specList = (List) redisTemplate.boundHashOps("specList").get(typeId);
			map.put("specList", specList);				
		}			
		return map;
	}

搜索主要的方法 : Search(Map searchMap)

	@Override
	public Map<String, Object> search(Map searchMap) {
		Map<String,Object> map=new HashMap<>();	//查询之后的所有结果都储存到这个集合中去了		
		//1.按关键字查询(高亮显示)
		map.putAll(searchList(searchMap));
		
		//2.分组查询 商品分类列表(根据关键字查询商品分类)
		List<String> categoryList = searchCategoryList(searchMap);
		map.put("categoryList", categoryList);
		
		//3.查询品牌和规格列表
		if(categoryList.size()>0){
			map.putAll(searchBrandAndSpecList(categoryList.get(0)));//这里直接传入(0是表示查询集合中的一个值,相当于这里是随便传入一个分类来进行查询,后续会继续完善,根据搜索内容进行查询)
		}
		return map;
	}
  1. 前端(html) --------------获取品牌列表
<div class="type-wrap logo" ng-if="resultMap.brandList!=null"><!-- 判断是否有值,没值就显示这列,表示没有选择 -->
	<div class="fl key brand">品牌</div>
	<div class="value logos">
		<ul class="logo-list">
			<li ng-repeat="brand in resultMap.brandList"><!-- resultMaps是我们前端js定的的,具体看上一篇 -->
				{{brand.text}}
			</li>							
		</ul>
	</div>
	<div class="ext">
		<a href="javascript:void(0);" class="sui-btn">多选</a>
		<a href="javascript:void(0);">更多</a>
	</div>
</div>
  1. 前端(html)--------------------获取规格列表
<div class="type-wrap" ng-repeat="spec in resultMap.specList">
	<div class="fl key">{{spec.text}}</div>
	<div class="fl value">
		<ul class="type-list">						
			<li ng-repeat="pojo in spec.options">
				<a>{{pojo.optionName}}</a>
			</li>							
		</ul>
	</div>
	<div class="fl ext"></div>
</div>

5. 过滤条件构建

目标:当面包屑显示分类、品牌和规格时,要同时隐藏搜索面板对应的区域。    //相当于上面的(8)
          点击搜索面板上的分类、品牌和规格以及价格,实现查询条件的构建。查询条件以面包屑的形式显示。
          用户可以点击面包屑上的X 撤销查询条件。撤销后显示搜索面包相应的区域。
实现:(3)查询条件的构建、面板的隐藏需要使用angularJS来实现

  1. 前端(Controller层)------------添加搜索项方法
$scope.searchMap={'keywords':'','category':'','brand':'','spec':{},'price':''};//搜索对象(关键字,分类,品牌,规格,价格)
//添加搜索项(点击分类标签会运行这个方法)
$scope.addSearchItem=function(key,value){
	if(key=='category' || key=='brand' || key=='price'){//如果点击的是分类或者是品牌或是价格
		$scope.searchMap[key]=value;
	}else{
		$scope.searchMap.spec[key]=value;
	}	
}
  1. 前端(html)------------点击搜索项(这里就是上面红框中的东西,点击它这一列会消失,并且点击的内容会跑商 3. 中去)
<!-- 点击商品分类标签(这是完整的标签,下面两个省略了) -->
<!-- 隐藏分类面板:  && searchMap.category==''   ,这段就是隐藏分类面板的方法,判断他是否有值,如果有值,则直接隐藏这个选项-->
<div class="type-wrap" ng-if="resultMap.categoryList!=null && searchMap.category=='' ">
	<div class="fl key">商品分类</div>
	<div class="fl value">
		<span ng-repeat="category in resultMap.categoryList" > 
			 <a href="#" ng-click="addSearchItem('category',category)">{{category}}</a>
		</span>						
	</div>
	<div class="fl ext"></div>
</div>
...

<!-- 点击品牌标签 -->
<a href="#" ng-click="addSearchItem('brand',brand.text)">{{brand.text}}</a>	
...

<!-- 点击规格标签-->
<a href="#"  ng-click="addSearchItem(spec.text,pojo.optionName)">
{{pojo.optionName}}</a>

<!-- 价格区间 -->
<ul class="type-list" ng-if="searchMap.price==''">
	<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>


  1. 前端(html)-------------显示面包屑(就是 : a / b / c / d ,这种东西,点击 2. 中的选项,这里就会生成一个,不过我们点击面包屑中的 x ,它就会消失,并且 2. 中的选项会再次出现)
<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>
    <li class="tag" ng-if="searchMap.brand!=''" ng-click="removeSearchItem('brand')">品牌:{{searchMap.brand}}<i class="sui-icon icon-tb-close"></i></li>
	<li class="tag" ng-repeat="(key,value) in searchMap.spec" ng-click="removeSearchItem(key)">{{key}}:{{value}}<i class="sui-icon icon-tb-close"></i></li>	
	<li class="tag" ng-if="searchMap.price!=''" ng-click="removeSearchItem('price')">价格:{{searchMap.price}}<i  class="sui-icon icon-tb-close"></i></li>				
</ul>
  1. 前端(Controller层)---------------撤销搜索项的方法
	//移除复合搜索条件(点击面包屑上面的 x 会运行这个方法)
	$scope.removeSearchItem=function(key){
		if(key=="category" ||  key=="brand" || key==''price''){//如果是分类或品牌或者价格
			$scope.searchMap[key]="";		
		}else{//否则是规格
			delete $scope.searchMap.spec[key];//移除此属性
		}	
	}
  1. 如何在点击的时候有用?--------- 在添加和删除筛选条件时自动调用搜索方法
	$scope.searchMap={'keywords':'','category':'','brand':'','spec':{},'price':''};//搜索对象(关键字,分类,品牌,规格,价格)
	//添加复合搜索条件
	$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();//执行搜索 (其他都是重复代码,只有这句是新加上去的)



	}

*写一下完整的搜索方法

前端(html)

<!--searchAutoComplete-->
<div class="input-append">
	<input type="text" id="autocomplete" ng-model="searchMap.keywords" type="text" class="input-error input-xxlarge" />
	<button class="sui-btn btn-xlarge btn-danger" ng-click="search()" type="button">搜索</button>
</div>

前端(controller层)

	//都在同一个
	$scope.searchMap={'keywords':'','category':'','brand':'','spec':{},'price':''};//搜索对象(关键字,分类,品牌,规格,价格)(注意细节,这里的规格是: a:{}  这种格式,里面的是数据时键值对: a{a:b,c:d})
	
	//搜索
	$scope.search=function(){
		searchService.search($scope.searchMap).success(//注意:这里searchMap参数就是上面我们的添加进去的搜索条件
			function(response){
				$scope.resultMap=response;				
			}
		);		
	}

前端(service层)

	
	this.search=function(searchMap){
		return $http.post('itemsearch/search.do',searchMap);
	}

后端(service层)

	@Override
	public Map search(Map searchMap) {
		Map map=new HashMap();
		
		//1.查询列表
		map.putAll(searchList(searchMap));
		//2.分组查询 商品分类列表
		List<String> categoryList = searchCategoryList(searchMap);
		map.put("categoryList", categoryList);
		
		//3.查询品牌和规格列表
		String category= (String) searchMap.get("category");
		if(!category.equals("")){						
			map.putAll(searchBrandAndSpecList(category));
		}else{
			if(categoryList.size()>0){			
				map.putAll(searchBrandAndSpecList(categoryList.get(0)));
			}	
		}
		
		return map;
	}

后端(Controller层)

	@Reference
	private ItemSearchService itemSearchService;
	
	@RequestMapping("/search")
	public Map search(@RequestBody Map searchMap){
		
		return itemSearchService.search(searchMap);
		
	}

6.过滤查询(可以理解为 5. 是判断前端是否有值 6. 是判断后端是否有值)

目标:(4)当用户点击搜索面板的商品分类时,显示按照这个关键字查询结果的基础上,筛选此分类的结果。
           (5)当用户点击搜索面板的品牌时,显示在以上结果的基础上,筛选此品牌的结果
           (6)当用户点击搜索面板的规格时,显示在以上结果的基础上,筛选此规格的结果
           (7)当用户点击价格区间时,显示在以上结果的基础上,按价格进行筛选的结果
          (8)当用户点击价格区间时,显示在以上结果的基础上,按价格进行筛选的结果

实现:

  1. 后端(service层)---------------分类过滤
	/**
	 * 根据关键字搜索列表  +  根据分类查询
	 * @param keywords
	 * @return
	 */
	private Map searchList(Map searchMap){
		.......
		//1.1关键字查询......	
		//1.2按分类筛选(判断传过来的值中 category 是否为空,不为空则把这个查询条件添加进去)
		if(!"".equals(searchMap.get("category"))){			
			Criteria filterCriteria=new Criteria("item_category").is(searchMap.get("category"));
			FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria);
			query.addFilterQuery(filterQuery);
		}
		//高亮显示处理.....		
	}
  1. 后端(service层)---------------品牌过滤
	/**
	 * 根据关键字搜索列表  +  根据分类查询  +  根据品牌查询
	 * @param keywords
	 * @return
	 */
	private Map searchList(Map searchMap){
		.......
		//1.1关键字查询
         .......
		//1.2按分类筛选
         .......
		//1.3按品牌筛选
		if(!"".equals(searchMap.get("brand"))){			
			Criteria filterCriteria=new Criteria("item_brand").is(searchMap.get("brand"));
			FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria);
			query.addFilterQuery(filterQuery);
		}
		//高亮显示处理...............
	}
  1. 后端(service层)---------------规格过滤
	/**
	 * 根据关键字搜索列表  +  根据分类查询  +  根据品牌查询  +  根据规格查询
	 * @param keywords
	 * @return
	 */
	private Map searchList(Map searchMap){
		......
		//1.1关键字查询
		....
		//1.2按分类筛选
		.....
		//1.3按品牌筛选
		......
		//1.4过滤规格
		if(searchMap.get("spec")!=null){
				Map<String,String> specMap= (Map) searchMap.get("spec");
				for(String key:specMap.keySet() ){
	Criteria filterCriteria=new Criteria("item_spec_"+key).is( specMap.get(key) );
					FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria);
					query.addFilterQuery(filterQuery);				
				}			
		}
		//高亮显示处理.....
	}
  1. 后端(service层)---------------根据分类查询品牌规格列表(当点击分类时,品牌和规格自动出现相应的属性:点击手机下面显示手机品牌,点击电视显示电视的品牌)
	
	@Override
	public Map<String, Object> search(Map searchMap) {
		Map<String,Object> map=new HashMap<>();		
		//1.按关键字查询(高亮显示)
......		
		//2.根据关键字查询商品分类	
		List<String> categoryList = searchCategoryList(searchMap);
		map.put("categoryList", categoryList);
......	
		//3.查询品牌和规格列表
		String categoryName=(String)searchMap.get("category");//获得分类
		if(!"".equals(categoryName)){//如果有分类名称(这是是前端传过来的数据来判断是否有值,如果选择了分类这里就有值)
			map.putAll(searchBrandAndSpecList(categoryName));	//根据商品分类名称查询品牌和规格列表(下同),并把它全部put到map集合中	
		}else{//如果没有分类名称,按照第一个查询
			if(categoryList.size()>0){//判断我们是否在数据库中有获取到category(这里是从数据库中查询出来的值,,理论上说不会为空,这个判断只是为了以防万一,怕没获取到)
				map.putAll(searchBrandAndSpecList(categoryList.get(0)));
			}
		}
		return map;
	}
  1. 后端(service层)---------------实现品优购价格区间筛选功能
	/**
	 * 根据关键字搜索列表
	 * @param keywords
	 * @return
	 */
	private Map searchList(Map searchMap){
         ......		
		//1.1关键字查询.....
		//1.2按分类筛选.....
		//1.3按品牌筛选.....
		//1.4过滤规格	......
		//1.5按价格筛选.....
		if(!"".equals(searchMap.get("price"))){
			String[] price = ((String) searchMap.get("price")).split("-");
			if(!price[0].equals("0")){//如果区间起点不等于0
				Criteria filterCriteria=new Criteria("item_price").greaterThanEqual(price[0]);//这个是大于或者等于的方法
				FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria);
				query.addFilterQuery(filterQuery);				
			}		
			if(!price[1].equals("*")){//如果区间终点不等于*
				Criteria filterCriteria=new  Criteria("item_price").lessThanEqual(price[1]);//这个是小于或者等于的方法
				FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria);
				query.addFilterQuery(filterQuery);				
			}
		}		
		//高亮显示处理	.....
	}

你可能感兴趣的:(10. SpringDataSolr-----实例(搜索加强))