乐优商城(八)商品详情

news2025/1/19 13:10:39

1. 搭建商品详情微服务

当用户搜索到商品后,如果想要了解商品的更多信息,就需要进入商品详情页。

由于商品详情浏览量比较大,所以我们会创建一个微服务,用来展示商品详情。我们的商品详情页会采用 Thymeleaf 模板引擎渲染后,再返回到客户端。

1.1 创建工程

  1. 右键 leyou 项目 --> New Module --> Maven --> Next

  2. 填写项目信息 --> Next

3.填写保存的位置 --> Finish

4.添加依赖

<?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</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.parent</groupId>
    <artifactId>leyou-goods-web</artifactId>
    <version>1.0-SNAPSHOT</version>

    <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.common</groupId>
            <artifactId>leyou-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.leyou.item</groupId>
            <artifactId>leyou-item-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
</project>

5.编写配置文件 application.yaml

server:
  port: 8084
spring:
  application:
    name: goods-web-service
  thymeleaf:
    cache: false
eureka:
  client:
    service-url:
      defaultZone: http://localhost:10086/eureka
  instance:
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 10

6.编写启动类

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

1.2 创建页面模板

  1. 从 leyou-portal 项目中复制 item.html 模板到当前项目 resource 目录下的 templates 中。

2.把 HTML 的名称空间改成 xmlns:th="http://www.thymeleaf.org",这样页面就由 Thymeleaf 的引擎解析了。

1.3 页面跳转

1.3.1 修改页面跳转路径

当我们点击某个商品图片时,应该携带该商品的 SpuId 跳转到商品详情页。

例如:

http://www.leyou.com/item/2314123.html

我们打开 search.html,修改其中的商品路径:

1.3.2 Nginx 反向代理

接下来,我们要把这个地址指向我们的 leyou-goods-web 服务,其端口为 8084。

我们在 nginx.conf 中添加配置,并重启 Nginx

1.3.3 编写 Controller

在 leyou-goods-web 中编写 Controller,接收请求,并跳转到商品详情页

@Controller
@RequestMapping("/item")
public class GoodsController {

    /**
     * 跳转到商品详情页
     *
     * @param model
     * @param id
     * @return
     */
    @GetMapping("/{id}.html")
    public String toItemPage(Model model, @PathVariable("id") Long id) {
        return "item";
    }
}
1.3.4 测试
  1. 启动 leyou-goods-web 工程

  2. 点击一个搜索到的商品,成功跳转到商品详情页

1.4 后台提供接口

1.4.1 分析模型数据

首先我们一起来分析一下,在这个页面中需要哪些数据。

我们已知的条件是传递来的 Spu 的 id,我们需要根据 Spu 的 id 查询到下面的数据:

  • Spu 信息
  • Spu 详情
  • Spu 下的所有 Sku
  • 品牌
  • 商品三级分类
  • 规格参数组
  • 规格参数
1.4.2 商品微服务提供接口

为了查询到上面的数据,我们需要在商品微服务中提供一些接口。

通过 Spu 的 id 查询 Spu

在 SpuApi 接口中添加方法 querySpuById

/**
 * 通过 spuId 查询 Spu
 * @param spuId
 * @return
 */
@GetMapping("{spuId}")
public Spu querySpuById(@PathVariable("spuId") Long spuId);

在 SpuController 中添加方法 querySpuById

/**
 * 通过 spuId 查询 Spu
 * @param spuId
 * @return
 */
@GetMapping("{spuId}")
public ResponseEntity<Spu> querySpuById(@PathVariable("spuId") Long spuId){
    Spu spu = spuService.querySpuById(spuId);
    if (spu == null) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(spu);
}

在 SpuService 中添加方法 querySpuById

/**
 * 通过 spuId 查询 Spu
 * @param spuId
 * @return
 */
public Spu querySpuById(Long spuId) {
    Spu spu = spuMapper.selectByPrimaryKey(spuId);
    return spu;
}

