谷粒商城 Day05 商品详情页接口准备

Day05 商品详情页接口准备

一、Thymeleaf

1、thymeleaf 简介

① HelloController

com.atguigu.thymeleaf.controller.HelloController

/**
 * 1、引入starter
 *         <dependency>
 *             <groupId>org.springframework.boot</groupId>
 *             <artifactId>spring-boot-starter-thymeleaf</artifactId>
 *         </dependency>
 * 2、正常开发
 */
//@RestController //返回json
@Controller  //页面跳转
public class HelloController {

    @GetMapping("/hello")
    public String  hello(HttpServletRequest request){
        //thymeleaf 默认会在  类路径下的 templates 文件夹下找
        //自动配置好了thymeleaf的视图解析器  prefix   suffix
        /**
         * spring.thymeleaf.prefix=classpath:/templates/
         * spring.thymeleaf.suffix=.html
         */
        //自然语言
        request.setAttribute("userName","张三");
        
        return "success";   //  classpath:/templates/success.html
    }
}
② ThymeleafApplication
@SpringBootApplication
public class ThymeleafApplication {

    public static void main(String[] args) {
        SpringApplication.run(ThymeleafApplication.class, args);
    }
}
③ success.html

thymeleaf/src/main/resources/templates/success.html

<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>成功</title>
</head>
<body>

<h1>th语法</h1>
<h2 th:text="${userName}">李四</h2>

</body>
</html>

userName 是张三,而 h2 标签里面的是李四,显示出来的依然是张三

④ application.properties
# 应用名称
spring.application.name=thymeleaf
# 应用服务 WEB 访问端口
server.port=8080

spring.thymeleaf.prefix="classpath:/templates/"
spring.thymeleaf.suffix=".html"

## THYMELEAF (ThymeleafAutoConfiguration)
## 开启模板缓存(默认值: true )
#spring.thymeleaf.cache=true
## 检查模板是否存在,然后再呈现
#spring.thymeleaf.check-template=true
## 检查模板位置是否正确(默认值 :true )
#spring.thymeleaf.check-template-location=true
##Content-Type 的值(默认值: text/html )
#spring.thymeleaf.content-type=text/html
## 开启 MVC Thymeleaf 视图解析(默认值: true )
#spring.thymeleaf.enabled=true
## 模板编码
#spring.thymeleaf.encoding=UTF-8
## 要被排除在解析之外的视图名称列表,⽤逗号分隔
#spring.thymeleaf.excluded-view-names=
## 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
#spring.thymeleaf.mode=HTML5
## 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
#spring.thymeleaf.prefix=classpath:/templates/
## 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
#spring.thymeleaf.suffix=.html

2、五种表达式

二、分析&创建微服务

1、详情渲染功能介绍

image-20210910110038827

商品详情所需构建的数据如下:

1,Sku基本信息(名字,id,xxx,价格,sku_描述) sku_info

2,Sku图片信息(sku的默认图片[sku_info],sku_image[一组图片])

3,Sku分类信息(sku_info[只有三级分类],根据这个三级分类查出所在的一级,二级分类内容,连上三张分类表继续查)

4,Sku销售属性相关信息(查出自己的sku组合,还要查出这个sku所在的spu定义了的所有销售属性和属性值)

5,Sku价格信息(平台可以单独修改价格,sku后续会放入缓存,为了回显最新价格,所以单独获取)

… sku参与的促销。Sku所在最近仓库库存 … 调用远程服务查询

微服务的调用链路

2、详情模块规划

模块规划思路:

  • service-item微服务模块封装详情页面所需数据接口;单独抽取的商品详情页微服务
  • service-item通过feign client调用其他微服务(service-product)数据接口进行数据汇总
  • Pc端前台页面通过web-all调用service-item数据接口渲染页面
  • service-item可以为ps端、H5、安卓与ios等前端应用提供数据接口,web-all为ps端页面渲染形式
  • service-item获取商品信息需要调用service-product服务sku信息等
  • 由于service各微服务可能会相互调用,调用方式都是通过feign client调用,所以我们把feign client api接口单独封装出来,需要时直接引用feign client api模块接口即可,即需创建service-client父模块,管理各service微服务feign client api接口
