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
@SpringBootApplicationpublic 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=""><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=8080spring.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接口
3、新建 service-item 模块
① pom.xml
<?xml version="1.0" encoding="UTF-8"?><project xmlns="/POM/4.0.0"xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /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-itemserver.port=9000spring.cloud.nacos.server-addr=192.168.200.188:8848
③ application.yaml
server:port: 9000
④ ItemApplication
/*** 所有和商品有关的数据 由service-product来做的,我们不操作,远程调用*/// 排除跟数据库有关的自动配置@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})@EnableDiscoveryClient@EnableCircuitBreakerpublic 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";}}
控制台:
zipkin 没有配置,所以报错
所以我们来吧 zipkin 的配置加上
⑥ application.yaml 2
server:port: 9000#怎么抽取全微服务都能用spring:zipkin:base-url: http://192.168.200.188:9411/sender:type: web
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 {@AutowiredSpuFeignClient spuFeignClient;// 测试远程调用请求@GetMapping("/hello")public Result<Page<SpuInfo>> hello(){// feign远程调用Result<Page<SpuInfo>> pageResult = spuFeignClient.spuInfoPageList(61L, 1L, 5L);return pageResult;}}
远程调用:
自己发
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@Slf4jpublic 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 {@AutowiredApiProductService 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
@Servicepublic class ApiProductServiceImpl implements ApiProductService {@AutowiredSkuInfoMapper skuInfoMapper;@AutowiredSkuImageMapper skuImageMapper;@Overridepublic 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#安装ntpyum -y install ntp#更新时间ntpdate #开机启动同步systemctl enable ntpd --now
(2)容器同步时间
#启动时设置-v /etc/localtime:/etc/localtime:ro
(3)容器镜像修改时间
# CentOSRUN echo "Asia/shanghai" > /etc/timezone;# UbuntuRUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
3、根据三级分类的id查询整个三级分类信息
① 写 SQL
(1) 根据三级分类的id查询整个三级分类信息
sku是挂在三级分类下面的,我们的分类信息分别在base_category1、base_category2、base_category3这三张表里面,目前需要通过sku表的三级分类id获取一级分类名称、二级分类名称和三级分类名称
解决方案:
我们可以建立一个视图(view),把三张表关联起来,视图id就是三级分类id,这样通过三级分类id就可以查询到相应数据,效果如下:
(2) 创建视图(虚表)
再查就方便了
② 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
@AutowiredBaseCategoryViewMapper baseCategoryViewMapper;/*** 根据三级分类 id,获取整个分类信息** @param category3Id* @return*/@Overridepublic BaseCategoryView getCategoryView(Long category3Id) {BaseCategoryView baseCategoryView = baseCategoryViewMapper.selectById(category3Id);return baseCategoryView;}
⑥ 测试
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*/@Overridepublic 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.idsku_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_namefrom sku_info sileft join spu_sale_attr spusa on spusa.spu_id = si.spu_idleft join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_idand spusa.base_sale_attr_id = spusav.base_sale_attr_idwhere si.id = 48;
(2)根据skuId查出它对应的spu的所有销售属性以及属性值组合,标识出当前sku属于哪一个
#根据skuId查出它对应的spu的所有销售属性以及属性值组合#标识出当前sku属于哪一个select si.idsku_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_idfrom sku_info sileft join spu_sale_attr spusa on spusa.spu_id = si.spu_idleft join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_idand spusa.base_sale_attr_id = spusav.base_sale_attr_idleft join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.idand skusav.spu_id = spusa.spu_idand skusav.sku_id = 49where si.id = 49ORDER BY spusa.base_sale_attr_id,skusav.sale_attr_value_id
(3)与 SpuSaleAttrValue 对应版(我们需要的)
#根据skuId查出它对应的spu的所有销售属性以及属性值组合#标识出当前sku属于哪一个#SpuSaleAttrValue:spu_id、base_sale_attr_id、sale_attr_value_name、sale_attr_name、isCheckedselect si.idsku_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_checkedfrom sku_info sileft join spu_sale_attr spusa on spusa.spu_id = si.spu_idleft join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_idand spusa.base_sale_attr_id = spusav.base_sale_attr_idleft join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.idand skusav.spu_id = spusa.spu_idand skusav.sku_id = 49where si.id = 49ORDER BY spusa.base_sale_attr_id,skusav.sale_attr_value_id
这两个功能的 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_checkedfrom sku_info sileft join spu_sale_attr spusa on spusa.spu_id = si.spu_idleft join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_idand spusa.base_sale_attr_id = spusav.base_sale_attr_idleft join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.idand skusav.spu_id = spusa.spu_idand skusav.sku_id = 49where si.id = 49ORDER 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_checkedfrom sku_info sileft join spu_sale_attr spusa on spusa.spu_id = si.spu_idleft join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_idand spusa.base_sale_attr_id = spusav.base_sale_attr_idleft join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.idand skusav.spu_id = spusa.spu_idand 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_checkedfrom sku_info sileft join spu_sale_attr spusa on spusa.spu_id = si.spu_idleft join spu_sale_attr_value spusav on spusav.spu_id = spusa.spu_idand spusa.base_sale_attr_id = spusav.base_sale_attr_idleft join sku_sale_attr_value skusav on skusav.sale_attr_value_id = spusav.idand skusav.spu_id = spusa.spu_idand 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
@AutowiredSpuSaleAttrMapper spuSaleAttrMapper;@Overridepublic 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
我们在查询 spuSaleAttrValue.* 即 spusav.* 的时候要单独把它的 id 拿出来起别名 sav_id,与 resultMap 中的一致
此时就正常了
(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页面
① 思路
实现思路:
1 、从页面中获得所有选中的销售属性进行组合比如:
“属性值1|属性值2” 用这个字符串匹配一个对照表,来获得skuId。并进行跳转,或者告知无货
2、后台要生成一个“属性值1|属性值2:skuId”的一个json串以提供页面进行匹配。如
3、需要从后台数据库查询出该 spu 下的所有 skuId 和属性值关联关系,然后加工成如上的 Json 串,用该 json串,跟前台匹配
② 分析
(1)SQL 语句(第一版)
# 知道当前sku对应的spu到底还有多少个skuSELECTssav.spu_id,ssav.sku_id,ssav.sale_attr_value_id,spusav.base_sale_attr_id,spusav.sale_attr_name,spusav.sale_attr_value_nameFROMsku_sale_attr_value ssavLEFT JOIN spu_sale_attr_value spusav ON ssav.sale_attr_value_id = spusav.idWHEREssav.spu_id = 25ORDER BY ssav.sku_id,spusav.base_sale_attr_id
颜色: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 语句(第二版)
SELECTssav.sku_id,ssav.sale_attr_value_idFROMsku_sale_attr_value ssavLEFT JOIN spu_sale_attr_value spusav ON ssav.sale_attr_value_id = spusav.idWHEREssav.spu_id = 25ORDER BY ssav.sku_id,spusav.base_sale_attr_id
还要继续改成前端需要的“127|129|132”:”50”
这种形式
(3)SQL 语句(第三版)
# 知道当前sku对应的spu到底还有多少个skuSELECTssav.sku_id,GROUP_CONCAT(ssav.sale_attr_value_id ORDER BY ssav.sale_attr_value_id SEPARATOR '|')value_idsFROMsku_sale_attr_value ssavLEFT JOIN spu_sale_attr_value spusav ON ssav.sale_attr_value_id = spusav.idWHEREssav.spu_id = 25GROUP BY ssav.sku_id
(4)group_concat函数
语法:group_concat( [DISTINCT] 要连接的字段 [Order BY 排序字段 ASC/DESC] [Separator ‘分隔符’] )
/u01260/article/details/81945004
③ 自定义封装一个 SkuAllSaleValue
新建 com.atguigu.gmall.product.bean.SkuAllSaleValue
@Datapublic 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_idsfrom sku_sale_attr_value skusavleft join spu_sale_attr_value spusav on skusav.sale_attr_value_id = spusav.idwhere 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
@Overridepublic Map getSkuValueIdsMap(Long spuId){List<SkuAllSaleValue> skuAllSaleValue = spuSaleAttrMapper.getSkuAllSaleValue(spuId);//map明确 sting stringHashMap<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;}
⑨ 测试看效果