乐优商城第十五天(商品详情以及静态化)

商品详情页浏览量大,并发高,更新的频率并不是很高。如果我们每次都去后台请求数据的话,会造成很大的服务器压力,这里我们使用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的配置如下

listen 80 ;
        server_name www.leyou.com ;
        
        proxy_set_header X-Forwarded-Host $host ;
        proxy_set_header X-Forwarded-Server $host ;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;

      location /item {
# 先找本地
        alias html ;
         #index index.html index.htm;
if (!-f $request_filename) { #请求的文件不存在,就反向代理
proxy_pass http://127.0.0.1:8084 ;
break ;
}
}

我们这个服务并没有走网关,当然我们也可以走网关,只需要配置以下即可

第二步,假设我们请求的页面本地没有,这边就把我们代理到了商品详情微服务,服务端代码如下

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();
    //skustock
    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

模版引擎技术

利用上下文,模版引擎输出文件

我们的模版引擎,需要一个上下文对象,里面是模型的数据,需要模版的名称,还需要输出的位置

猜你喜欢

转载自blog.csdn.net/qpc672456416/article/details/80698434