所有代码发布在 [/hades0525/leyou]
Day13(rabbitmq)
2月13日
14:45
使用thymeleaf
thymeleaf基本信息
• 默认前缀:classpath:/templates/
• 默认后缀:.html
所以如果我们返回视图:users,会指向到 classpath:/templates/users.html
• Thymeleaf默认会开启页面缓存,提高页面并发能力。但会导致我们修改页面不会立即被展现,因此我们关闭缓存:
spring.thymeleaf.cache=false
• 把html 的名称空间,改成:xmlns:th=“” 会有语法提示
goodspage微服务微服务搭建
• pom
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>com.leyou.service</groupId><artifactId>ly-item-interface</artifactId><version>1.0.0-SNAPSHOT</version></dependency></dependencies>
• 启动类
@EnableDiscoveryClient@EnableFeignClients@SpringBootApplicationpublicclassLyPageApplication{publicstaticvoidmain(String[]args){SpringApplication.run(LyPageApplication.class);}}
• application.yml
server:port: 8084spring:application:name: page-servicethymeleaf:cache: falseeureka:client:service-url:defaultZone: http://127.0.0.1:10086/eureka
• 修改sku图片点击的跳转路径
<a :href="'/item/'+goods.id+'.html'" target="_blank">
• 在nginx.conf的中加入
location /item{proxy_pass http://192.168.0.6:8084}
• 编写跳转controller
@Controllerpublic class PageController{@GetMapping("item/{id}.html")public String toItemPage(@PathVariable("id")LongspuId){//准备模型数据//返回视图return "item";}}
封装模型数据
• 分析
我们已知的条件是传递来的spu的id,我们需要根据spu的id查询到下面的数据:
• spu信息(没写)
• spu的详情
• spu下的所有sku
• 品牌
• 商品三级分类
• 商品规格参数、规格参数组(没写)
• 查询spu接口
•GoodsApi/***根据spu的id查询spu*@paramid*@return*/@GetMapping("spu/{id}")SpuquerySpuById(@PathVariable("id")Longid);•GoodsService/***根据spu的id查询spu(sku,detail)*@paramid*@return*/@GetMapping("spu/{id}")publicResponseEntity<Spu>querySpuById(@PathVariable("id")Longid){returnResponseEntity.ok(goodsService.querySpuById(id));}•GoodsController/***根据spu的id查询spu(sku,detail)*@paramid*@return*/publicSpuquerySpuById(Longid){//查询spuSpuspu=spuMapper.selectByPrimaryKey(id);if(spu==null){thrownewLyException(ExceptionEnum.GOODS_NOT_FOUND);}//查询skuspu.setSkus(querySkuBySpuId(id));//查询spudetailspu.setSpuDetail(queryDetailById(id));returnspu;}
• 查询规格参数组
• 在页面展示规格时,需要按组展示。组内有多个参数,为了方便展示。我们提供一个接口,查询规格组,同时在规格组中持有组内的所有参数。
• 扩展SpecGroup类
//添加SpecParam的集合,保存组下所有规格参数@Transientprivate List<SpecParam> params; // 该组下的所有规格参数集合•SpecifictionApi/***根据cid查询规格组及组内参数*@paramcid*@return*/@GetMapping("spec/group")List<SpecGroup> queryListByCid(@RequestParam("cid")Long cid);•SpecifictionController/***根据cid查询规格组及组内参数*@paramcid*@return*/@GetMapping("group")public ResponseEntity<List<SpecGroup>>queryListByCid(@RequestParam("cid")Long cid){return ResponseEntity.ok(specificationService.queryListByCid(cid));}•SpecifictionService/***根据cid查询规格组及组内参数*@paramcid*@return*/public List<SpecGroup> queryListByCid(Long cid){//查询规格组List<SpecGroup>specGroups=queryGroupByCid(cid);//查询当前分类下的参数List<SpecParam>specParams=queryParamByList(null,cid,null);//把规格参数变为mapkey为规格组id,value为组下所有参数Map<Long,List<SpecParam>>map=newHashMap<>();for(SpecParamparam:specParams){if(!map.containsKey(param.getId())){//组id在map中不存在,新增一个listmap.put(param.getGroupId(),newArrayList<>());}map.get(param.getGroupId()).add(param);}//填充param到groupfor(SpecGroupspecGroup:specGroups){specGroup.setParams(map.get(specGroup.getId()));}returnspecGroups;}
页面静态化
• 我们的页面是通过Thymeleaf模板引擎渲染后返回到客户端。在后台需要大量的数据查询,而后渲染得到HTML页面。会对数据库造成压力,并且请求的响应时间过长,并发能力不高。
• 什么是静态化
静态化是指把动态生成的HTML页面变为静态内容保存,以后用户的请求到来,直接访问静态页面,不再经过服务的渲染。
而静态的HTML页面可以部署在nginx中,从而大大提高并发能力,减小tomcat压力。
• 如何实现静态化
•静态化页面都是通过模板引擎来生成,而后保存到nginx服务器来部署。Thymeleaf除了可以把渲染结果写入Response,也可以写到本地文件,从而实现静态化。•通过TemplateEngine解析模板引擎templateEngine.process("模板名", context, writer);三个参数:模板名称,上下文:里面包含模型数据,writer:输出目的地的流输出时,我们可以指定输出的目的地,如果目的地是Response的流,那就是网络响应。如果目的地是本地文件,那就实现静态化了。
• pageController
@Controllerpublic class PageController{@Autowiredprivate PageService pageService;@GetMapping("item/{id}.html")public String toItemPage(@PathVariable("id")Long spuId,Model model){//查询模型数据Map<String,Object> attributes = pageService.loadModel(spuId);//准备模型数据model.addAllAttributes(attributes);//返回视图return"item";}}•pageService@Slf4j@Servicepublic class PageService{@Autowiredprivate BrandClient brandClient;@AutowiredprivateCategoryClientcategoryClient;@AutowiredprivateSpecifictionClientspecClient;@AutowiredprivateGoodsClientgoodsClient;@AutowiredprivateTemplateEnginetemplateEngine;/***加载模型*@paramspuId*@return*/public Map<String,Object> loadModel(Long spuId){Map<String,Object> model = new HashMap<>();//查询spuSpu spu=goodsClient.querySpuById(spuId);//查询skusList<Sku> skus =spu.getSkus();//查询detailSpuDetail detail=spu.getSpuDetail();//查询brandBrand brand=brandClient.queryBrandById(spu.getBrandId());//查询categoriesList<Category>categories=categoryClient.queryCategoryByIds(Arrays.asList(spu.getCid1(),spu.getCid2(),spu.getCid3()));//查询specsList<SpecGroup> specs=specClient.queryListByCid(spu.getCid3());model.put("title",spu.getTitle());model.put("subTitle",spu.getSubTitle());model.put("skus",skus);model.put("detail",detail);model.put("brand",brand);model.put("categories",categories);model.put("specs",specs);return model;}/***创建静态页面*@paramspuId*/public void createHtml(LongspuId){//创建上下文,Contextcontext=newContext();//把数据加入上下文context.setVariables(loadModel(spuId));//创建输出流,关联到一个临时文件Filedest=new File("D:\\MY_IDEA\\upload",spuId+".html");try(PrintWriter writer=new PrintWriter(dest,"UTF-8")){//利用thymeleaf模板引擎生成静态页面templateEngine.process("item",context,writer);}catch(Exceptione){log.error("[静态页服务]生成静态页异常");}}}•PageServiceTest 现在只能通过test来生成静态文件@RunWith(SpringRunner.class)@SpringBootTestpublic class PageServiceTest{@Autowiredprivate PageService pageService;@Testpublic void createHtml(){pageService.createHtml(141L);}}
• 因为nginx在虚拟机里面,所以手动把物理机生成的静态页面复制到虚拟机目录(nginx/html/item)下
修改nginx.conflocation /item {# 先找本地root html;if (!-f $request_filename) { #请求的文件不存在,就反向代理proxy_pass http://192.168.0.6:8084;break;}}
消息队列
搜索与商品服务存在的问题
• 商品的原始数据保存在数据库中,增删改查都在数据库中完成。
• 搜索服务数据来源是索引库,如果数据库商品发生变化,索引库数据不能及时更新。
• 商品详情做了页面静态化,静态页面数据也不会随着数据库商品发生变化。
如果我们在后台修改了商品的价格,搜索页面和商品详情页显示的依然是旧的价格,这样显然不对。该如何解决?
• 通过另外一种方式来解决这个问题:消息队列什么是消息队列
消息队列,即MQ,Message Queue。
• 消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。
• 现在实现MQ的有两种主流方式:AMQP、JMS。
两者间的区别和联系:
• JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
• JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
• JMS规定了两种消息模型;而AMQP的消息模型更加丰富
• 常见产品
• ActiveMQ:基于JMS
• RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
• RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会
• Kafka:分布式消息系统,高吞吐量安装RabbitMQ
• 安装erlang
$ sudo yum install epel-release -y$ yum install erlang -y
• 安装ribbitmq
rpm -ivh rabbitmq-server-3.4.1-1.noarch.rpm
• 修改配置文件
cp /usr/share/doc/rabbitmq-server-3.4.1/rabbitmq.config.example /etc/rabbitmq/rabbitmq.configvim /etc/rabbitmq/rabbitmq.config
• 启动rabbitmq出错
启动rabbitMQ出错解决方法
systemctl stop firewalldsystemctl disable firewalld vim /etc/selinux/config修改SELINUX=disabled然后重启虚拟机
• 开启web管理界面
rabbitmq-plugins enable rabbitmq_management
• 重启rabbitmq
service rabbitmq-server restart
• 访问
在主机中通过地址:http://192.168.163.128:15672即可访问到管理界面默认账号/密码:guest/guest添加了一个用户leyou/leyou