通过分类 id 查询规格参数组

商品详情页需要展示商品的规格参数组,以及其下的规格参数。所以我们需要提供一个接口,通过 Spu 的 id 查询规格参数组,并将规格参数封装其中。

在 SpecificationApi 接口中添加方法 queryGroupWithCid

/**
 * 通过分类 id 查询规格参数组
 * @param cid
 * @return
 */
@GetMapping("/group/param/{cid}")
public List<SpecGroup> queryGroupWithCid(@PathVariable("cid") Long cid);

在 SpecificationController 中添加方法 queryGroupsWithParam

/**
 * 通过分类 id 查询规格参数组
 *
 * @param cid
 * @return
 */
@GetMapping("/group/param/{cid}")
public ResponseEntity<List<SpecGroup>> queryGroupsWithParam(@PathVariable("cid") Long cid) {
    List<SpecGroup> specGroups = specificationService.queryGroupsWithParam(cid);
    if (CollectionUtils.isEmpty(specGroups)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(specGroups);
}

在 SpecificationService 中添加方法 queryGroupsWithParam

/**
 * 通过分类 id 查询规格参数组
 *
 * @param cid
 * @return
 */
public List<SpecGroup> queryGroupsWithParam(Long cid) {
    List<SpecGroup> specGroups = querySpecGroupsByCid(cid);
    for (SpecGroup specGroup : specGroups) {
        List<SpecParam> params = querySpecParams(specGroup.getId(), null, null, null);
        specGroup.setParams(params);
    }
    return specGroups;
}
1.4.3 创建 FeignClient

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

@FeignClient(value = "item-service")
public interface BrandClient extends BrandApi {
}
@FeignClient(value = "item-service")
public interface SpuClient extends SpuApi {
}
@FeignClient(value = "item-service")
public interface CategoryClient extends CategoryApi {
}
@FeignClient(value = "item-service")
public interface SpecificationClient extends SpecificationApi {
}
1.4.4 商品详情微服务提供接口

再来回顾一下商品详情页需要的数据,如下:

  • Spu 信息
  • Spu 详情
  • Spu 下的所有 Sku
  • 品牌
  • 商品三级分类
  • 规格参数组
  • 规格参数

我们可以使用 Map<String, Object> 的数据结构封装这些数据,第一个参数为数据名称,第二个参数为数据。

  1. 在 GoodsController 中查询到所需数据,并放入 model

@Controller
@RequestMapping("/item")
public class GoodsController {
    @Autowired
    private GoodsService goodsService;

    /**
     * 通过 spuId 查询所需数据
     *
     * @param model
     * @param id
     * @return
     */
    @GetMapping("/{id}.html")
    public String toItemPage(Model model, @PathVariable("id") Long id) {
        // 通过 spuId 查询所需数据
        Map<String, Object> modelMap = this.goodsService.loadData(id);
        // 放入模型
        model.addAllAttributes(modelMap);
        return "item";
    }
}

在 GoodsService 中添加方法 loadData

@Service
public class GoodsService {
    @Autowired
    private BrandClient brandClient;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private SpuClient spuClient;

    @Autowired
    private SpecificationClient specificationClient;

    /**
     * 通过 spuId 查询所需数据
     * @param spuId
     * @return
     */
    public Map<String, Object> loadData(Long spuId) {
        Map<String, Object> map = new HashMap<>();

        // 查询 Spu
        Spu spu = this.spuClient.querySpuById(spuId);

        // 查询 SpuDetail
        SpuDetail spuDetail = this.spuClient.querySpuDetailBySpuId(spuId);

        // 查询 Sku 集合
        List<Sku> skus = this.spuClient.querySkusBySpuId(spuId);

        // 查询分类
        List<Long> cids = Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3());
        List<String> names = this.categoryClient.queryNamesByIds(cids);
        List<Map<String, Object>> categories = new ArrayList<>();
        for (int i = 0; i < cids.size(); i++) {
            Map<String, Object> categoryMap = new HashMap<>();
            categoryMap.put("id", cids.get(i));
            categoryMap.put("name", names.get(i));
            categories.add(categoryMap);
        }

        // 查询品牌
        Brand brand = this.brandClient.queryBrandById(spu.getBrandId());

        // 查询规格参数组
        List<SpecGroup> groups = this.specificationClient.queryGroupsWithParam(spu.getCid3());

        // 查询特殊的规格参数
        List<SpecParam> params = this.specificationClient.querySpecParams(null, spu.getCid3(), false, null);
        Map<Long, String> paramMap = new HashMap<>();
        params.forEach(param -> {
            paramMap.put(param.getId(), param.getName());
        });

        // 封装 Spu
        map.put("spu", spu);
        // 封装 SpuDetail
        map.put("spuDetail", spuDetail);
        // 封装 Sku 集合
        map.put("skus", skus);
        // 封装分类
        map.put("categories", categories);
        // 封装品牌
        map.put("brand", brand);
        // 封装规格参数组
        map.put("groups", groups);
        // 封装特殊规格参数
        map.put("paramMap", paramMap);

        return map;
    }
}

