商品详情页浏览量大,并发高,更新的频率并不是很高。如果我们每次都去后台请求数据的话,会造成很大的服务器压力,这里我们使用Thymeleaf技术来渲染页面,Thymeleaf的特点是动静结合,既可以让前端在没有服务端数据的情况下看到效果, 又可以让后端在服务器端带着数据去查看效果。
我们的商品详情并发很大,需要一个单独的微服务。
第一步,导入依赖
<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>leyou-item-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> </dependencies>
第二步,编写启动类
@EnableDiscoveryClient @EnableFeignClients @SpringBootApplication public class goodsDetail { public static void main(String[] args){ SpringApplication.run(goodsDetail.class); } }
第三步,配置文件
server: port: 8084 spring: application: name: goods-page thymeleaf: cache: false rabbitmq: host: 192.168.56.101 username: admin password: admin virtual-host: /leyou eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳 lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期 prefer-ip-address: true ip-address: 127.0.0.1 instance-id: ${spring.application.name}.${server.port} ly: thymeleaf: destPath: D:\nginx-1.13.12\html
这里我们配置了rabbit的地址,用户,虚拟主机等信息,同时还有生成静态文件的位置
下面就是具体的商品详情代码了
步骤是:
第一步,我们点击搜索页的商品数据,会发起一个服务器请求,去请求页面,但是这个请求会被nginx代理,如果我们本地有商品对应的静态页面的时候,我们直接从本地读取,只有我们本地没有的时候,我们才会从后台去生成一个模版,保存在本地。
nginx的配置如下
我们这个服务并没有走网关,当然我们也可以走网关,只需要配置以下即可
第二步,假设我们请求的页面本地没有,这边就把我们代理到了商品详情微服务,服务端代码如下
controller
我们在controller中会渲染页面,并且异步生成一个静态页面存贮到本地
@GetMapping("{id}.html") public String getGoodsDetails(Model model,@PathVariable("id") Long id){ Map<String,Object> modelMap = this.goodsDetailsService.getGoodsDetails(id); model.addAllAttributes(modelMap); if (!fileService.createPath(id).exists()){ this.fileService.asynCreateHtml(id); } return "item"; }
service
我们需要去各个微服务中去请求数据
public Map<String,Object> getGoodsDetails(Long id) { //spu ResponseEntity<Spu> spuResponseEntity = this.spuClient.querySpuBySpuId(id); if (!spuResponseEntity.hasBody()){ logger.error("没有spu的信息"); return null; } Spu spu = spuResponseEntity.getBody(); //spuDeail ResponseEntity<SpuDetail> spuDetailResponseEntity = this.goodsClient.querySpuDetailById(id); if (!spuDetailResponseEntity.hasBody()){ logger.error("没有spuDetail的信息"); return null; } SpuDetail spuDetail = spuDetailResponseEntity.getBody(); //sku和stock ResponseEntity<List<Sku>> listResponseEntity = this.goodsClient.querySkuList(id); if (!listResponseEntity.hasBody()){ logger.error("查询sku信息失败"); return null; } List<Sku> skus = listResponseEntity.getBody(); //查询分类 ResponseEntity<List<Brand>> brandResponsity = this.brandClient.queryBrandsByBrandIds(Arrays.asList(spu.getBrandId())); if (!brandResponsity.hasBody()){ logger.error("查询品牌信息失败"); return null; } List<Brand> brands = brandResponsity.getBody(); //三级分类 ResponseEntity<List<Category>> categoryResponseEntity = this.categoryClient.queryParentByCid3(spu.getCid3()); if (!categoryResponseEntity.hasBody()){ logger.error("没有查询到category信息"); return null; } List<Category> categories = categoryResponseEntity.getBody(); HashMap<String, Object> map = new HashMap<>(); map.put("spu",spu); map.put("spuDetail",spuDetail); map.put("skus",skus); map.put("categories",categories); map.put("brand",brands.get(0)); return map; }
我们还做了一个操作就是异步生成静态文件
@Service public class FileService { @Autowired private GoodsDetailService goodsDetailService; @Autowired private TemplateEngine templateEngine; private Logger logger = LoggerFactory.getLogger(FileService.class); @Value("${ly.thymeleaf.destPath}") private String destPath ; public void createHtml(Long id) { //创建一个上下文对象 Context context = new Context(); //将数据塞入上下文 Map<String, Object> goodsDetails = this.goodsDetailService.getGoodsDetails(id); context.setVariables(goodsDetails); //准备一个输出流对象,关联一个临时文件 File temp = new File(id + ".html"); //创建一个目标文件 File dest = this.createPath(id); //创建一个临时文件的文件夹 File bak = new File(id + "_bak.html"); try( PrintWriter printWriter = new PrintWriter(temp, "utf-8")) { templateEngine.process("item", context, printWriter); //如果目标文件存在,则先把目标文件转移i到临时文件,然后尝试覆盖 if (dest.exists()) { dest.renameTo(bak); } //用新文件将就文件覆盖 FileCopyUtils.copy(temp,dest); //删除成功将备份删除 bak.delete(); } catch (Exception e) { //发生异常,还原旧文件 e.printStackTrace(); logger.error("创建静态页面失败"); bak.renameTo(dest); throw new RuntimeException(e); } finally { if (temp.exists()) { temp.delete(); } } } /** * 根据id创建一个文件 * * @param id * @return */ public File createPath(Long id) { if (id == null) { return null; } //创建路径 File dest = new File(this.destPath); if (!dest.exists()) { dest.mkdirs(); } return new File(dest, id + ".html"); } /** * 异步创建页面 * * @param id */ public void asynCreateHtml(Long id) { ThreadUtils.execute(new Runnable() { @Override public void run() { try { createHtml(id); } catch (Exception e) { e.printStackTrace(); } } }); } public void deleteHtml(Long id) { File file = new File(destPath + id + ".html"); file.deleteOnExit(); } }
Thymeleaf是一个模版引擎技术,他里面有3个概念
1.context,上下文对象,用于存储需要渲染的页面的数据
2.templateResolver
模版解析器,用于读取模版相关的位置,模版的文件名称,模版的类型,这个我们其实是有默认配置的
3.TemplateEngine
模版引擎技术
利用上下文,模版引擎输出文件
我们的模版引擎,需要一个上下文对象,里面是模型的数据,需要模版的名称,还需要输出的位置