image-20210910113435748

3、新建 service-item 模块

① pom.xml
<?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>service</artifactId>
        <groupId>com.atguigu.gmall</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service-item</artifactId>
    <description>商品详情页服务</description>

    <dependencies>
        <dependency>
            <groupId>com.atguigu.gmall</groupId>
            <artifactId>service-feign-client</artifactId>
            <version>1.0</version>
    </dependencies>

    <build>
        <finalName>service-item</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

build 表示打包的时候文件名叫啥

我们顺便把 service-product 的 build 标签也给写上

pom.xml

<build>
    <finalName>service-product</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
② bootstrap.properties
spring.application.name=service-item
server.port=9000

spring.cloud.nacos.server-addr=192.168.200.188:8848
③ application.yaml
server:
  port: 9000
④ ItemApplication
/**
 * 所有和商品有关的数据 由service-product来做的,我们不操作,远程调用
 */

// 排除跟数据库有关的自动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ItemApplication {

    public static void main(String[] args) {
        SpringApplication.run(ItemApplication.class,args);
    }
}
⑤ SkuInfoController

新建 com.atguigu.gmall.item.controller.SkuInfoController

@RestController
@RequestMapping("/api/item")
public class SkuInfoController {
	@GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}
image-20210920180758264

控制台:

image-20210920180923097

zipkin 没有配置,所以报错

所以我们来吧 zipkin 的配置加上

⑥ application.yaml 2
server:
  port: 9000

#怎么抽取全微服务都能用
spring:
  zipkin:
    base-url: http://192.168.200.188:9411/
    sender:
      type: web

image-20210920181444293

image-20210920181537199

4、测试feign远程调用

新建 service-feign-client

① SpuFeignClient

新建 com.atguigu.gmall.feign.product.SpuFeignClient

/**
 * 我将要调用远程服务
 */
@FeignClient("service-product") //申明要调用的远程服务的名字
public interface SpuFeignClient {

    //1、别人调用了 SpuFeignClient.spuInfoPageList
    //2、Feign先去 注册中心找 "service-product" 的所有位置
    //3、给 "service-product" 发送  @GetMapping 申明的请求 /admin/product/1/5?category3Id=61
    //4、@RequestParam 自动带到请求路径上   @PathVariable:处理请求路径的占位符
    @GetMapping("/admin/product/{page}/{limit}")
    Result<Page<SpuInfo>> spuInfoPageList(
            @RequestParam("category3Id") Long category3Id,
            @PathVariable("page") Long page,
            @PathVariable("limit") Long limit);
}
② SkuInfoController

com.atguigu.gmall.item.controller.SkuInfoController

@RestController
@RequestMapping("/api/item")
public class SkuInfoController {
    
    @Autowired
    SpuFeignClient spuFeignClient;
    
    // 测试远程调用请求
	@GetMapping("/hello")
    public Result<Page<SpuInfo>> hello(){
        
        // feign远程调用
    	Result<Page<SpuInfo>> pageResult = spuFeignClient.spuInfoPageList(61L, 1L, 5L);
    	return pageResult;
    }
}
image-20210910142626038 image-20210910142700076

远程调用:

image-20210910143623528

自己发

image-20210910143736340

5、规划商品详情接口

service-item

① ItemService

新建 com.atguigu.gmall.item.service.ItemService

/**
 * 根据skuId返回sku的详情信息
 */
public interface ItemService {
    
    // 方式一:VO(View Object/Value Object)对照封装页面的信息
    // SkuDetailVo getSkuInfo(Long skuId);
    
    // 方式二(采用):使用map先来封装未来交给页面的所有数据,Map代表了sku的所有信息
    Map<String,Object> getSkuInfo(Long skuId);
}
② ItemServiceImpl

新建 com.atguigu.gmall.item.service.impl.ItemServiceImpl

@Service
@Slf4j
public class ItemServiceImpl implements ItemService {
    