1.4.5 测试
  1. 在 item.html 页面写一段 JS 代码,把模型中的数据取出观察

<script th:inline="javascript">
    const a = /*[[${groups}]]*/ [];
    const b = /*[[${params}]]*/ [];
    const c = /*[[${categories}]]*/ [];
    const d = /*[[${spu}]]*/ {};
    const e = /*[[${spuDetail}]]*/ {};
    const f = /*[[${skus}]]*/ [];
    const g = /*[[${brand}]]*/ {};
</script>
  1. 点击一个商品详情页,查看网页源码,成功查到数据

  2. 在 item.html 页面写一段 JS 代码,把模型中的数据取出观察

1.5 渲染页面

略,交给前端吧。最终效果如下:

在这里插入图片描述

在这里插入图片描述

2. 页面静态化

2.1 问题分析

现在我们的商品详情页会采用 Thymeleaf 模板引擎渲染后,再返回到客户端。

但这样在后台需要做大量的数据查询,而后渲染得到 HTML 页面。会对数据库造成压力,并且请求的响应时间过长,并发能力不高。

有没有办法解决这些问题呢?

  • 首先,我们能想到的就是缓存技术。比如使用 Redis 缓存,不过 Redis 适合数据规模比较小的情况。假如数据量比较大,比如我们的商品详情页,每个页面如果 10 kb,100 万商品,就是 10 GB 空间,对内存占用比较大,此时就给缓存系统带来极大压力,如果缓存崩溃,接下来倒霉的就是数据库了。
  • 其次,可以使用静态化技术。静态化是指把动态生成的 HTML 页面变为静态内容保存,以后用户的请求到来,直接访问静态页面,不再经过服务器的渲染。而静态的 HTML 页面可以部署在 Nginx 中,从而大大提高并发能力。

2.2 如何实现静态化

原来,我们商品详情页通过 Thymeleaf 模板引擎生成后,直接就返回给客户端了。

现在,我们生成商品详情页后,将它先部署一份在 Nginx 中,再返回给客户端。下一次在访问这个页面时,就直接访问 Niginx 中的静态页面

2.3 实现页面静态化

  1. 在 leyou-goods-web 工程中的 service 包下创建 GoodsHtmlService

@Service
public class GoodsHtmlService {

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private TemplateEngine templateEngine;


