品优购电商系统开发 第10章 搜索解决方案-Solr 【2】

21 篇文章 2 订阅
15 篇文章 0 订阅

课程目标

目标1:实现品优购搜索结果高亮显示功能
目标2:说出品优购搜索的业务规则和实现思路
目标3:完成查询分类列表的功能
目标4:完成缓存品牌和规格数据的功能
目标5:完成显示品牌和规格数据的功能
目标6:完成过滤条件构建的功能
目标7:完成过滤查询的功能

1.品优购-高亮显示

1.1需求分析

将用户输入的关键字在标题中以红色的字体显示出来,就是搜索中常用的高亮显示.
在这里插入图片描述

1.2后端代码

修改服务层代码ItemSearchServiceImpl.java
创建私有方法,用于返回查询列表的结果(高亮)


	/**
	 * 根据关键字搜索列表
	 * @param keywords
	 * @return
	 */
	private Map searchList(Map searchMap){
		Map map=new HashMap();
		HighlightQuery query=new SimpleHighlightQuery();
		HighlightOptions highlightOptions=new HighlightOptions().addField("item_title");//设置高亮的域
		highlightOptions.setSimplePrefix("<em style='color:red'>");//高亮前缀 
		highlightOptions.setSimplePostfix("</em>");//高亮后缀
		query.setHighlightOptions(highlightOptions);//设置高亮选项
		//按照关键字查询
		Criteria criteria=new Criteria("item_keywords").is(searchMap.get("keywords"));
		query.addCriteria(criteria);
		HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query, TbItem.class);
		for(HighlightEntry<TbItem> h: page.getHighlighted()){//循环高亮入口集合
			TbItem item = h.getEntity();//获取原实体类			
			if(h.getHighlights().size()>0 && h.getHighlights().get(0).getSnipplets().size()>0){
				item.setTitle(h.getHighlights().get(0).getSnipplets().get(0));//设置高亮的结果
			}			
		}		
		map.put("rows",page.getContent());
		return map;
	}

修改ItemSearchServiceImpl 的search方法,调用刚才编写的私有方法

	@Override
	public Map<String, Object> search(Map searchMap) {
		Map<String,Object> map=new HashMap<>();	
		//1.查询列表		
		map.putAll(searchList(searchMap));
		return map;
	}

1.3前端代码

我们测试后发现高亮显示的html代码原样输出,这是angularJS为了防止html攻击采取的安全机制。我们如何在页面上显示html的结果呢?我们会用到$sce服务的trustAsHtml方法来实现转换。
因为这个功能具有一定通用性,我们可以通过angularJS的过滤器来简化开发,这样只写一次,调用的时候就非常方便了,看代码:
(1)修改base.js

// 定义模块:
var app = angular.module("pinyougou",[]);
/*$sce服务写成过滤器*/
app.filter('trustHtml',['$sce',function($sce){
    return function(data){
        return $sce.trustAsHtml(data);
    }
}]);

(2)使用过滤器
ng-bind-html指令用于显示html内容
竖线 |用于调用过滤器

<div class="attr" ng-bind-html="item.title | trustHtml"></div>

|就是竖线,看起来有点斜是因为字体原因。

2.搜索业务规则分析

2.1需求分析

我们今天要完成的目标是在关键字搜索的基础上添加面板搜索功能。
面板上有商品分类、品牌、各种规格和价格区间等条件
在这里插入图片描述
业务规则:
(1)当用户输入关键字搜索后,除了显示列表结果外,还应该显示通过这个关键字搜索到的记录都有哪些商品分类。
(2)根据第一个商品分类查询对应的模板,根据模板查询出品牌列表
(3)根据第一个商品分类查询对应的模板,根据模板查询出规格列表
(4)当用户点击搜索面板的商品分类时,显示按照这个关键字查询结果的基础上,筛选此分类的结果。
(5)当用户点击搜索面板的品牌时,显示在以上结果的基础上,筛选此品牌的结果
(6)当用户点击搜索面板的规格时,显示在以上结果的基础上,筛选此规格的结果
(7)当用户点击价格区间时,显示在以上结果的基础上,按价格进行筛选的结果
(8)当用户点击搜索面板的相应条件时,隐藏已点击的条件。

2.2实现思路

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

3.查询分类列表

3.1需求分析

根据搜索关键字查询商品分类名称列表
在这里插入图片描述

3.2后端代码