    // TODO 1,Sku基本信息(名字,id,xxx,价格,sku_描述) sku_info
    // TODO 2,Sku图片信息(sku的默认图片[sku_info],sku_image[一组图片])
    // TODO 3,Sku分类信息(sku_info[只有三级分类],根据这个三级分类查出所在的一级,二级分类内容,连上三张分类表继续查)
    // TODO 4,Sku销售属性相关信息(查出自己的sku组合,还要查出这个sku所在的spu定义了的所有销售属性和属性值)
    // TODO 5,Sku价格信息(平台可以单独修改价格,sku后续会放入缓存,为了回显最新价格,所以单独获取)
    return null;
}

service-product

③ api 包

新建 com.atguigu.gmall.product.api 包,供其他模块远程调用

api包下的所有controller,不是为了给前端页面提供数据,而是为了集群内部微服务之间调用数据用的为service-item 提供远程接口

三、查询商品详情的远程接口

1、查询 Sku基本信息 以及 所有sku图片

service-product

① ProductApiController

新建 com.atguigu.gmall.product.api.ProductApiController

/**
 * api包下的所有controller,不是为了给前端页面提供数据,而是为了集群内部微服务之间调用数据用的,为service-item 提供远程接口
 * @RequestMapping 中的路径都以 /api 开头就是为了区分
 */
@RestController
@RequestMapping("/api/product")
public class ProductApiController {

    @Autowired
    ApiProductService apiProductService;

    /**
     * 1、Sku基本信息 以及 所有sku图片
     * @param skuId
     * @return
     */
    @GetMapping("/inner/getSkuInfo/{skuId}")
    public SkuInfo getSkuInfo(@PathVariable("skuId") Long skuId) {
        return apiProductService.getSkuInfo(skuId);
    }
}

为什么现在的返回值类型不是 Result 呢?因为 Result 类型是给前端页面提供数据的,Result 里面有 code 返回码、message 返回消息、date 返回数据,而我们现在的远程接口要啥给啥就行了

② ApiProductService

新建 com.atguigu.gmall.product.service.ApiProductService

/**
 * 以后 ApiXXXService 代表提供远程功能的接口
 */
public interface ApiProductService {

    SkuInfo getSkuInfo(Long skuId);
}
③ ApiProductServiceImpl

新建 com.atguigu.gmall.product.service.impl.ApiProductServiceImpl

@Service
public class ApiProductServiceImpl implements ApiProductService {

    @Autowired
    SkuInfoMapper skuInfoMapper;

    @Autowired
    SkuImageMapper skuImageMapper;

    @Override
    public SkuInfo getSkuInfo(Long skuId) {

        //1、查询 sku的详情
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        
        // 为什么不连表查询?如果 skuInfo 查询出来有一百万数据,要跟 sku 图片连表就很爆炸

        //2、查询 sku 的图片
        QueryWrapper<SkuImage> skuImageQueryWrapper = new QueryWrapper<>();
        skuImageQueryWrapper.eq("sku_id", skuId);

        List<SkuImage> skuImages = skuImageMapper.selectList(skuImageQueryWrapper);

        //3、把查出的图片设置到 bean 属性
        skuInfo.setSkuImageList(skuImages);
        return skuInfo;
    }
}

运行时报错了

2、Minion 宿主机与容器时间同步问题

① 问题情况

image-20210920191052953

② 问题原因

The difference between the request time and the server’s time is too large.

服务器里面docker 安装的minio 没有做时间同步

③ 解决方案
(1)设置宿主机时间
#查询可用时区
timedatectl list-timezones|grep Asia/Shanghai

#查看系统时间  UTC:/ CST:(China Standard Time)
date

#设置时区
timedatectl set-timezone Asia/Shanghai

#安装ntp
yum -y install ntp
#更新时间
ntpdate pool.ntp.org
#开机启动同步
systemctl enable ntpd --now
(2)容器同步时间
#启动时设置
-v /etc/localtime:/etc/localtime:ro
(3)容器镜像修改时间
# CentOS
RUN echo "Asia/shanghai" > /etc/timezone;
# Ubuntu
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

3、根据三级分类的id查询整个三级分类信息

① 写 SQL
(1) 根据三级分类的id查询整个三级分类信息
image-20210920192258407