    /**
     * 创建 HTML 静态页面
     *
     * @param spuId
     * @throws Exception
     */
    public void createHtml(Long spuId) {

        PrintWriter writer = null;
        try {
            // 获取页面数据
            Map<String, Object> spuMap = this.goodsService.loadData(spuId);

            // 创建 Thymeleaf 上下文对象
            Context context = new Context();
            // 把数据放入上下文对象
            context.setVariables(spuMap);

            // 创建输出流
            File file = new File("D:\\nginx-1.14.0\\html\\item\\" + spuId + ".html");
            writer = new PrintWriter(file);

            // 执行页面静态化方法
            templateEngine.process("item", context, writer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }
}

在 GoodsController 中调用页面静态化方法

/**
 * 通过 spuId 查询所需数据
 *
 * @param model
 * @param id
 * @return
 */
@GetMapping("/{id}.html")
public String toItemPage(Model model, @PathVariable("id") Long id) {
    // 通过 spuId 查询所需数据
    Map<String, Object> modelMap = this.goodsService.loadData(id);
    // 放入模型
    model.addAllAttributes(modelMap);
    // 页面静态化
    goodsHtmlService.createHtml(id);
    return "item";
}

修改 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 html;
        if (!-f $request_filename) { #请求的文件不存在,就反向代理
            proxy_pass http://127.0.0.1:8084;
            break;
        }
    }

    location / {
        proxy_pass http://127.0.0.1:9002;
        proxy_connect_timeout 600;
        proxy_read_timeout 600;
    }
}

2.4 测试

  1. 重启商品详情微服务

  2. 重启 Nginx

  3. 访问一个商品详情页后,成功生成静态页面

4.再次访问,速度得到极大提升

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1496236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux高级编程:网络

回顾&#xff1a; 进程间的通信&#xff1a; 同一主机内通信&#xff1a; 传统的进程间通信方式&#xff08;管道、信号&#xff09;&#xff1b; IPC对象&#xff08;共享内存&#xff0c;消息队列&#xff0c;信号量集&#xff09;&#xff1b; 不同主机间进程的通信&#…

Linux之线程概念

目录 一、细粒度划分 1、堆区细粒度划分 2、物理内存和可执行程序细粒度划分 3、虚拟地址到物理地址的转化 二、线程的概念 1、基本概念 2、线程的优点 3、线程的缺点 4、线程异常 5、线程用途 三、Linux下的进程和线程 一、细粒度划分 1、堆区细粒度划分 在语言…

【MySQL】索引优化与关联查询优化

数据库调优的几个维度&#xff1a; 索引失效&#xff0c;没有充分用到索引——索引建立关联查询太多JOIN——SQL优化服务器调优以及各个参数设置——调整my.cnf数据过多——分库分表 SQL查询优化的几种方式&#xff1a; 物理查询优化&#xff1a;通过索引以及表连接方式进行…

Day30-Linux基础阶段总复习

Day30-Linux基础阶段总复习 1. 运维人员的三个核心职责&#xff08;了解&#xff09;2. 企业网站和应用的可用性的衡量标准&#xff08;重点&#xff09;2.1 高并发企业业务写入流程图2.2 中小型企业案例 3. Linux系统诞生发展过程中的关键代表人物4. 企业场景如何针对不同的业…

Springboot配置MySQL数据库

Springboot配置MySQL数据库 一、创建springboot项目&#xff0c;并添加如下依赖 <dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope> </dependency>二、在applica…

Django框架——路由

上篇文章我们学习了Django框架——请求与响应&#xff0c;这篇文章我们学习Django框架——路由。本次学习过程中使用的项目目录如下图所示&#xff1a; 大家放心&#xff0c;这个是新建的Django项目&#xff0c;还没编写任何代码&#xff0c;这里展示目录是为了大家更好地理解本…

力扣大厂热门面试算法题 - 矩阵

解数独&#xff0c;单词搜索&#xff0c;被围绕的区域。每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.07 可通过leetcode所有测试用例。 目录 37. 解数独 解题思路 完整代码 Python Java 79. 单词搜索 解题思路 完整代码 Python…

金融行业数据安全面临的问题及解决办法

金融行业包括商业银行业务、证券业务、保险业务、基金业务、信托业务等&#xff0c;因此数据类型多种多样&#xff0c;并且数据涉及主体众多&#xff0c;应用场景上较为多样复杂&#xff0c;在数据交换上存在安全、合规、可控、可靠、高效等需求。首先&#xff0c;我们来看一下…

