前言
网站前台Solr解决方案+网页静态化
因为是根据大佬的项目点滴做起,如果看到此博客侵犯利益,请告知立即删除。
第11章 网站前台-搜索解决方案Solr 【3】
1.按价格区间筛选
1.1需求分析
点击搜索面板上的价格区间,实现按价格筛选
1.2前端代码
1.2.1前端控制层
(1)修改pinyougou-search-web的searchController.js 搜索条件的定义
$scope.searchMap={'keywords':'','category':'','brand':'','spec':{},'price':'' };//搜索条件封装对象
(2)修改pinyougou-search-web的searchController.js 添加搜索项和删除搜索项的方法
//添加搜索项
$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();
}
1.2.2页面
(1)修改页面search.html ,在标签上调用方法
<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>
(2)修改search.html,增加面包屑
<li class="tag" ng-if="searchMap.price!=''" ng-click="removeSearchItem('price')">价格:{{searchMap.price}}
<i class="sui-icon icon-tb-close"></i></li>
1.3后端代码
修改pinyougou-search-service的ItemSearchServiceImpl.java
/**
* 根据关键字搜索列表
* @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);
}
}
//高亮显示处理 .....
}
2.搜索结果分页
2.1需求分析
在上述功能基础上实现分页查询
2.2后端代码
修改pinyougou-search-service工程ItemSearchServiceImpl.java
/**
* 根据关键字搜索列表
* @param keywords
* @return
*/
private Map searchList(Map searchMap){
.....
//1.1关键字查询
.....
//1.2按分类筛选
.....
//1.3按品牌筛选
.....
//1.4过滤规格
.....
//1.5按价格筛选
......
//1.6 分页查询
Integer pageNo= (Integer) 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 map=new HashMap<>();
map.put("rows", page.getContent());
map.put("totalPages", page.getTotalPages());//返回总页数
map.put("total", page.getTotalElements());//返回总记录数
return map;
}
2.3前端代码
2.3.1构建分页标签
需求:
(1)如果我们需要修改默认页码和每页记录数,可以修改searchController.js的searchMap,为搜索对象添加属性。
//搜索条件封装对象
$scope.searchMap={'keywords':'','category':'','brand':'','spec':{},'price':'','pageNo':1,'pageSize':40 };
(2)修改searchController.js 实现页码的构建
//构建分页标签(totalPages为总页数)
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.pageNo+2;
}
}
//循环产生页码标签
for(var i=firstPage;i<=lastPage;i++){
$scope.pageLabel.push(i);
}
}
(3)在查询后调用此方法
//搜索
$scope.search=function(){
searchService.search( $scope.searchMap ).success(
function(response){
$scope.resultMap=response;//搜索返回的结果
buildPageLabel();//调用
}
);
}
(4)修改search.html, 循环产生页码
<ul>
<li class="prev disabled">
<a href="#">«上一页</a>
</li>
<li ng-repeat="p in pageLabel">
<a href="#" >{{p}}</a>
</li>
<li class="next">
<a href="#">下一页»</a>
</li>
</ul>
<div>
<span> 共{{resultMap.totalPages}}页 </span>
.......
</div>
(5)显示总条数
搜索结果:{{resultMap.total}}条记录
2.3.2提交页码查询
(1)在searchController.js增加方法,修改页码执行查询
//根据页码查询
$scope.queryByPage=function(pageNo){
//页码验证
if(pageNo<1 || pageNo>$scope.resultMap.totalPages){
return;
}
$scope.searchMap.pageNo=pageNo;
$scope.search();
}
(2)修改页码调用方法
<div class="sui-pagination pagination-large">
<ul>
<li class="prev">
<a href="#" ng-click="queryByPage(searchMap.pageNo-1)">«</a>
</li>
<li ng-repeat="p in pageLabel">
<a href="#" ng-click="queryByPage(p)">{{p}}</a>
</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>
(3)修改search方法, 在执行查询前,转换为int类型,否则提交到后端有可能变成字符串
//搜索
$scope.search=function(){
$scope.searchMap.pageNo= parseInt($scope.searchMap.pageNo) ;
.......
}
2.3.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 ,显示前5页
lastPage=5;
$scope.firstDot=false;//前面没点
}else if( $scope.searchMap.pageNo>= $scope.resultMap.totalPages-2 ){//显示后5页
firstPage=$scope.resultMap.totalPages-4;
$scope.lastDot=false;//后边没点
}else{ //显示以当前页为中心的5页
firstPage=$scope.searchMap.pageNo-2;
lastPage=$scope.searchMap.pageNo+2;
}
}else{
$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 class="dotted" ng-if="lastDot==true"><span>...</span></li>
2.3.4页码不可用样式
修改searchController.js增加方法
//判断当前页为第一页
$scope.isTopPage=function(){
if($scope.searchMap.pageNo==1){
return true;
}else{
return false;
}
}
//判断当前页是否为最后一页
$scope.isEndPage=function(){
if($scope.searchMap.pageNo==$scope.resultMap.totalPages){
return true;
}else{
return false;
}
}
(2)修改页面
<ul>
<li class="prev {{isTopPage()?'disabled':''}}">
<a href="#" ng-click="queryByPage(searchMap.pageNo-1)">«上一页</a>
</li>
<li ng-repeat="p in resultMap.pageLabel">
<a href="#" ng-click="queryByPage(p)">{{p}}</a>
</li>
<li class="dotted"><span>...</span></li>
<li class="next {{isEndPage()?'disabled':''}}">
<a href="#" ng-click="queryByPage(searchMap.pageNo+1)">下一页»</a>
</li>
</ul>
2.3.5搜索起始页码处理
测试:如果我们先按照“手机”关键字进行搜索,得出的页数是19页,然后我们点击第18页进行查询,然后我们再根据“三星”关键字搜索,会发现没有结果显示。是因为当前页仍然为18,而三星的结果只有4页,所以无法显示。我们需要在每次点击查询时将页码设置为1 。
修改搜索按钮,调用搜索前将起始页码设置为1
<button class="sui-btn btn-xlarge btn-danger" ng-click="searchMap.pageNo=1;search()" type="button">搜索</button>
3.多关键字搜索
3.1多关键字搜索规则
我们之前测试都是使用单一的词(比如手机)来进行搜索,如果我们输入的关键字是一个复合的词组(比如三星手机),那solr如何进行搜索呢?
经过测试:
我搜索“三星”是148条记录
我搜索“手机”是727条记录
我搜索“三星手机”是740条记录
经过查看,发现结果中也包含了关键字只有三星和手机的记录,由此得出结论,solr在搜索时是将搜索关键字进行分词,然后按照或的关系来进行搜索的。
你现在可能要说,为什么不是并的关系而是或的关系呢?如果你是电商网站的运营者,肯定希望给用户更多的选择,因为如果采用并的关系来进行搜索时极有可能查询到很少的记录甚至查询不到任何记录。 另外这里还有他智能的排序策略,就是按照关键字匹配度来进行排序,也就是说如果记录中同时包含了三星和手机,那么这部分数据会排列在前面显示,而只包含三星和只包含手机的记录会显示在后边。
3.2多关键字搜索空格处理
有些用户会在关键字中间习惯性的输入一些空格,而这个空格输入后,很有可能查询不到结果了。我们测试输入“三星 手机”结果并没有查询到任何结果。所以我们还要对空格至于做一下处理,删除关键字中的空格
修改pinyougou-search-service的ItemSearchServiceImpl.java
@Override
public Map<String, Object> search(Map searchMap) {
//关键字空格处理
String keywords = (String) searchMap.get(“keywords”);
searchMap.put(“keywords”, keywords.replace(" ", “”));
…
}
4.排序
4.1按价格排序
实现价格的排序(升降序可切换)
4.1.1后端代码
修改pinyougou-search-service的ItemSearchServiceImpl.java 添加排序的代码
/**
* 根据关键字搜索列表
* @param keywords
* @return
*/
private Map searchList(Map searchMap){
........
//1.7排序
String sortValue= (String) searchMap.get("sort");//ASC DESC
String sortField= (String) searchMap.get("sortField");//排序字段
if(sortValue!=null && !sortValue.equals("")){
if(sortValue.equals("ASC")){
Sort sort=new Sort(Sort.Direction.ASC, "item_"+sortField);
query.addSort(sort);
}
if(sortValue.equals("DESC")){
Sort sort=new Sort(Sort.Direction.DESC, "item_"+sortField);
query.addSort(sort);
}
}
//高亮显示处理
......
return map;
}
4.1.2前端代码
(1)修改searchController.js的searchMap, 增加排序
//搜索对象
$scope.searchMap={'keywords':'','category':'','brand':'','spec':{},'price':'','pageNo':1,'pageSize':40 ,
'sortField':'','sort':'' };
(2)修改searchController.js ,增加方法实现查询
//设置排序规则
$scope.sortSearch=function(sortField,sort){
$scope.searchMap.sortField=sortField;
$scope.searchMap.sort=sort;
$scope.search();
}
(3)修改页面search.html
<div class="navbar-inner filter">
<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>
</div>
4.2按上架时间排序
4.2.1增加域定义
修改solrhome的schema.xml 添加域定义
<field name="item_updatetime" type="date" indexed="true" stored="true" />
4.2.2修改实体类
为updatetime属性添加注解
@Field("item_updatetime")
private Date updateTime;
4.2.3重新运行导入程序
重新启动solr
安装pinyougou-pojo
重新运行pinyougou-solr-util
4.2.4修改页面
修改search.html
<li>
<a href="#" ng-click="sortSearch('updatetime','DESC')">新品</a>
</li>
4.3按销量排序(实现思路TODO:待写)
(1)增加域item_salecount 用于存储每个SKU的销量数据
(2)编写定时器程序,用于更新每个SKU的销量数据(查询近1个月的销量数据,不是累计数据)
(3)定时器每天只需执行一次,可以设定为凌晨开始执行。
定时器可以使用spring task技术来实现,学员们自行百度。
4.4按评价排序(实现思路)
与按销量排序思路基本相同,有一个细节需要注意:
评论分为好评、中评、差评,我们不能简单地将评论数相加,而是应该根据每种评论加权进行统计。比如好评的权重是3 ,中评的权重是1,而差评的权重是 -3,这样得出的是评价的综合得分。
5.隐藏品牌列表
5.1需求分析
需求:如果用户输入的是品牌的关键字,则隐藏品牌列表
5.2代码实现
(1)修改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;
}
(2)修改页面
<div class="type-wrap logo" ng-if="resultMap.brandList!=null&& searchMap.brand=='' && keywordsIsBrand()==false">
<div class="fl key brand">品牌</div>
.........
</div>
6.搜索页与首页对接
6.1需求分析
用户在首页的搜索框输入关键字,点击搜索后自动跳转到搜索页查询
6.2代码实现
6.2.1首页传递关键字
修改pinyougou-portal-web的contentController.js
//搜索跳转
$scope.search=function(){
location.href="http://localhost:9104/search.html#?keywords="+$scope.keywords;
}
修改pinyougou-portal-web的index.html
<div class="input-append">
<input type="text" id="autocomplete" type="text" ng-model="keywords" class="input-error input-xxlarge" />
<button class="sui-btn btn-xlarge btn-danger" ng-click="search()" type="button">搜索</button>
</div>
6.2.2搜索页接收关键字
修改pinyougou-search-web的searchController.js
添加location服务用于接收参数
app.controller('searchController',function($scope,$location,searchService){
......
接收参数并进行查询
//加载查询字符串
$scope.loadkeywords=function(){
$scope.searchMap.keywords= $location.search()['keywords'];
$scope.search();
}
7.更新索引库
7.1需求分析
在进行商品审核后更新到solr索引库,在商品删除后删除solr索引库中相应的记录.
7.2查询审核商品(SKU)列表
7.2.1服务接口层
修改pinyougou-sellergoods-interface的GoodsService.java,新增方法
/**
* 根据商品ID和状态查询Item表信息
* @param goodsId
* @param status
* @return
*/
public List<TbItem> findItemListByGoodsIdandStatus(Long[] goodsIds, String status );
7.2.2服务实现层
修改 pinyougou-sellergoods-service工程GoodsServiceImpl.java
@Override
public List<TbItem> findItemListByGoodsIdandStatus(Long[] goodsIds, String status) {
TbItemExample example=new TbItemExample();
com.pinyougou.pojo.TbItemExample.Criteria criteria = example.createCriteria();
criteria.andGoodsIdIn(Arrays.asList(goodsIds));
criteria.andStatusEqualTo(status);
return itemMapper.selectByExample(example);
}
7.3更新到索引库
7.3.1服务接口层
修改pinyougou-search-interface的ItemSearchService.java
/**
* 导入数据
* @param list
*/
public void importList(List list);
7.3.2服务实现层
修改pinyougou-search-service的ItemSearchServiceImpl.java
@Override
public void importList(List list) {
solrTemplate.saveBeans(list);
solrTemplate.commit();
}
7.3.3控制层
(1)pinyougou-manager-web工程引入依赖pinyougou-search-interface
(2)修改pinyougou-manager-web工程的GoodsController.java
@Reference
private ItemSearchService itemSearchService;
@RequestMapping("/updateStatus")
public Result updateStatus(Long[] ids,String status){
try {
goodsService.updateStatus(ids, status);
//按照SPU ID查询 SKU列表(状态为1)
if(status.equals("1")){//审核通过
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, "修改状态失败");
}
}
7.4商品删除同步索引数据
7.4.1服务接口层
修改pinyougou-search-interface的ItemSearchService.java
/**
* 删除数据
* @param ids
*/
public void deleteByGoodsIds(List goodsIdList);
7.4.2服务实现层
@Override
public void deleteByGoodsIds(List goodsIdList) {
System.out.println("删除商品ID"+goodsIdList);
Query query=new SimpleQuery();
Criteria criteria=new Criteria("item_goodsid").in(goodsIdList);
query.addCriteria(criteria);
solrTemplate.delete(query);
solrTemplate.commit();
}
7.4.3控制层
修改pinyougou-manager-web的GoodsController.java
/**
* 批量删除
* @param ids
* @return
*/
@RequestMapping("/delete")
public Result delete(Long [] ids){
try {
goodsService.delete(ids);
itemSearchService.deleteByGoodsIds(Arrays.asList(ids));
return new Result(true, "删除成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false, "删除失败");
}
}
第12章 网站前台-网页静态化解决方案-Freemarker
1.网页静态化技术Freemarker
1.1为什么要使用网页静态化技术
网页静态化解决方案在实际开发中运用比较多,例如新闻网站,门户网站中的新闻频道或者是文章类的频道。
对于电商网站的商品详细页来说,至少几百万个商品,每个商品又有大量的信息,这样的情况同样也适用于使用网页静态化来解决。
网页静态化技术和缓存技术的共同点都是为了减轻数据库的访问压力,但是具体的应用场景不同,缓存比较适合小规模的数据,而网页静态化比较适合大规模且相对变化不太频繁的数据。另外网页静态化还有利于SEO。
另外我们如果将网页以纯静态化的形式展现,就可以使用Nginx这样的高性能的web服务器来部署。Nginx可以承载5万的并发,而Tomcat只有几百。关于Nginx我们在后续的课程中会详细讲解。
今天我们就研究网页静态化技术----Freemarker 。
2.商品详情页-数据显示
2.1需求分析
运用Freemarker技术来实现商品详细页的静态化。通过地址栏输入某地址,如下形式
http://localhost:9101/gen_item.do?goodsId=149187842867952
能在本地电脑某目录生成商品详细页,页面的名称为商品goodsId.html
2.2工程搭建
2.2.1服务接口层
创建pinyougou-page-interface工程,创建com.pinyougou.page.service包,包下创建接口
/**
* 商品详细页接口
* @author Administrator
*
*/
public interface ItemPageService {
/**
* 生成商品详细页
* @param goodsId
*/
public boolean genItemHtml(Long goodsId);
}
2.2.2服务实现层
(1)创建war工程pinyougou-page-service
(2)pom.xml引入依赖 参见其它服务工程, 另外添加freemarker依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
(3)添加web.xml 参见其它服务工程
(4)spring配置文件 参见其它服务工程 ,另外配置:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/ftl/" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
(5)创建属性文件
内容为:
pagedir=d:\\item\\
用于配置网页生成目录
(6)建立com.pinyougou.page.service.impl包,包下建立类
@Service
public class ItemPageServiceImpl implements ItemPageService {
@Value("${pagedir}")
private String pagedir;
@Autowired
private FreeMarkerConfig freeMarkerConfig;
@Autowired
private TbGoodsMapper goodsMapper;
@Autowired
private TbGoodsDescMapper goodsDescMapper;
@Override
public boolean genItemHtml(Long goodsId){
try {
Configuration configuration = freeMarkerConfig.getConfiguration();
Template template = configuration.getTemplate("item.ftl");
Map dataModel=new HashMap<>();
//1.加载商品表数据
TbGoods goods = goodsMapper.selectByPrimaryKey(goodsId);
dataModel.put("goods", goods);
//2.加载商品扩展表数据
TbGoodsDesc goodsDesc = goodsDescMapper.selectByPrimaryKey(goodsId);
dataModel.put("goodsDesc", goodsDesc);
Writer out=new FileWriter(pagedir+goodsId+".html");
template.process(dataModel, out);
out.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
(7)将item.html拷贝至web-inf/ftl下 ,修改扩展名为ftl ,将商品名称用插值代替
<div class="sku-name">
<h4>${goods.goodsName}</h4>
</div>
(8)在D盘建立文件夹item,将必要的样式表和Js拷贝到此目录下,此目录为生成的目录
2.2.3运营商管理后台
(1)pinyougou-manager-web引入依赖pinyougou-page-interface
(2)在GoodsController.java中新增方法
@Reference(timeout=40000)
private ItemPageService itemPageService;
/**
* 生成静态页(测试)
* @param goodsId
*/
@RequestMapping("/genHtml")
public void genHtml(Long goodsId){
itemPageService.genItemHtml(goodsId);
}
2.3商品详情页模板构建
2.3.1模板模块化引入
此时我们的item.ftl内容较多,当我们编辑时不容易快速找到编辑的位置,所以我们将头部分拆分到head.ftl ,将尾部拆分到foot.ftl ,用include指令在item.ftl中引入 。
2.3.2生成基本数据
在模板中找到合适的位置,用插值替换静态文本
<div class="news"><span>${goods.caption}</span></div>
<div class="fl price"><i>¥</i><em>${goods.price}</em><span>降价通知</span></div>
<div class="intro-detail"><!-- 商品详情 --> ${goodsDesc.introduction}</div>
<div id="two" class="tab-pane"><p>${goodsDesc.packageList}</p></div>
<div id="three" class="tab-pane"><p>${goodsDesc.saleService}</p></div>
运行控制层代码,测试生成效果
http://localhost:9101/goods/genHtml.do?goodsId=149187842867960
2.3.3生成图片列表
编辑模板文件
<#--图片列表 -->
<#assign imageList=goodsDesc.itemImages?eval />
这一句要转换图片列表的json字符串
图片部分的代码
<!--默认第一个预览-->
<div id="preview" class="spec-preview">
<span class="jqzoom">
<#if (imageList?size>0)>
<img jqimg="${imageList[0].url}" src="${imageList[0].url}" width="400px" height="400px" />
</#if>
</span>
</div>
<!--下方的缩略图--><div class="spec-scroll">
<div class="items">
<ul>
<#list imageList as item>
<li><img src="${item.url}" bimg="${item.url}" "preview(this)" /></li>
</#list>
</ul>
</div>
</div>
生成效果如下:
2.3.4生成扩展属性列表
修改模板 首先进行json转换
<#--扩展属性列表 -->
<#assign customAttributeList=goodsDesc.customAttributeItems?eval />
显示扩展属性数据,如果扩展属性为空则不显示此条数据
<#list customAttributeList as item>
<#if item.value??>
<li>${item.text} :${item.value}</li>
</#if>
</#list>
2.3.5生成规格列表
修改模板 转换规格列表
<#--规格列表 -->
<#assign specificationList=goodsDesc.specificationItems?eval />
此时,我们需要使用嵌套循环
<#list specificationList as specification>
<dl>
<dt>
<div class="fl title">
<i>${specification.attributeName}</i>
</div>
</dt>
<#list specification.attributeValue as item>
<dd><a href="javascript:;" >${item}</a></dd>
</#list>
</dl>
</#list>
2.3.6生成商品类型面包屑
修改ItemPageServiceImpl ,读取三级商品分类名称,加入到数据模型中
@Service
public class ItemPageServiceImpl implements ItemPageService {
@Autowired
private FreeMarkerConfig freeMarkerConfig;
@Autowired
private TbGoodsMapper goodsMapper;
@Autowired
private TbGoodsDescMapper goodsDescMapper;
@Autowired
private TbItemCatMapper itemCatMapper;
@Override
public boolean genItemHtml(Long goodsId){
try {
Configuration configuration = freeMarkerConfig.getConfiguration();
Template template = configuration.getTemplate("item.ftl");
Map dataModel=new HashMap<>();
//1.加载商品表数据
TbGoods goods = goodsMapper.selectByPrimaryKey(goodsId);
dataModel.put("goods", goods);
//2.加载商品扩展表数据
TbGoodsDesc goodsDesc = goodsDescMapper.selectByPrimaryKey(goodsId);
dataModel.put("goodsDesc", goodsDesc);
//3.商品分类
String itemCat1 = itemCatMapper.selectByPrimaryKey(goods.getCategory1Id()).getName();
String itemCat2 = itemCatMapper.selectByPrimaryKey(goods.getCategory2Id()).getName();
String itemCat3 = itemCatMapper.selectByPrimaryKey(goods.getCategory3Id()).getName();
dataModel.put("itemCat1", itemCat1);
dataModel.put("itemCat2", itemCat2);
dataModel.put("itemCat3", itemCat3);
Writer out=new FileWriter("d:\\item\\"+goodsId+".html");
template.process(dataModel, out);
out.close();
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
}
修改模板,展示商品分类面包屑
<ul class="sui-breadcrumb">
<li><a href="#">${itemCat1}</a></li>
<li><a href="#">${itemCat2}</a></li>
<li><a href="#">${itemCat3}</a></li>
</ul>
3.商品详情页-前端逻辑
3.1购买数量加减操作
3.1.1加入angularJS库
将angularJS库加入d:\item下
3.1.2前端控制层
将base.js拷贝到js目录下
在js目录下构建controller文件夹,创建itemController.js
//商品详细页(控制层)
app.controller('itemController',function($scope){
//数量操作
$scope.addNum=function(x){
$scope.num=$scope.num+x;
if($scope.num<1){
$scope.num=1;
}
}
});
在方法中控制数量不能小于1
3.1.3模板
引入js
<script type="text/javascript" src="plugins/angularjs/angular.min.js"> </script>
<script type="text/javascript" src="js/base.js"> </script>
<script type="text/javascript" src="js/controller/itemController.js"> </script>
添加指令
<body ng-app="pinyougou" ng-controller="itemController" ng-init="num=1">
调用操作数量的方法
<div class="controls">
<input autocomplete="off" type="text" value="{{num}}" minnum="1" class="itxt" />
<a href="javascript:void(0)" class="increment plus" ng-click="addNum(1)" >+</a>
<a href="javascript:void(0)" class="increment mins" ng-click="addNum(-1)">-</a>
</div>
3.2规格选择
最终我们需要实现的效果:
3.2.1前端控制层
修改itemController.js
$scope.specificationItems={};//记录用户选择的规格
//用户选择规格
$scope.selectSpecification=function(name,value){
$scope.specificationItems[name]=value;
}
//判断某规格选项是否被用户选中
$scope.isSelected=function(name,value){
if($scope.specificationItems[name]==value){
return true;
}else{
return false;
}
}
3.2.2模板
页面调用控制器的方法
<dd>
<a class="{{isSelected('${specification.attributeName}','${item}')?'selected':''}}"
ng-click="selectSpecification('${specification.attributeName}','${item}')">
${item}
<span title="点击取消选择"> </span>
</a>
</dd>
4.商品详情页-读取SKU信息
需求:当我们选择规格后,应该在页面上更新商品名称为SKU的商品标题,价格也应该为SKU的商品价格。
4.1页面生成SKU列表变量
4.1.1后端服务层
修改pinyougou-page-service的ItemPageServiceImpl.java
@Autowired
private TbItemMapper itemMapper;
@Override
public boolean genItemHtml(Long goodsId){
try {
Configuration configuration = freeMarkerConfig.getConfiguration();
Template template = configuration.getTemplate("item.ftl");
Map dataModel=new HashMap<>();
//1.加载商品表数据
//2.加载商品扩展表数据
//3.商品分类
//4.SKU列表
TbItemExample example=new TbItemExample();
Criteria criteria = example.createCriteria();
criteria.andStatusEqualTo("1");//状态为有效
criteria.andGoodsIdEqualTo(goodsId);//指定SPU ID
example.setOrderByClause("is_default desc");//按照状态降序,保证第一个为默认
List<TbItem> itemList = itemMapper.selectByExample(example);
dataModel.put("itemList", itemList);
Writer out=new FileWriter(pagedir+goodsId+".html");
template.process(dataModel, out);
out.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
4.1.2模板
修改模板:
<script>
//SKU商品列表
var skuList=[
<#list itemList as item>
{
"id":${item.id?c},
"title":"${item.title!''}",
"price":${item.price?c},
"spec": ${item.spec}
} ,
</#list>
];
</script>
测试生成,发现页面源代码中生成了变量
4.2显示SKU标题和价格
4.2.1加载默认SKU信息
修改itemController.js
//加载默认SKU
$scope.loadSku=function(){
$scope.sku=skuList[0];
$scope.specificationItems= JSON.parse(JSON.stringify($scope.sku.spec)) ;
}
修改模板item.ftl
<body ng-app="pinyougou" ng-controller="itemController" ng-init="num=1;loadSku()">
修改模板,显示标题
<div class="sku-name"><h4>{{sku.title}}</h4></div>
显示价格
<div class="summary-wrap">
<div class="fl title"><i>价 格</i></div>
<div class="fl price"><i>¥</i> <em>{{sku.price}}</em> <span>降价通知</span></div>
</div>
4.2.2选择规格更新SKU
修改itemController.js , 编写匹配对象的方法
//匹配两个对象,TODO:可将map1 map2变为string类型,之后直接对比字符串
matchObject=function(map1,map2){
for(var k in map1){
if(map1[k]!=map2[k]){
return false;
}
}
for(var k in map2){
if(map2[k]!=map1[k]){
return false;
}
}
return true;
}
编写方法,在SKU列表中查询当前用户选择的SKU
//查询SKU
searchSku=function(){
for(var i=0;i<skuList.length;i++ ){
if( matchObject(skuList[i].spec ,$scope.specificationItems ) ){
$scope.sku=skuList[i];
return ;
}
}
$scope.sku={id:0,title:’--------’,price:0};//如果没有匹配的
}
在用户选择规格后触发读取方法
//用户选择规格
$scope.selectSpecification=function(name,value){
$scope.specificationItems[name]=value;
searchSku();//读取sku
}
4.3添加商品到购物车
修改itemController.js
//添加商品到购物车,TODO:
$scope.addToCart=function(){
alert('skuid:'+$scope.sku.id);
}
修改模板:
<li><a href="#" target="_blank" class="sui-btn btn-danger addshopcar" ng-click="addToCart()">加入购物车</a></li>
5.系统模块对接
5.1运营商后台调用页面生成服务
修改pinyougou-manager-web的GoodsController.java
@RequestMapping("/updateStatus")
public Result updateStatus(Long[] ids,String status){
try {
goodsService.updateStatus(ids, status);
//按照SPU ID查询 SKU列表(状态为1)
if(status.equals("1")){//审核通过
List<TbItem> itemList = goodsService.findItemListByGoodsIdandStatus(ids, status);
//调用搜索接口实现数据批量导入
if(itemList.size()>0){
itemSearchService.importList(itemList);
}else{
System.out.println("没有明细数据");
}
//静态页生成
for(Long goodsId:ids){
itemPageService.genItemHtml(goodsId);
}
}
return new Result(true, "修改状态成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false, "修改状态失败");
}
}
5.2创建商品详细页web工程
创建war模块工程pinyougou-page-web ,将目标目录(d:\item)的文件拷贝到此工程(生成的页面不用拷贝)
5.3搜索系统与商品详细页对接
修改pinyougou-search-web 的search.html,修改点击图片的链接为http://localhost:9105/{{item.id}}.html
说明:商品详细页是静态页,所以在开发阶段我们可以使用tomcat来进行测试。部署在生产环境是部署在Nginx中。