修改SearchItemServiceImpl.java创建方法

	/**
	 * 查询分类列表  
	 * @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.按关键字查询(高亮显示)
		......		
		//2.根据关键字查询商品分类
		List categoryList = searchCategoryList(searchMap);
		map.put("categoryList",categoryList);		
		return map;
	}

3.3前端代码

修改search.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>

4.缓存品牌和规格数据

4.1需求分析

将商品分类数据、品牌数据、和规格数据都放入Redis存储。
(1)当用户进入运营商后台的商品分类页面时,将商品分类数据放入缓存(Hash)。以分类名称作为key ,以模板ID作为值
(2)当用户进入运营商后台的模板管理页面时,分别将品牌数据和规格数据放入缓存(Hash)。以模板ID作为key,以品牌列表和规格列表作为值。

4.2缓存商品分类数据

将商品分类表存入缓存 pinyougou-sellergoods-service工程需要引入pinyougou-common工程依赖。
修改pinyougou-sellergoods-service的ItemCatServiceImpl.java,添加redisTemplate

	@Autowired
	private RedisTemplate redisTemplate;
	
	/**
	 * 根据上级ID查询列表
	 */
	@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);		
	}

4.3缓存品牌和规格列表数据

(1)修改pinyougou-sellergoods-service的TypeTemplateServiceImpl.java

	@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);		
		}
	}

(2)在查询分页方法(findPage) 时调用此方法

public PageResult findPage(TbTypeTemplate typeTemplate, int pageNum, int pageSize) {
		......	
		saveToRedis();//存入数据到缓存
		return new PageResult(page.getTotal(), page.getResult());
}

这样在增删改后会自动调用该方法.

4.4加载缓存数据

启动redis ,运行运营商管理后台,打开商品分类和模板管理页,即可将数据放入缓存中。

5.显示品牌和规格数据

5.1需求分析

在搜索面板区域显示第一个分类的品牌和规格列表
在这里插入图片描述

5.2后端代码

修改ItemSearchServiceImpl.java ,增加方法

	@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方法调用此方法

	@Override
	public Map<String, Object> search(Map searchMap) {
		Map<String,Object> map=new HashMap<>();			
		//1.按关键字查询(高亮显示)
		//2.根据关键字查询商品分类
		//3.查询品牌和规格列表
		if(categoryList.size()>0){
			map.putAll(searchBrandAndSpecList(categoryList.get(0)));
		}
		return map;
	}

5.3前端代码

