美文网首页
商品详情页goods-web

商品详情页goods-web

作者: 碎碎念_碎语 | 来源:发表于2020-06-22 21:43 被阅读0次

    you-goods-web`

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>leyou-parent</artifactId>
            <groupId>com.leyou</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.leyou.goods</groupId>
        <artifactId>leyou-goods-web</artifactId>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</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>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>com.leyou.item</groupId>
                <artifactId>leyou-item-interface</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </project>
    

    启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class LeyouGoodsWebApplication {
        public static void main(String[] args) {
            SpringApplication.run(LeyouGoodsWebApplication.class, args);
        }
    }
    

    pei zhi

    server:
      port: 7004
    spring:
      application:
        name: goods-web
      thymeleaf:
        cache: false
      main:
        allow-bean-definition-overriding: true
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:10001/eureka
      instance:
        lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
        lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期
    

    商品微服务中提供这个接口:根据id查询spu --- GoodsApi

    /**
     * 根据spu的id查询spu
     * @param id
     * @return
     */
    @GetMapping("/spu/{id}")
    public Spu querySpuById(@PathVariable("id") Long id);
    

    GoodsController

    @GetMapping("/spu/{id}")
    public ResponseEntity<Spu> querySpuById(@PathVariable("id") Long id){
        Spu spu = this.goodsService.querySpuById(id);
        if(spu == null){
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.ok(spu);
    }
    

    GoodsService

    public Spu querySpuById(Long id) {
        return this.spuMapper.selectByPrimaryKey(id);
    }
    

    leyou-item-service中提供一个接口,查询规格组,同时查询规格组内的所有参数。
    SpecificationAPI:

    @GetMapping("/{cid}")
    public List<SpecGroup> querySpecsByCid(@PathVariable Long cid);
    

    SpecificationController:

    @GetMapping("/{cid}")
    public ResponseEntity<List<SpecGroup>> querySpecsByCid(@PathVariable Long cid) {
        List<SpecGroup> specGroups = this.specificationService.querySpecsByCid(cid);
        if (CollectionUtils.isEmpty(specGroups)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(specGroups);
        }
    

    SpecificationService

    @Override
    public List<SpecGroup> querySpecsByCid(Long cid) {
        //查询所有的规格参数组
        List<SpecGroup> specGroups = this.querySpecGroupsByCid(cid);
        specGroups.forEach(specGroup -> {
            //查询该规格参组下的所有规格参数
            List<SpecParam> specParams = this.queryParams(specGroup.getId(), null, null, null);
            //封装到规格参数组中
            specGroup.setParams(specParams);
        });
    
        return specGroups;
    }
    

    创建FeignClient

    我们在leyou-goods-web服务中,创建FeignClient:

    [图片上传失败...(image-88592f-1592833403265)]

    将之前搜索服务中的复制过来

    封装数据模型

    我们创建一个GoodsService,在里面来封装数据模型。

    这里要查询的数据:

    • SPU

    • SpuDetail

    • SKU集合

    • 商品分类

      • 这里值需要分类的id和name就够了,因此我们查询到以后自己需要封装数据
    • 品牌对象

    • 规格组

      • 查询规格组的时候,把规格组下所有的参数也一并查出,上面提供的接口中已经实现该功能,我们直接调
    • sku的特有规格参数

      有了规格组,为什么这里还要查询?

      因为在SpuDetail中的SpecialSpec中,是以id作为规格参数的key,如图:

      [图片上传失败...(image-6570d0-1592833403265)]

      但是,在页面渲染时,需要知道参数的名称,如图:

      [图片上传失败...(image-a6d876-1592833403265)]

      我们就需要把id和name一一对应起来,因此需要额外查询sku的特有规格参数,然后变成一个id:name的键值对格式。也就是一个Map,方便将来根据id查找!

    Service代码

    @Override
    public Map<String, Object> loadData(Long spuId) {
        //根据商品id查询商品(spu)
        Spu spu = this.goodsClient.querySpuById(spuId);
        //查询商品详情(spuDetail)
        SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spuId);
    
        //根据spuId查询其下的所有sku
        List<Sku> skus = this.goodsClient.querySkuBySpuId(spuId);
    
        // 查询分类
        List<Category> categories = this.categoryClient.queryAllByCid3(spu.getCid3());
        //只需要分类id和分类名称
        List<Map<String, Object>> categoryData = categories.stream().map(category -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", category.getId());
            map.put("name", category.getName());
            return map;
        }).collect(Collectors.toList());
    
        // 查询品牌
        Brand brand = this.brandClient.queryBrandByid(spu.getBrandId());
    
        // 查询规格参数组和组下规格参数
        List<SpecGroup> groups = this.specificationClient.querySpecsByCid(spu.getCid3());
    
        //查询特殊的规格参数获取  id,name
        List<SpecParam> specialParams = this.specificationClient.querySpecParams(null, spu.getCid3(), false, null);
    
        Map<Long, String> params = new HashMap<>();
        specialParams.forEach(specParam -> {
            params.put(specParam.getId(), specParam.getName());
        });
        //封装商品信息
        Map<String, Object> goodsInfo = new HashMap<>();
        // 封装spu
        goodsInfo.put("spu", spu);
        // 封装spuDetail
        goodsInfo.put("spuDetail", spuDetail);
        // 封装sku集合
        goodsInfo.put("skus", skus);
        // 分类
        goodsInfo.put("categories", categories);
        // 品牌
        goodsInfo.put("brand", brand);
        // 规格参数组
        goodsInfo.put("groups", groups);
        // 查询特殊规格参数
        goodsInfo.put("params", params);
    
        return goodsInfo;
    }
    

    然后在controller中把数据放入model:

    @Controller
    @RequestMapping("item")
    public class GoodsController {
    
        @Autowired
        private GoodsService goodsService;
    
        @GetMapping("/{id}.html")
        public String toItemPage(Model model, @PathVariable("id") Long id) {
            Map<String, Object> map = goodsService.loadData(id);
            model.addAllAttributes(map);
            return "item";
        }
    }
    

    Thymeleaf实现静态化

    概念

    先说下Thymeleaf中的几个概念:

    • Context:运行上下文
    • TemplateResolver:模板解析器
    • TemplateEngine:模板引擎

    Context

    上下文: 用来保存模型数据,当模板引擎渲染时,可以从Context上下文中获取数据用于渲染。

    当与SpringBoot结合使用时,我们放入Model的数据就会被处理到Context,作为模板渲染的数据使用。

    TemplateResolver

    模板解析器:用来读取模板相关的配置,例如:模板存放的位置信息,模板文件名称,模板文件的类型等等。

    当与SpringBoot结合时,TemplateResolver已经由其创建完成,并且各种配置也都有默认值,比如模板存放位置,其默认值就是:templates。比如模板文件类型,其默认值就是html。

    TemplateEngine

    模板引擎:用来解析模板的引擎,需要使用到上下文、模板解析器。分别从两者中获取模板中需要的数据,模板文件。然后利用内置的语法规则解析,从而输出解析后的文件。来看下模板引擎进行处理的函数:

    templateEngine.process("模板名", context, writer);
    

    三个参数:

    • 模板名称
    • 上下文:里面包含模型数据
    • writer:输出目的地的流

    在输出时,我们可以指定输出的目的地,如果目的地是Response的流,那就是网络响应。如果目的地是本地文件,那就实现静态化了。

    而在SpringBoot中已经自动配置了模板引擎,因此我们不需要关心这个。现在我们做静态化,就是把输出的目的地改成本地文件即可!

    具体实现

    Service代码:

    @Service
    public class GoodsHtmlServiceImpl implements GoodsHtmlService {
    
        @Autowired
        private GoodsService goodsService;
    
        @Autowired
        private TemplateEngine templateEngine;
    
        private final Logger LOGGER = LoggerFactory.getLogger(getClass());
    
        @Override
        public void createHtml(Map<String, Object> model) {
            //创建thymeleaf上下文对象
            Context context = new Context();
            //把数据放入上下文
            context.setVariables(model);
            //获取spu
            Spu spu = (Spu) model.get("spu");
    
            Writer writer = null;
            try {
                writer = new PrintWriter(new File("/home/cloudlandboy/Project/leyou/html/item/" + spu.getId() + ".html"));
                // 执行页面静态化方法
                templateEngine.process("item", context, writer);
            } catch (FileNotFoundException e) {
                LOGGER.error("页面静态化出错:{}," + e, model);
            } finally {
                IOUtils.closeQuietly(writer);
            }
    
        }
    
        @Override
        public void asyncExcuteCreateHtml(Map<String, Object> model) {
            ThreadUtils.execute(() -> createHtml(model));
        }
    }
    

    线程工具类:

    public class ThreadUtils {
    
        private static final ExecutorService es = Executors.newFixedThreadPool(10);
    
        public static void execute(Runnable runnable) {
            es.submit(runnable);
        }
    }
    

    什么时候创建静态文件

    我们编写好了创建静态文件的service,那么问题来了:什么时候去调用它呢

    想想这样的场景:

    假如大部分的商品都有了静态页面。那么用户的请求都会被nginx拦截下来,根本不会到达我们的leyou-goods-web服务。只有那些还没有页面的请求,才可能会到达这里。

    因此,如果请求到达了这里,我们除了返回页面视图外,还应该创建一个静态页面,那么下次就不会再来麻烦我们了。

    所以,我们在GoodsController中添加逻辑,去生成静态html文件:

    @Controller
    @RequestMapping("item")
    public class GoodsController {
    
        @Autowired
        private GoodsService goodsService;
    
        @Autowired
        private GoodsHtmlService goodsHtmlService;
    
        /**
         * 跳转到商品详情页
         *
         * @param model
         * @param id
         * @return
         */
        @GetMapping("/{id}.html")
        public String toItemPage(Model model, @PathVariable("id") Long id) {
            Map<String, Object> map = goodsService.loadData(id);
            model.addAllAttributes(map);
    
            //页面静态化
            goodsHtmlService.asyncExcuteCreateHtml(map);
            return "item";
        }
    }
    

    注意:生成html 的代码不能对用户请求产生影响,所以这里我们使用额外的线程进行异步创建。

    nginx代理静态页面

    接下来,我们修改nginx,让它对商品请求进行监听,指向本地静态页面,如果本地没找到,才进行反向代理:

    server {
        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 {
            # 先找本地
            root /home/cloudlandboy/Project/leyou/html;
    
            # 请求的文件不存在,就反向代理
            if (!-f $request_filename){
                proxy_pass http://127.0.0.1:7004;
            }
        }
    
        location / {
            proxy_pass http://127.0.0.1:9002;
            proxy_connect_timeout 600;
            proxy_read_timeout 600;
        }
    }
    

    !> 注意if和后面的括号之间要有一个空格

    重启测试:

    发现请求速度得到了极大提升:

    [图片上传失败...(image-f0200a-1592833403265)]

    相关文章

      网友评论

          本文标题:商品详情页goods-web

          本文链接:https://www.haomeiwen.com/subject/dsoffktx.html