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、详情渲染功能介绍
商品详情所需构建的数据如下:
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](https://i-blog.csdnimg.cn/blog_migrate/40207f99d6b79c4de89308e82e59f2a7.png)
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](https://i-blog.csdnimg.cn/blog_migrate/3712a58d543ea3d86cdc0db5eefba265.png)
控制台:
zipkin 没有配置,所以报错
所以我们来吧 zipkin 的配置加上
⑥ application.yaml 2
server:
port: 9000
#怎么抽取全微服务都能用
spring:
zipkin:
base-url: http://192.168.200.188:9411/
sender:
type: web
![image-20210920181537199](https://i-blog.csdnimg.cn/blog_migrate/ea44a52b747f7407db671211d56548fa.png)
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](https://i-blog.csdnimg.cn/blog_migrate/f19829e26e111c4ddd0af059769bbd58.png)
![image-20210910142700076](https://i-blog.csdnimg.cn/blog_migrate/773dee707e5d85be69a150f397c8f15d.png)
远程调用:
![image-20210910143623528](https://i-blog.csdnimg.cn/blog_migrate/20ed933e22eeaf26c984787fa70e6e4b.png)
自己发
![image-20210910143736340](https://i-blog.csdnimg.cn/blog_migrate/9ef14209c88d993f71b26fd22f51de20.png)
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 宿主机与容器时间同步问题
① 问题情况
② 问题原因
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](https://i-blog.csdnimg.cn/blog_migrate/9653a6018db61d38596fbac1145b975f.png)
sku是挂在三级分类下面的,我们的分类信息分别在base_category1、base_category2、base_category3这三张表里面,目前需要通过sku表的三级分类id获取一级分类名称、二级分类名称和三级分类名称
解决方案:
我们可以建立一个视图(view),把三张表关联起来,视图id就是三级分类id,这样通过三级分类id就可以查询到相应数据,效果如下:
![image-20210920193119464](https://i-blog.csdnimg.cn/blog_migrate/68d8a61a0d69f377a2cc348191641609.png)
(2) 创建视图(虚表)
![image-20210920193230853](https://i-blog.csdnimg.cn/blog_migrate/eaf0196cce52181fe1c0c4acfa21c4fa.png)
再查就方便了
![image-20210920194206954](https://i-blog.csdnimg.cn/blog_migrate/5f8437cd088f6c59aa06eb69029d66af.png)
② 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](https://i-blog.csdnimg.cn/blog_migrate/62fed55b0b269bc69df0f3efd30a09a8.png)
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 查出:
- 查出该商品的spu的所有销售属性和属性值
- 标识出本商品对应的销售属性
- 点击其他销售属性值的组合,跳转到另外的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](https://i-blog.csdnimg.cn/blog_migrate/d48e55590d3cc127115154e0beba4f4b.png)
(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](https://i-blog.csdnimg.cn/blog_migrate/b417f76678f148ee49a1f269e2d7a504.png)
(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](https://i-blog.csdnimg.cn/blog_migrate/fe12407fdba7e6fc1441010cf4bb73e5.png)
这两个功能的 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
④ 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)选择颜色
(2)选择套装(版本)
(3)选择服务
(4)图片里面有个小 bug
上面的 SpuSaleAttrMapper.xml 中已经是改正后的
spuSaleAttrValueList 里面的 id 为 null
原因是在 SpuSaleAttrResultMap 内部的 spuSaleAttrValue 它的字段映射是 sav_id
![image-20210921111702784](https://i-blog.csdnimg.cn/blog_migrate/acdf191728ad975ae7fb70bb4f85f9c0.png)
我们在查询 spuSaleAttrValue.* 即 spusav.* 的时候要单独把它的 id 拿出来起别名 sav_id,与 resultMap 中的一致
![image-20210921112008215](https://i-blog.csdnimg.cn/blog_migrate/8ccb428a7ea0c440e31da9907e160c1d.png)
此时就正常了
![image-20210921112308007](https://i-blog.csdnimg.cn/blog_migrate/42935dbcb3e87e56d7e115b0585b48c3.png)
(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](https://i-blog.csdnimg.cn/blog_migrate/b772a5bbd53439d75e687399347a7591.jpeg)
1 、从页面中获得所有选中的销售属性进行组合比如:
“属性值1|属性值2” 用这个字符串匹配一个对照表,来获得skuId。并进行跳转,或者告知无货
2、后台要生成一个“属性值1|属性值2:skuId”的一个json串以提供页面进行匹配。如
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](https://i-blog.csdnimg.cn/blog_migrate/e42de814c69566bcc2a3414ca2aa0379.png)
![image-20210921120358607](https://i-blog.csdnimg.cn/blog_migrate/447df3f3a9b05cac2064435c79bd1644.png)
![image-20210921120647802](https://i-blog.csdnimg.cn/blog_migrate/0a72957c5d9b5885563a09cba6a73e07.png)
颜色: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](https://i-blog.csdnimg.cn/blog_migrate/8036796283202c6cd5bee79315614922.png)
还要继续改成前端需要的 “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](https://i-blog.csdnimg.cn/blog_migrate/0acd4e3745b7142392120563569998ed.png)
(4)group_concat函数
语法:group_concat( [DISTINCT] 要连接的字段 [Order BY 排序字段 ASC/DESC] [Separator ‘分隔符’] )
https://blog.csdn.net/u012620150/article/details/81945004
③ 自定义封装一个 SkuAllSaleValue
![image-20211209204125755](https://i-blog.csdnimg.cn/blog_migrate/15a8403b719f22c4014fc31d44ec3370.png)
新建 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](https://i-blog.csdnimg.cn/blog_migrate/12e3cf69696ecdfbf7d4ceff61b4fcd5.png)