sku是挂在三级分类下面的,我们的分类信息分别在base_category1、base_category2、base_category3这三张表里面,目前需要通过sku表的三级分类id获取一级分类名称、二级分类名称和三级分类名称

解决方案:

我们可以建立一个视图(view),把三张表关联起来,视图id就是三级分类id,这样通过三级分类id就可以查询到相应数据,效果如下:

image-20210920193119464
(2) 创建视图(虚表)
image-20210920193230853

再查就方便了

image-20210920194206954

image-20210920194111053

② BaseCategoryView

com.atguigu.gmall.model.product.BaseCategoryView

已经有封装好了的 BaseCategoryView

@Data
@ApiModel(description = "BaseCategoryView")
@TableName("base_category_view")
public class BaseCategoryView extends BaseEntity {
   
   private static final long serialVersionUID = 1L;
   
   @ApiModelProperty(value = "一级分类编号")
   @TableField("category1_id")
   private Long category1Id;

   @ApiModelProperty(value = "一级分类名称")
   @TableField("category1_name")
   private String category1Name;

   @ApiModelProperty(value = "二级分类编号")
   @TableField("category2_id")
   private Long category2Id;

   @ApiModelProperty(value = "二级分类名称")
   @TableField("category2_name")
   private String category2Name;

   @ApiModelProperty(value = "三级分类编号")
   @TableField("category3_id")
   private Long category3Id;

   @ApiModelProperty(value = "三级分类名称")
   @TableField("category3_name")
   private String category3Name;

}
③ ProductApiController
/**
     * 2、根据三级分类的id查询整个三级分类信息
     * inner/getCategoryView/{category3Id}
     *
     * 已经有封装好了的 BaseCategoryView 
     */
    @GetMapping("/inner/getCategoryView/{category3Id}")
    public BaseCategoryView getCategoryView(@PathVariable("category3Id") Long category3Id) {
        return apiProductService.getCategoryView(category3Id);
    }
④ ApiProductService
    BaseCategoryView getCategoryView(Long category3Id);
⑤ ApiProductServiceImpl
    @Autowired
    BaseCategoryViewMapper baseCategoryViewMapper;

	/**
     * 根据三级分类 id,获取整个分类信息
     *
     * @param category3Id
     * @return
     */
    @Override
    public BaseCategoryView getCategoryView(Long category3Id) {
        BaseCategoryView baseCategoryView = baseCategoryViewMapper.selectById(category3Id);
        return baseCategoryView;
    }
⑥ 测试
image-20210920194955981

4、按照SkuId查询最新价格

① ProductApiController
	/**
     * 3、按照SkuId查询最新价格
     */
    @GetMapping("inner/getSkuPrice/{skuId}")
    public BigDecimal getSkuPrice(@PathVariable Long skuId) {
        return apiProductService.getSkuPrice(skuId);
    }
② ApiProductService
    BigDecimal getSkuPrice(Long skuId);
③ ApiProductServiceImpl
    /**
     * 根据skuId获取价格
     *
     * @param skuId
     * @return
     */
    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        
        /*
            写法一:(不好)
            高并发情况下会增加sendingdate时间,假如写法一占用的带宽为 50k,写法二则只需要 5k
            我只要一个 price 结果你给我把 skuInfo 中
            // 底层为 select * from sku_info where id = #{skuId}
            SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
            return skuInfo.getPrice();
        */
        
        // 写法二:
        BigDecimal price = skuInfoMapper.getSkuPrice(skuId);
        return price;
    }
④ SkuInfoMapper

com.atguigu.gmall.product.mapper.SkuInfoMapper

    BigDecimal getSkuPrice(Long skuId);
⑤ SkuInfoMapper.xml

service/service-product/src/main/resources/mapper/SkuInfoMapper.xml

    <select id="getSkuPrice" resultType="java.math.BigDecimal">
        select price from sku_info where id = #{skuId}
    </select>

5、获取销售信息(1、2)

① 思路

根据 skuId 查出:

  1. 查出该商品的spu的所有销售属性和属性值
  2. 标识出本商品对应的销售属性
  3. 点击其他销售属性值的组合,跳转到另外的sku页面