5.3.1获取品牌列表
修改页面search.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">
				{{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>

5.3.2获取规格列表
修改页面search.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>

6.过滤条件构建

6.1需求分析

点击搜索面板上的分类、品牌和规格,实现查询条件的构建。查询条件以面包屑的形式显示。
当面包屑显示分类、品牌和规格时,要同时隐藏搜索面板对应的区域。
用户可以点击面包屑上的X 撤销查询条件。撤销后显示搜索面包相应的区域。

6.2添加搜索项

6.2.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.2.2点击搜索项
修改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.2.3显示面包屑
修改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.3撤销搜索项

6.3.1撤销搜索项的方法
修改pinyougou-search-web工程searchController.js

	//移除复合搜索条件
	$scope.removeSearchItem=function(key){
		if(key=="category" ||  key=="brand"){//如果是分类或品牌
			$scope.searchMap[key]="";		
		}else{//否则是规格
			delete $scope.searchMap.spec[key];//移除此属性
		}	
	}

6.3.2页面调用方法
pinyougou-search-web工程的search.html

<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>					
</ul>

6.4隐藏查询面板

6.4.1隐藏分类面板
修改search.html

<div class="type-wrap" ng-if="resultMap.categoryList!=null && searchMap.category==''">
	<div class="fl key">商品分类</div>
	......
</div>

6.4.2隐藏品牌面板
修改search.html

<div class="type-wrap logo" ng-if="resultMap.brandList!=null && searchMap.brand==''">
	<div class="fl key brand">品牌</div>
	.......
</div>

6.4.3隐藏规格面板
修改search.html

<div class="type-wrap" ng-repeat="spec in resultMap.specList"  ng-if="searchMap.spec[spec.text]==null">
	<div class="fl key">{{spec.text}}</div>
	......
</div>

6.5提交查询
修改searchController.js 在添加和删除筛选条件时自动调用搜索方法

	//添加复合搜索条件
	$scope.addSearchItem=function(key,value){
		if(key=="category" ||  key=="brand"){//如果是分类或品牌
			$scope.searchMap[key]=value;		
		}else{//否则是规格
			$scope.searchMap.spec[key]=value;
		}	
		$scope.search();//执行搜索 
	}	
	//移除复合搜索条件
	$scope.removeSearchItem=function(key){
		if(key=="category" ||  key=="brand"){//如果是分类或品牌
			$scope.searchMap[key]="";		
		}else{//否则是规格
			delete $scope.searchMap.spec[key];//移除此属性
		}	
		$scope.search();//执行搜索 
	}

7.过滤查询

7.1需求分析

根据上一步构建的查询条件,实现分类、品牌和规格的过滤查询
在这里插入图片描述

7.2代码实现

7.2.1分类过滤
修改pinyougou-search-service工程的SearchItemServiceImpl.java

	/**
	 * 根据关键字搜索列表
	 * @param keywords
	 * @return
	 */
	private Map searchList(Map searchMap){
		.......
		//1.1关键字查询......	
		//1.2按分类筛选
		if(!"".equals(searchMap.get("category"))){			
			Criteria filterCriteria=new Criteria("item_category").is(searchMap.get("category"));
			FilterQuery filterQuery=new SimpleFilterQuery(filterCriteria);
			query.addFilterQuery(filterQuery);
		}
		//高亮显示处理.....		
	}

7.2.2品牌过滤
修改pinyougou-search-service工程的SearchItemServiceImpl.java

	/**
	 * 根据关键字搜索列表
	 * @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);
		}
		//高亮显示处理...............
	}

7.2.3规格过滤
实现思路:规格有多项,需要循环过滤。循环规格查询条件,根据key得到域名城,根据value设置过滤条件。
修改pinyougou-search-service工程的SearchItemServiceImpl.java

	/**
	 * 根据关键字搜索列表
	 * @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);				
				}			
		}
		//高亮显示处理.....
	}

7.2.4根据分类查询品牌规格列表

	
	@Override
	public Map<String, Object> search(Map searchMap) {
		Map<String,Object> map=new HashMap<>();		
		//1.按关键字查询(高亮显示)
......		
		//2.根据关键字查询商品分类	
......	
		//3.查询品牌和规格列表
		String categoryName=(String)searchMap.get("category");
		if(!"".equals(categoryName)){//如果有分类名称
			map.putAll(searchBrandAndSpecList(categoryName));			
		}else{//如果没有分类名称,按照第一个查询
			if(categoryList.size()>0){
				map.putAll(searchBrandAndSpecList(categoryList.get(0)));
			}
		}
		return map;
	}
1.2. 结构化一下 1.3. 图形化一下 1.3.1. 运营商后台 1.3.2. 商家后台 1.3.3. 网页前台 参考京东 2. 技术选型 前端:angularJS + Bootstrap 后台:SSM( springmvc+spring+mybatis) 数据库:mysql,使用mycat读写分离 开发模式:SOA 服务中间件:dubbox,需要和zookeeper配合使用 注册中心:zookeeper 消息中间件:Activemq,使用spring-jms 负载均衡:nginx 搜索solr集群(solrCloud),配合zookeeper搭建, 使用spring-data-solor 缓存:redis集群,使用spring-data-redis 图片存储:fastDFS集群 网页静态化:freemarker 单点登录:cas 权限管理:SpringSecurity, 跨域:cros 支付:微信扫描 短信验证:阿里大于 密码加密:BCrypt 富文本:KindEditor 事务:声明式事务 任务调度:spring task 所有的技术,都可能涉及到为什么用?怎么用?用的过程中有什么问题? 3. 框架搭建 3.1. 前端 理解baseControler.js、base.js、base_pagination.js,以及每一个xxxController.js里面都公共的做了些什么。 baseControler.js 分页配置 列表刷新 处理checkBox勾选 xxxControler.js 自动生成增删改查 base_pagination.js 带分页 base.js 不带分页 3.2. dao 使用了mybatis逆向工程 4. 模块开发 逐个模块开发就好 4.1. 学会评估模块难不难 一个模块难不难从几方面考虑。 涉及几张表? 1,2张表的操作还是没有什么难度的。 涉及哪些功能? 增删改查,批量删除。 前端展示? 分页列表、树形、面包屑、三级联动、内容格式化。 4.2. 举几个简单模块的例子 4.2.1. 品牌管理 单表 分页、新增、删除、修改 4.2.2. 规格管理 2张表 分页、新增、删除、修改、显示优化(显示列表内容的一部分) 4.2.3. 模板管理 2张表 分页、新增、删除、修改、显示优化(显示列表内容的一部分) 4.2.4. 分类管理 单表 4.2.5. 商家审核 单表 4.3. 举一个复杂模块 4.3.1. 商品新增 需要插入3张表,tb_goods、tb_goods_desc、tb_item 前端:三级联动、富文本、图片上传、动态生成内容 4.3.2. 商品修改 需要从3张表获取数据,然后进行回显。 4.4. 典型模块设计 4.4.1. 管理后台 商品新增、商品修改 4.4.2. 前台页面 搜索模块实现 购物车模块实现 支付模块实现 秒杀模块实现 5. 开发过程中问题&优化 1.1. 登录 单点登录怎么实现 session怎么共享 1.2. 缓存 哪些场景需要用到redis redis存储格式的选择 怎么提高redis缓存利用率 缓存如何同步 1.3. 图片上传 图片怎么存储 图片怎么上传 1.4. 搜索 ​ 怎么实现 数据量大、 并发量高的搜索 怎么分词 1.5. 消息通知 ​ 哪些情况用到activeMq 1.6. 优化 seo怎么优化 怎么加快访问速度 1.7. 秒杀 ​ 怎么处理高并发 ​ 秒杀过程中怎么控制库存
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值