5.畅购商品详情页
5.1需求分析
当系统审核完成商品,需要将商品详情页进行展示,那么采用静态页面生成的方式生成,并部署到高性能的web服务器中进行访问是比较合适的。所以,开发流程如下图所示:
此处MQ我们使用Rabbitmq即可。
执行步骤解释:
系统管理员(商家运维人员)修改或者审核商品的时候, 会更改数据库中商品上架状态并发送商品id给rabbitMq中的上架交换器上架交换器会将商品id发给静态页生成队列静态页微服务设置监听器, 监听静态页生成队列, 根据商品id获取商品详细数据并使用thymeleaf的模板技术生成静态页
5.2商品静态化微服务创建
5.2.1需求分析
该微服务只用于生成商品静态页,不做其他事情。
5.2.2搭建项目
(1)在changgou-service下创建一个名称为changgou_service_page的项目,作为静态化页面生成服务
(2)changgou-service-page中添加起步依赖,如下
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_service_goods_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
(3)修改application.yml的配置
server:
port: 9011
spring:
application:
name: page
rabbitmq:
host: 192.168.200.128
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: false
client:
config:
default: #配置全局的feign的调用超时时间 如果 有指定的服务配置 默认的配置不会生效
connectTimeout: 600000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接 单位是毫秒
readTimeout: 600000 # 指定的是调用服务提供者的 服务 的超时时间() 单位是毫秒
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制
enabled: true
isolation:
strategy: SEMAPHORE
# 生成静态页的位置
pagepath: D:\items
(4)创建系统启动类
package com.changgou.page;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import java.awt.print.Pageable;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.changgou.goods.feign"})
public class PageApplication {
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
}
5.3生成静态页
5.3.1需求分析
页面发送请求,传递要生成的静态页的商品的SpuID.后台controller 接收请求,调用thyemleaf的原生API生成商品静态页。
上图是要生成的商品详情页,从图片上可以看出需要查询SPU的3个分类作为面包屑显示,同时还需要查询SKU和SPU信息。
5.3.2Feign创建
一会儿需要查询SPU和SKU以及Category,所以我们需要先创建Feign,修改changgou-service-goodsapi,添加CategoryFeign,并在CategoryFeign中添加根据ID查询分类数据,代码如下:
@FeignClient(name = "goods")
public interface CategoryFeign {
@GetMapping("/category/{id}")
public Result<Category> findById(@PathVariable("id") Integer id);
}
在changgou-service-goods-api,添加SkuFeign,并添加根据SpuID查询Sku集合,代码如下:
package com.changgou.goods.feign;
import com.changgou.goods.pojo.Sku;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@FeignClient(name = "goods")
public interface SkuFeign {
@GetMapping("/sku/spu/{spuId}")
public List<Sku> findSkuListBySpuId(@PathVariable("spuId") String spuId);
}
在changgou-service-goods-api,添加SpuFeign,并添加根据SpuID查询Spu信息,代码如下:
package com.changgou.goods.feign;
import com.changgou.entity.Result;
import com.changgou.goods.pojo.Spu;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "goods")
public interface SpuFeign {
@GetMapping("/spu/findSpuById/{id}")
public Result<Spu> findSpuById(@PathVariable("id") String id);
}
5.3.3静态页生成代码
(1)创建PageService
package com.changgou.page.service;
public interface PageService {
//生成静态化页面
void generateHtml(String spuId);
}
(2)创建PageServiceImpl
package com.changgou.page.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.entity.Result;
import com.changgou.goods.feign.CategoryFeign;
import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.feign.SpuFeign;
import com.changgou.goods.pojo.Category;
import com.changgou.goods.pojo.Sku;
import com.changgou.goods.pojo.Spu;
import com.changgou.page.service.PageService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class PageServiceImpl implements PageService {
@Value("${pagepath}")
private String pagepath;
@Autowired
private TemplateEngine templateEngine;
@Override
public void generateHtml(String spuId) {
//1.获取context对象,用于存储商品的相关数据
Context context = new Context();
//获取静态化页面的相关数据
Map<String,Object> itemData= this.getItemData(spuId);
context.setVariables(itemData);
//2.获取商品详情页面的存储位置
File dir = new File(pagepath);
//3.判断当前存储位置的文件夹是否存在,如果不存在,则新建
if (!dir.exists()){
dir.mkdirs();
}
//4.定义输出流,完成文件的生成
File file = new File(dir+"/"+spuId+".html");
Writer out = null;
try{
out = new PrintWriter(file);
//生成静态化页面
/**
* 1. 模板名称
* 2.context
* 3.输出流
*/
templateEngine.process("item",context,out);
}catch (Exception e){
e.printStackTrace();
}finally {
//5.关闭流
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Autowired
private SpuFeign spuFeign;
@Autowired
private CategoryFeign categoryFeign;
@Autowired
private SkuFeign skuFeign;
//获取静态化页面的相关数据
private Map<String, Object> getItemData(String spuId) {
Map<String, Object> resultMap = new HashMap<>();
//获取spu
Spu spu = spuFeign.findSpuById(spuId).getData();
resultMap.put("spu",spu);
//获取图片信息
if (spu != null){
if (StringUtils.isNotEmpty(spu.getImages())){
resultMap.put("imageList",spu.getImages().split(","));
}
}
//获取商品的分类信息
Category category1 = categoryFeign.findById(spu.getCategory1Id()).getData();
resultMap.put("category1",category1);
Category category2 = categoryFeign.findById(spu.getCategory2Id()).getData();
resultMap.put("category2",category2);
Category category3 = categoryFeign.findById(spu.getCategory3Id()).getData();
resultMap.put("category3",category3);
//获取sku的相关信息
List<Sku> skuList = skuFeign.findSkuListBySpuId(spuId);
resultMap.put("skuList",skuList);
//获取商品规格信息
resultMap.put("specificationList",JSON.parseObject(spu.getSpecItems(),Map.class));
return resultMap;
}
}
(3)声明page_create_queue队列,并绑定到商品上架交换机
package com.changgou.page.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
//定义交换机名称
public static final String GOODS_UP_EXCHANGE="goods_up_exchange";
public static final String GOODS_DOWN_EXCHANGE="goods_down_exchange";
//定义队列名称
public static final String AD_UPDATE_QUEUE="ad_update_queue";
public static final String SEARCH_ADD_QUEUE="search_add_queue";
public static final String SEARCH_DEL_QUEUE="search_del_queue";
public static final String PAGE_CREATE_QUEUE="page_create_queue";
//声明队列
@Bean
public Queue queue(){
return new Queue(AD_UPDATE_QUEUE);
}
@Bean(SEARCH_ADD_QUEUE)
public Queue SEARCH_ADD_QUEUE(){
return new Queue(SEARCH_ADD_QUEUE);
}
@Bean(SEARCH_DEL_QUEUE)
public Queue SEARCH_DEL_QUEUE(){
return new Queue(SEARCH_DEL_QUEUE);
}
@Bean(PAGE_CREATE_QUEUE)
public Queue PAGE_CREATE_QUEUE(){
return new Queue(PAGE_CREATE_QUEUE);
}
//声明交换机
@Bean(GOODS_UP_EXCHANGE)
public Exchange GOODS_UP_EXCHANGE(){
return ExchangeBuilder.fanoutExchange(GOODS_UP_EXCHANGE).durable(true).build();
}
@Bean(GOODS_DOWN_EXCHANGE)
public Exchange GOODS_DOWN_EXCHANGE(){
return ExchangeBuilder.fanoutExchange(GOODS_DOWN_EXCHANGE).durable(true).build();
}
//队列与交换机的绑定
@Bean
public Binding GOODS_UP_EXCHANGE_BINDING(@Qualifier(SEARCH_ADD_QUEUE)Queue queue,@Qualifier(GOODS_UP_EXCHANGE)Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
@Bean
public Binding PAGE_CREATE_QUEUE_BINDING(@Qualifier(PAGE_CREATE_QUEUE)Queue queue,@Qualifier(GOODS_UP_EXCHANGE)Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
@Bean
public Binding GOODS_DOWN_EXCHANGE_BINDING(@Qualifier(SEARCH_DEL_QUEUE)Queue queue,@Qualifier(GOODS_DOWN_EXCHANGE)Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
}
(4)创建PageListener监听类,监听page_create_queue队列,获取消息,并生成静态化页面
package com.changgou.page.listener;
import com.changgou.page.config.RabbitMQConfig;
import com.changgou.page.service.PageService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class PageListener {
@Autowired
private PageService pageService;
@RabbitListener(queues = RabbitMQConfig.PAGE_CREATE_QUEUE)
public void receiveMessage(String spuId){
System.out.println("获取静态化页面的商品id,id的值为: "+spuId);
//条用业务层完成静态化页面生成
pageService.generateHtml(spuId);
}
}
(6)更新canal中对于spu表的监听类,当商品审核状态从0变1,则将当前spuId发送到消息队列
//获取最新审核商品
if ("0".equals(oldData.get("status")) && "1".equals(newData.get("status"))){
//发送商品spuId
rabbitTemplate.convertAndSend(RabbitMQConfig.GOODS_UP_EXCHANGE,"",newData.get(
"id"));
}
5.3.4模板填充
(1)面包屑数据
修改item.html,填充三个分类数据作为面包屑,代码如下
(2)商品图片
修改item.html,将商品图片信息输出,在真实工作中需要做空判断,代码如下:
(3)规格输出
<dl th:each="spec,specStat:${specificationList}">
<dt>
<div class="fl title">
<i th:text="${spec.key}"></i>
</div>
</dt>
<dd th:each="arrValue:${specStat.current.value}">
<a href="javascript:;"
th:v-bind:class="|{selected:sel('${spec.key}','${arrValue}')}|"
th:@click="|selectSpecification('${spec.key}','${arrValue}')|" >
<i th:text="${arrValue}"></i>
<span title="点击取消选择"> </span>
</a>
</dd>
</dl>
(4)默认SKU显示
静态页生成后,需要显示默认的Sku,我们这里默认显示第1个Sku即可,这里可以结合着Vue一起实现。可以先定义一个集合,再定义一个spec和sku,用来存储当前选中的Sku信息和Sku的规格,代码如下:
页面显示默认的Sku信息
在当前Spu的所有Sku中spec值是唯一的,我们可以根据spec来判断用户选中的是哪个Sku,我们可以在Vue中添加代码来实现,代码如下:
添加规格点击事件
(6)样式切换
点击不同规格后,实现样式选中,我们可以根据每个规格判断该规格是否在当前选中的Sku规格中,如果在,则返回true添加selected样式,否则返回false不添加selected样式。
Vue添加代码:
//是否绑定selected样式
sel:function(name,value){
if(this.spec == undefined){
return false;
}
if(this.spec[name]==value){
return true;
}else{
return false;
}
},
页面添加样式绑定,代码如下:
5.3.5启动测试
启动eurekea服务端,数据监控服务,商品服务,静态页生成服务. 将spu表中status字段从0更新为1.