② SQL 语句
(1)根据skuId查出它对应spu的所有销售属性以及属性值组合
#根据skuId查出它对应的spu的所有销售属性以及属性值组合
select si.id     sku_id,
       si.spu_id spu_id,
       spusa.id  spu_sale_attr_id,
       spusa.base_sale_attr_id,
       spusa.sale_attr_name,
       spusav.sale_attr_value_name
from sku_info si
         left join spu_sale_attr spusa on spusa.spu_id = si.spu_id
         left join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_id
    and spusa.base_sale_attr_id = spusav.base_sale_attr_id
where si.id = 48;
image-20210920225107380
(2)根据skuId查出它对应的spu的所有销售属性以及属性值组合,标识出当前sku属于哪一个
#根据skuId查出它对应的spu的所有销售属性以及属性值组合
#标识出当前sku属于哪一个
select si.id     sku_id,
       si.spu_id spu_id,
       spusa.id  spu_sale_attr_id,
       spusa.base_sale_attr_id,
       spusa.sale_attr_name,
       spusav.sale_attr_value_name,
       skusav.sale_attr_value_id
from sku_info si
         left join spu_sale_attr spusa on spusa.spu_id = si.spu_id
         left join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_id
    and spusa.base_sale_attr_id = spusav.base_sale_attr_id
         left join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.id
    and skusav.spu_id = spusa.spu_id
    and skusav.sku_id = 49
where si.id = 49
ORDER BY spusa.base_sale_attr_id,skusav.sale_attr_value_id
image-20210920231319543
(3)与 SpuSaleAttrValue 对应版(我们需要的)
#根据skuId查出它对应的spu的所有销售属性以及属性值组合
#标识出当前sku属于哪一个
#SpuSaleAttrValue:spu_id、base_sale_attr_id、sale_attr_value_name、sale_attr_name、isChecked
select si.id     sku_id,
       si.spu_id spu_id,
       spusa.base_sale_attr_id,
       spusa.sale_attr_name,
       spusav.sale_attr_value_name,
       if(skusav.id is null,0,1) is_checked
from sku_info si
         left join spu_sale_attr spusa on spusa.spu_id = si.spu_id
         left join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_id
    and spusa.base_sale_attr_id = spusav.base_sale_attr_id
         left join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.id
    and skusav.spu_id = spusa.spu_id
    and skusav.sku_id = 49
where si.id = 49
ORDER BY spusa.base_sale_attr_id,skusav.sale_attr_value_id
image-20210920233853425

这两个功能的 SQL 已经准备好了,我们就开始完成我们的功能

1、查出该商品的spu的所有销售属性和属性值

2、标识出本商品对应的销售属性

③ SpuSaleAttrMapper

com.atguigu.gmall.product.mapper.SpuSaleAttrMapper

    /**
     * 按照skuId查出当前skuId所属的spu的所有销售属性值,并标识出哪个是这个sku的
     * @param skuId
     * @return
     */
    List<SpuSaleAttrValue> getSpuSaleAttrValue(@Param("skuId") Long skuId);

	// 上面的 getSpuSaleAttrValue 是返回 SpuSaleAttrValue 类型的 List 集合
	// 下面的 getSpuSaleAttrWithAllValueAndSkuCheck 返回的是 SpuSaleAttr 类型的 List 集合
    // SpuSaleAttr 里面嵌套了 SpuSaleAttrValue,为了与课件文档一致,我们用下面更加复杂的接口

	/**
     * 按照skuId查出当前skuId所属的spu的所有销售属性值,并标识出哪个是这个sku的
     *
     * 以组合关系展示
     * SpuSaleAttr {
     *     //颜色
     *
     *     //List<SpuSaleAttrValue></>
     * }
     * @param skuId
     * @return
     */
    List<SpuSaleAttr> getSpuSaleAttrWithAllValueAndSkuCheck(@Param("skuId") Long skuId);

getSpuSaleAttrWithAllValueAndSkuCheck 的 SQL

select spusa.*,
			 spusav.*,
			 if(skusav.id is null,0,1) is_checked
