美文网首页
商品详情页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