商品详情页生成
需求分析
当系统审核完成商品,需要将商品详情页进行展示,那么采用静态页面生成的方式生成,并部署到高性能的web服务器中进行访问是比较合适的。所以,开发流程如下图所示:
此处MQ我们使用Rabbitmq即可。
执行步骤解释:
- 系统管理员(商家运维人员)修改或者审核商品的时候, 会更改数据库中商品上架状态并发送商品id给rabbitMq中的上架交换器
- 上架交换器会将商品id发给静态页生成队列
- 静态页微服务设置监听器, 监听静态页生成队列, 根据商品id获取商品详细数据并使用thymeleaf的模板技术生成静态页
商品静态化微服务创建
需求分析
该微服务只用于生成商品静态页,不做其他事情。
搭建项目
(1)在changgou-service下创建一个名称为changgou_service_page的项目,作为静态化页面生成服务
(2)changgou-service-page中添加起步依赖,如下
<dependencies> <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> </dependencies>
(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)创建系统启动类
@SpringBootApplication @EnableEurekaClient @EnableFeignClients(basePackages = "com.changgou.goods.feign") public class PageApplication { public static void main(String[] args) { SpringApplication.run(PageApplication.class); } }
生成静态页
需求分析
页面发送请求,传递要生成的静态页的商品的SpuID.后台controller 接收请求,调用thyemleaf的原生API生成商品静态页。
上图是要生成的商品详情页,从图片上可以看出需要查询SPU的3个分类作为面包屑显示,同时还需要查询SKU和SPU信息。
Feign创建
一会儿需要查询SPU和SKU以及Category,所以我们需要先创建Feign,修改changgou-service-goods-api,添加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集合,代码如下:
@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信息,代码如下:
@FeignClient(name = "goods") public interface SpuFeign { @GetMapping("/spu/findSpuById/{id}") public Result<Spu> findSpuById(@PathVariable("id") String id); }
静态页生成代码
(1)创建PageService
public interface PageService { /** * 生成静态化页面 * @param spuId */ void generateItemPage(String spuId); }
(2)创建PageServiceImpl
@Service public class PageServiceImpl implements PageService { @Autowired private SpuFeign spuFeign; @Autowired private CategoryFeign categoryFeign; @Autowired private SkuFeign skuFeign; @Value("${pagepath}") private String pagepath; @Autowired private TemplateEngine templateEngine; @Override public void generateItemPage(String spuId) { //获取context对象,用于存放商品详情数据 Context context = new Context(); Map<String, Object> itemData = this.findItemData(spuId); context.setVariables(itemData); //获取商品详情页生成的指定位置 File dir = new File(pagepath); //判断商品详情页文件夹是否存在,不存在则创建 if (!dir.exists()){ dir.mkdirs(); } //定义输出流,进行文件生成 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 { //关闭流 try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } //获取静态化页面数据 private Map<String, Object> findItemData(String spuId) { Map<String,Object> resultMap = new HashMap<>(); //获取spu信息 Result<Spu> spuResult = spuFeign.findSpuById(spuId); Spu spu = spuResult.getData(); resultMap.put("spu",spu); //获取图片信息 if (spu != null){ if(!StringUtils.isEmpty(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队列,并绑定到商品上架交换机
@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队列,获取消息,并生成静态化页面
@Component public class PageListener { @Autowired private PageService pageService; @RabbitListener(queues = RabbitMQConfig.PAGE_CREATE_QUEUE) public void receiveMessage(String spuId){ System.out.println("生成商品详情页面,商品id为: "+spuId); //生成静态化页面 pageService.generateItemPage(spuId); } }
(5)更新canal中消息队列配置类与Page服务一致
(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")); }
模板填充
(1)面包屑数据
修改item.html,填充三个分类数据作为面包屑,代码如下:
(2)商品图片
修改item.html,将商品图片信息输出,在真实工作中需要做空判断,代码如下:
(3)规格输出
(4)默认SKU显示
静态页生成后,需要显示默认的Sku,我们这里默认显示第1个Sku即可,这里可以结合着Vue一起实现。可以先定义一个集合,再定义一个spec和sku,用来存储当前选中的Sku信息和Sku的规格,代码如下:
页面显示默认的Sku信息
(5)记录选中的Sku
在当前Spu的所有Sku中spec值是唯一的,我们可以根据spec来判断用户选中的是哪个Sku,我们可以在Vue中添加代码来实现,代码如下:
添加规格点击事件
(6)样式切换
点击不同规格后,实现样式选中,我们可以根据每个规格判断该规格是否在当前选中的Sku规格中,如果在,则返回true添加selected样式,否则返回false不添加selected样式。
Vue添加代码:
页面添加样式绑定,代码如下:
启动测试
启动eurekea服务端,数据监控服务,商品服务,静态页生成服务. 将spu表中status字段从0更新为1.
基于nginx完成静态页访问
生成的静态页我们可以先放到changgou-service-page工程中,后面项目实战的时候可以挪出来放到Nginx指定发布目录。一会儿我们将生成的静态页放到resources/templates/items目录下,所以请求该目录下的静态页需要直接到该目录查找即可。