from sku_info si
         left join spu_sale_attr spusa on spusa.spu_id = si.spu_id
         left join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_id
    and spusa.base_sale_attr_id = spusav.base_sale_attr_id
         left join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.id
    and skusav.spu_id = spusa.spu_id
    and skusav.sku_id = 49
where si.id = 49
ORDER BY spusa.base_sale_attr_id,skusav.sale_attr_value_id

image-20210921105843157

④ SpuSaleAttrMapper.xml

getSpuSaleAttrWithAllValueAndSkuCheck 的 SQL 和 getSpuSaleAttrValue 是一样的,只不过封装不一样

自定义结果集 SpuSaleAttrResultMap 我们以前就有,但这次多了一个 is_checked,一会儿有一会儿没有,所以要用一个自动结果集 autoMapping,如果有 is_checked 就自动映射上,没有就不自动映射

    <select id="getSpuSaleAttrValue" resultType="com.atguigu.gmall.model.product.SpuSaleAttrValue">
        select si.id                       sku_id,
               spusav.base_sale_attr_id,
               spusav.sale_attr_value_name,
               spusa.sale_attr_name,
               if(skusav.id is null, 0, 1) is_checked
        from sku_info si
                 left join spu_sale_attr spusa on spusa.spu_id = si.spu_id
                 left join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_id
            and spusa.base_sale_attr_id = spusav.base_sale_attr_id
                 left join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.id
            and skusav.spu_id = spusa.spu_id
            and skusav.sku_id = #{skuId}
        where si.id = #{skuId}
        order by spusav.base_sale_attr_id, skusav.sale_attr_value_id
    </select>

    <resultMap id="SpuSaleAttrResultMap" type="com.atguigu.gmall.model.product.SpuSaleAttr">
        <id property="id" column="id"></id>
        <result property="spuId" column="spu_id"></result>
        <result property="baseSaleAttrId" column="base_sale_attr_id"></result>
        <result property="saleAttrName" column="sale_attr_name"></result>
        <collection property="spuSaleAttrValueList"
                    ofType="com.atguigu.gmall.model.product.SpuSaleAttrValue" autoMapping="true">
            <id property="id" column="sav_id"></id>
            <result property="saleAttrName" column="sale_attr_name"></result>
            <result property="baseSaleAttrId" column="base_sale_attr_id"></result>
            <result property="spuId" column="spu_id"></result>
            <result property="saleAttrValueName" column="sale_attr_value_name"></result>
        </collection>
    </resultMap>

<!--  按照skuid查出所有销售属性组合,以及标识出自己的销售属性组合   -->
    <select id="getSpuSaleAttrWithAllValueAndSkuCheck"
            resultMap="SpuSaleAttrResultMap">
        select spusa.*,
               spusav.id                   sav_id,
               spusav.*,
               if(skusav.id is null, 0, 1) is_checked
        from sku_info si
                 left join spu_sale_attr spusa on spusa.spu_id = si.spu_id
                 left join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_id
            and spusa.base_sale_attr_id = spusav.base_sale_attr_id
                 left join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.id
            and skusav.spu_id = spusa.spu_id
            and skusav.sku_id = #{skuId}
        where si.id = #{skuId}
        order by spusav.base_sale_attr_id, skusav.sale_attr_value_id
    </select>
⑤ ProductApiController
/**
 * 4、根据sku的id查出
 *      1、查出该sku商品的spu的所有销售属性和属性值
 *      2、标识出本sku商品对应的销售属性
 *      3、点击其他销售属性值的组合,跳转到另外的sku页面
 */

@GetMapping("inner/getSpuSaleAttrListCheckBySku/{skuId}/{spuId}")
public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(@PathVariable("skuId") Long skuId,
                                                         @PathVariable("spuId") Long spuId){
    return apiProductService.getSpuSaleAttrListCheckBySku(skuId,spuId);
}
⑥ ApiProductService
List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId);
⑦ ApiProductServiceImpl
    @Autowired
    SpuSaleAttrMapper spuSaleAttrMapper;
    
    @Override
    public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
        return spuSaleAttrMapper.getSpuSaleAttrWithAllValueAndSkuCheck(skuId);
    }
⑧ 测试看效果
(1)选择颜色