c++初阶------类和对象(六大默认构造函数的揭破)

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

部署YOLOv8模型的实用常见场景

可以的话&#xff0c;GitHub上点个小心心&#xff0c;翻不了墙的xdm&#xff0c;csdn也可以点个赞&#xff0c;谢谢啦 车流量检测&#xff08;开源代码github&#xff09;&#xff1a; test3 meiqisheng/YOLOv8-DeepSORT-Object-Tracking (github.com) 车牌检测&#xff0…

代码随想录算法训练营第11天

20. 有效的括号 方法&#xff1a; 1. 如果 !st.empty() return false2.如果st.top() ! s[i] return false3. 如果 st.empty() return false注意&#xff1a; 以下这种写法 不满足 题目要求的第二点&#xff0c;不能以正确的顺序闭合 if(s[i] st.top()){return true;s…

openssl调试记录

openssl不能直接解密16进制密文&#xff0c;需要把密文转化成base64格式才能解密 调试记录如下&#xff1a;

【必读】产品经理必须要熟练掌握的五类产品管理框架

产品管理从其传统根源已经走过了很长一段路&#xff0c;不断发展以适应现代企业的步伐和需求。随着产品变得更加复杂&#xff0c;用户的需求更加细致&#xff0c;用于指导这些产品的框架需要升级。请阅读本文&#xff0c;深入了解从字面上看改变了产品管理游戏规则的结构。 01.…

越南、泰国发稿案例分析:CloudNEO专家级海外新闻传播矩阵

在东南亚地区&#xff0c;越南和泰国作为经济增长迅速、市场潜力巨大的国家&#xff0c;吸引着越来越多的国际企业进驻和投资。在这个充满机遇和挑战的市场中&#xff0c;有效的新闻传播成为企业赢得市场份额和建立品牌形象的关键一环。CloudNEO作为专业的海外新闻传播服务提供…

事务 失效的八种情况

在某些业务场景下&#xff0c;如果一个请求中&#xff0c;需要同时写入多张表的数据。为了保证操作的原子性&#xff08;要么同时成功&#xff0c;要么同时失败&#xff09;&#xff0c;避免数据不一致的情况&#xff0c;我们一般都会用到 spring 事务。 确实&#xff0c;sprin…

docker-compose Install ONLYOFFICE

ONLYOFFICE 前言 ONLYOFFICE 是一款全面的协作办公软件套件,集成了文档处理、电子表格和演示文稿等功能,为团队提供了无缝协作的工作环境。其功能强大,操作简便,是各种规模和类型的团队的首选工具。 功能介绍 多人协作:ONLYOFFICE 提供实时协作功能,让团队成员可以同时…

【设计模式 04】建造者模式

如果要构建的对象很复杂&#xff0c;那么可以将整个构建过程拆分成多个步骤&#xff0c;并为每一个步骤定义一个抽象的接口。并添加一个指导者用来控制构建产品的顺序和步骤。 Java实现&#xff1a; // 产品类 class Product {private String part1;private String part2;pub…

vue3+ts项目创建 使用npm create vue@latest

npm create vuelatest相关创建代码&#xff1a;

android基础学习

从上面的描述就可以知道&#xff0c;每一个Activity组件都有一个对应的ViewRoot对象、View对象以及WindowManager.LayoutParams对象。这三个对象的对应关系是由WindowManagerImpl类来维护的。具体来说&#xff0c;就是由WindowManagerImpl类的成员变量mRoots、mViews和mParams所…

onnxruntime模型部署(二)C++部署手写数字识别

导出onnx模型 模型链接&#xff1a; 夸克网盘链接 百度网盘链接&#xff0c;提取码&#xff1a;8fkb 环境配置 OpenCV配置 自行百度 onnxruntime C版配置 有两种方法&#xff0c;一种是下载源码自己编译&#xff0c;还有一种是使用预编译好的文件。众说周知&#xff0c;…