image-20210921110914347

(2)选择套装(版本)

image-20210921111043266

(3)选择服务

image-20210921111144375

(4)图片里面有个小 bug

上面的 SpuSaleAttrMapper.xml 中已经是改正后的

spuSaleAttrValueList 里面的 id 为 null

原因是在 SpuSaleAttrResultMap 内部的 spuSaleAttrValue 它的字段映射是 sav_id

image-20210921111702784

我们在查询 spuSaleAttrValue.* 即 spusav.* 的时候要单独把它的 id 拿出来起别名 sav_id,与 resultMap 中的一致

image-20210921112008215

此时就正常了

image-20210921112308007
(5)对应的 JSON 数据
//发送请求@GetMapping("inner/getSpuSaleAttrListCheckBySku/50/30")

//响应示例
[
  {
    "id": 61,
    "spuId": 30,
    "baseSaleAttrId": 1,
    "saleAttrName": "选择颜色",
    "spuSaleAttrValueList": [
      {
        "id": 128,
        "spuId": 30,
        "baseSaleAttrId": 1,
        "saleAttrValueName": "青色",
        "saleAttrName": "选择颜色",
        "isChecked": "0"
      },
      {
        "id": 127,
        "spuId": 30,
        "baseSaleAttrId": 1,
        "saleAttrValueName": "黑色",
        "saleAttrName": "选择颜色",
        "isChecked": "1"
      }
    ]
  },
  {
    "id": 62,
    "spuId": 30,
    "baseSaleAttrId": 3,
    "saleAttrName": "选择套装",
    "spuSaleAttrValueList": [
      {
        "id": 130,
        "spuId": 30,
        "baseSaleAttrId": 3,
        "saleAttrValueName": "12+256",
        "saleAttrName": "选择套装",
        "isChecked": "0"
      },
      {
        "id": 129,
        "spuId": 30,
        "baseSaleAttrId": 3,
        "saleAttrValueName": "8+128",
        "saleAttrName": "选择套装",
        "isChecked": "1"
      }
    ]
  },
  {
    "id": 63,
    "spuId": 30,
    "baseSaleAttrId": 4,
    "saleAttrName": "选择服务",
    "spuSaleAttrValueList": [
      {
        "id": 131,
        "spuId": 30,
        "baseSaleAttrId": 4,
        "saleAttrValueName": "三年免费换屏",
        "saleAttrName": "选择服务",
        "isChecked": "0"
      },
      {
        "id": 132,
        "spuId": 30,
        "baseSaleAttrId": 4,
        "saleAttrValueName": "每年免费换新",
        "saleAttrName": "选择服务",
        "isChecked": "1"
      }
    ]
  }
]

Tips:以结果为导向完成功能

6、获取销售信息(3)

点击其他销售属性值的组合,跳转到另外的sku页面

① 思路

实现思路:

img

img

1 、从页面中获得所有选中的销售属性进行组合比如:

“属性值1|属性值2” 用这个字符串匹配一个对照表,来获得skuId。并进行跳转,或者告知无货

2、后台要生成一个“属性值1|属性值2:skuId”的一个json串以提供页面进行匹配。如

img

3、需要从后台数据库查询出该 spu 下的所有 skuId 和属性值关联关系,然后加工成如上的 Json 串,用该 json串,跟前台匹配

② 分析
(1)SQL 语句(第一版)
# 知道当前sku对应的spu到底还有多少个sku
SELECT
	ssav.spu_id,
	ssav.sku_id,
	ssav.sale_attr_value_id,
	spusav.base_sale_attr_id,
	spusav.sale_attr_name,
	spusav.sale_attr_value_name
FROM
	sku_sale_attr_value ssav
LEFT JOIN spu_sale_attr_value spusav ON ssav.sale_attr_value_id = spusav.id
WHERE
	ssav.spu_id = 25
ORDER BY ssav.sku_id,spusav.base_sale_attr_id
image-20210921122644663 image-20210921120358607 image-20210921120647802

颜色:2 套餐:2 版本:2 总共有8个

{“127|129|132”:”50”,

“127|130|132”:”51”,

“128|129|131”:”52”} 可以对应一个Map

那么为什么不把商品的 id 作为 key,而是把组合作为 key 呢?

“127|130|132”:只是为了快,如果反过来就是 {“50”:“127|129|132”,“51”:“127|130|132”},这样就会慢一些

所以我们用这种 “销售属性值得组合”:”skuId”

我们再来精简一下 SQL 语句

(2)SQL 语句(第二版)
SELECT
	ssav.sku_id,
	ssav.sale_attr_value_id
FROM
	sku_sale_attr_value ssav
LEFT JOIN spu_sale_attr_value spusav ON ssav.sale_attr_value_id = spusav.id
WHERE
	ssav.spu_id = 25
ORDER BY ssav.sku_id,spusav.base_sale_attr_id
image-20210921122902147

还要继续改成前端需要的 “127|129|132”:”50”这种形式

(3)SQL 语句(第三版)
# 知道当前sku对应的spu到底还有多少个sku
SELECT
	ssav.sku_id,
	GROUP_CONCAT(ssav.sale_attr_value_id ORDER BY ssav.sale_attr_value_id SEPARATOR '|')value_ids
FROM
	sku_sale_attr_value ssav
LEFT JOIN spu_sale_attr_value spusav ON ssav.sale_attr_value_id = spusav.id
WHERE
	ssav.spu_id = 25
GROUP BY ssav.sku_id
image-20210921130048909
(4)group_concat函数

语法:group_concat( [DISTINCT] 要连接的字段 [Order BY 排序字段 ASC/DESC] [Separator ‘分隔符’] )

https://blog.csdn.net/u012620150/article/details/81945004

③ 自定义封装一个 SkuAllSaleValue
image-20211209204125755

新建 com.atguigu.gmall.product.bean.SkuAllSaleValue

@Data
public class SkuAllSaleValue {

    private String skuId;
    private String valueIds;

    /**
     * {"127|129|132":50}
     */
}

也可以不用封装一个 bean,直接从数据库查出来用 map 封装

④ SpuSaleAttrMapper
    /**
     * 根据spuId查到它下面所有sku对应的销售属性值,方便切换
     * @param spuId
     * @return
     */
    List<SkuAllSaleValue> getSkuAllSaleValue(@Param("spuId") Long spuId);
⑤ SpuSaleAttrMapper.xml
    <!-- 为页面准备的东西   -->
    <select id="getSkuAllSaleValue" resultType="com.atguigu.gmall.product.bean.SkuAllSaleValue">
        select skusav.sku_id,
               group_concat(skusav.sale_attr_value_id order by skusav.sale_attr_value_id separator '|') value_ids
        from sku_sale_attr_value skusav
                 left join spu_sale_attr_value spusav on skusav.sale_attr_value_id = spusav.id
        where skusav.spu_id = #{spuId}
        group by skusav.sku_id
    </select>
⑥ ProductApiController
    /**
     *  5、查询出spu下面的所有sku销售属性值可切换的信息
     *      {“127|129|132”:“50”}
     *      4.3 点击其他销售属性值的组合,跳转到另外的sku页面
     * @param spuId
     * @return
     */
    @GetMapping("inner/getSkuValueIdsMap/{spuId}")
    public Map getSkuValueIdsMap(@PathVariable("spuId") Long spuId){
        return apiProductService.getSkuValueIdsMap(spuId);
    }
⑦ ApiProductService
    Map getSkuValueIdsMap(Long spuId);
⑧ ApiProductServiceImpl
    @Override
    public Map getSkuValueIdsMap(Long spuId){
        List<SkuAllSaleValue> skuAllSaleValue = spuSaleAttrMapper.getSkuAllSaleValue(spuId);
        //map明确 sting string
        HashMap<String, String> map = new HashMap<>();
        if (!CollectionUtils.isEmpty(skuAllSaleValue)){
            for (SkuAllSaleValue allSaleValue : skuAllSaleValue) {
                //{"115|117":"44","114|117":"45"}
                map.put(allSaleValue.getValueIds(),allSaleValue.getSkuId());
            }
        }
        return map;
    }

⑨ 测试看效果

image-20210921141230891
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值