## 第4章-商品SKU管理 **学习目标:** - 熟悉商品SKU相关的数据模型 - 完成保存商品SKU功能 - 完成商品SKU列表功能 - 完成商品SKU上下架功能 - 了解商品详情业务 - 模板技术Thymeleaf入门 # 1. 业务介绍 ## 1.1 数据库表结构 根据以上的需求,以此将SKU关联的数据库表结构设计为如下: ![img](assets/day04/wps12.jpg) ## 1.2 数据准备 ### 1.2.1 平台属性添加 ![img](assets/day04/wps13.jpg) ### 1.2.2 商品spu管理 ![img](assets/day04/wps14.jpg) 添加销售属性信息 ![img](assets/day04/wps15.jpg) # 2. 保存skuInfo功能 ## 2.1 生成基础代码 在代码生成器中,生成商品SKU相关表基础代码到`service-product`商品微服务模块,相关表名称如下 - sku_info: 商品sku信息表 - sku_sale_attr_value:sku销售属性值关联表 - sku_attr_value:sku平台属性值关联表 - sku_image:sku商品图片表 但是这里我们不建议生成以上相关表三层类,这样同时生成的三层类过于多,而以上的6张表都跟商品SKU相关,故建议只提供对应的实体类跟持久层接口。业务层/持久层我们提供一个抽取SkuManagerXxx **注意**:在`mybatis-plus-code`代码生成器模块中`CodeGenerator`设置禁用模板来达到目标 ![image-20221212231530167](assets/image-20221212231530167.png) 代码生成器执行需要填写的表:sku_info,sku_sale_attr,sku_sale_attr_value,sku_attr_value,sku_image ## 2.2 销售属性 ![image-20221213011411802](assets/image-20221213011411802.png) > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/235 ### 2.2.1. 编写控制器 在SpuManageController处理查询SPU销售属性方法 ```java /** * 根据商品SPUID查询销售属性名称以及值 * @param spuId * @return */ @GetMapping("/spuSaleAttrList/{spuId}") public Result> getSpuSaleAttrList(@PathVariable("spuId") Long spuId){ List list = spuManageService.getSpuSaleAttrList(spuId); return Result.ok(list); } ``` ### 2.2.2. 业务接口以及实现类 SpuManageService中增加接口 ```java /** * 根据spuId 查询销售属性集合 * @param spuId * @return */ List getSpuSaleAttrList(Long spuId); ``` SpuManageServiceImpl实现类中实现该方法 ```java /** * 根据spuId 查询销售属性集合 * @param spuId * @return */ @Override public List getSpuSaleAttrList(Long spuId) { return spuSaleAttrMapper.selectSpuSaleAttrList(spuId); } ``` ### 2.2.3. 持久层 SpuSaleAttrMapper中增加自定义接口 ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.SpuSaleAttr; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; import java.util.List; /** * spu销售属性 Mapper 接口 * * @author atguigu * @since 2022-12-24 */ public interface SpuSaleAttrMapper extends BaseMapper { /** * 根据SPUID查询商品Spu销售属性名称列表 包含spu销售属性值 * @param spuId * @return */ List getSpuSaleAttrList(@Param("spuId") Long spuId); } ``` 对应的在resource/mapper目录下创建映射文件SpuSaleAttrMapper.xml ```xml ``` ## 2.3 图片加载功能 ![image-20221213011431420](assets/image-20221213011431420.png) > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/243 功能分析:图片列表是根据spuId得来,涉及到的数据库表spu_image ### 2.3.1. 添加控制器 在`service-product`模块中Spu控制器类:SpuManageController ```java /** * 根据spuId 查询spuImageList图片列表 * * @param spuId * @return */ @GetMapping("spuImageList/{spuId}") public Result> getSpuImageList(@PathVariable("spuId") Long spuId) { List spuImageList = spuManageService.getSpuImageList(spuId); return Result.ok(spuImageList); } ``` ### 2.3.2. 业务接口与实现类 在`SpuManageService`接口中新增方法 ```java /** * 根据spuId 查询spuImageList * @param spuId * @return */ List getSpuImageList(Long spuId); ``` 在`SpuManageServiceImpl`中实现上面方法 ```java /** * 根据spuId 查询spuImageList * @param spuId * @return */ @Override public List getSpuImageList(Long spuId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SpuImage::getSpuId, spuId); return spuImageMapper.selectList(queryWrapper); } ``` ## 2.4 保存SKU > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/227 ### 2.4.1. 编写控制器 新建SkuManageController中提供处理保存SKU方法 ```Java package com.atguigu.gmall.product.controller; import com.atguigu.gmall.common.result.Result; import com.atguigu.gmall.product.model.SkuInfo; import com.atguigu.gmall.product.service.SkuManageService; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @Api(tags = "商品SKU控制器") @RestController @RequestMapping("admin/product") public class SkuManageController { @Autowired private SkuManageService skuManageService; /** * 保存SKU信息 * * @param skuInfo SKU相关信息 * @return */ @PostMapping("/saveSkuInfo") public Result saveSkuInfo(@RequestBody SkuInfo skuInfo) { skuManageService.saveSkuInfo(skuInfo); return Result.ok(); } } ``` ### 2.4.2. 业务接口与实现 创建业务接口:SkuManageService接口中增加方法 ```java package com.atguigu.gmall.product.service; import com.atguigu.gmall.product.model.SkuInfo; public interface SkuManageService { /** * 保存sku * @param skuInfo * @return */ void saveSkuInfo(SkuInfo skuInfo); } ``` 创建业务实现类:SkuManageServiceImpl实现类中实现上面方法 ```java package com.atguigu.gmall.product.service.impl; import com.atguigu.gmall.product.mapper.SkuAttrValueMapper; import com.atguigu.gmall.product.mapper.SkuImageMapper; import com.atguigu.gmall.product.mapper.SkuInfoMapper; import com.atguigu.gmall.product.mapper.SkuSaleAttrValueMapper; import com.atguigu.gmall.product.model.*; import com.atguigu.gmall.product.service.SkuManageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.util.List; /** * @author: atguigu * @create: 2022-12-13 01:15 */ @Service public class SkuManageServiceImpl implements SkuManageService { @Autowired private SkuInfoMapper skuInfoMapper; @Autowired private SkuImageMapper skuImageMapper; @Autowired private SkuSaleAttrValueMapper skuSaleAttrValueMapper; @Autowired private SkuAttrValueMapper skuAttrValueMapper; /** * 保存SKU信息 * 1.将SKU基本信息存入sku_info表中 * 2.将提交SKU图片存入sku_image表 关联SKU 设置sku_id逻辑外键 * 3.将提交的平台属性列表 批量保存 sku_attr_value 关联SKU 设置sku_id逻辑外键 * 4.将提交的销售属性列表 批量保存 sku_sale_attr_value 关联SKU 设置sku_id逻辑外键 * * @param skuInfo SKU相关信息 */ @Override @Transactional(rollbackFor = Exception.class) public void saveSkuInfo(SkuInfo skuInfo) { //1.将SKU基本信息存入sku_info表中 skuInfoMapper.insert(skuInfo); //2.将提交SKU图片存入sku_image表 关联SKU 设置sku_id逻辑外键 if (!CollectionUtils.isEmpty(skuInfo.getSkuImageList())) { skuInfo.getSkuImageList().stream().forEach(skuImage -> { //2.1 关联商品SKU 设置逻辑外键sku_Id skuImage.setSkuId(skuInfo.getId()); //2.2 执行批量保存 skuImageMapper.insert(skuImage); }); } //3.将提交的平台属性列表 批量保存 sku_attr_value 关联SKU 设置sku_id逻辑外键 if (!CollectionUtils.isEmpty(skuInfo.getSkuAttrValueList())) { skuInfo.getSkuAttrValueList().stream().forEach(skuAttrValue -> { //3.1 关联商品SKU 设置逻辑外键sku_Id skuAttrValue.setSkuId(skuInfo.getId()); //3.2 保存商品SKU平台属性值 skuAttrValueMapper.insert(skuAttrValue); }); } //4.将提交的销售属性列表 批量保存 sku_sale_attr_value 关联SKU 设置sku_id逻辑外键 if (!CollectionUtils.isEmpty(skuInfo.getSkuSaleAttrValueList())) { skuInfo.getSkuSaleAttrValueList().stream().forEach(skuSaleAttrValue -> { //4.1 关联商品SPU 设置逻辑外键spu_Id skuSaleAttrValue.setSpuId(skuInfo.getSpuId()); //4.2 关联商品SKU 设置逻辑外键sku_Id skuSaleAttrValue.setSkuId(skuInfo.getId()); //4.3 完成保存 skuSaleAttrValueMapper.insert(skuSaleAttrValue); }); } } } ``` # 3. SKU列表及上下架处理 ## 3.1 SKU列表 > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/179 ### 3.1.1 编写控制器 SkuManageController 控制器 ```java /** * 查询商品SKU列表 * @param page 页面 * @param limit 页大小 * @param skuInfo 查询条件 * @return */ @GetMapping("/list/{page}/{limit}") public Result getSkuInfoPage(@PathVariable Long page, @PathVariable Long limit, SkuInfo skuInfo){ // 构建一个Page对象 Page skuInfoPage = new Page<>(page,limit); // 调用服务层方法! IPage iPage = skuManageService.getSkuInfoPage(skuInfoPage,skuInfo); return Result.ok(iPage); } ``` ### 3.1.2 业务接口与实现类 在SkuManageService 接口中添加方法 ```java /** * 根据三级分类数据获取到skuInfo 列表 * @param skuInfoPage * @param skuInfo * @return */ IPage getSkuInfoPage(Page skuInfoPage, SkuInfo skuInfo); ``` 在SkuManageServiceImpl 实现类中实现上面方法 ```java /** * 根据分类ID查询商品SKU列表 * * @param skuInfoPage * @param skuInfo 查询条件 * @return */ @Override public IPage getSkuInfoPage(Page skuInfoPage, SkuInfo skuInfo) { //1.构建查询条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (skuInfo.getCategory3Id() != null) { queryWrapper.eq(SkuInfo::getCategory3Id, skuInfo.getCategory3Id()); } queryWrapper.orderByDesc(SkuInfo::getUpdateTime); //2.执行查询 return skuInfoMapper.selectPage(skuInfoPage, queryWrapper); } ``` ## 3.2 上下架处理 > YAPI接口地址: > > - 上架业务:http://192.168.200.128:3000/project/11/interface/api/195 > > - 下架业务:http://192.168.200.128:3000/project/11/interface/api/203 ### 3.2.1 编写控制器 SkuManageController 控制器 ```java /** * 商品上架 * @param skuId * @return */ @GetMapping("onSale/{skuId}") public Result onSale(@PathVariable("skuId") Long skuId) { skuManageService.onSale(skuId); return Result.ok(); } /** * 商品下架 * @param skuId * @return */ @GetMapping("cancelSale/{skuId}") public Result cancelSale(@PathVariable("skuId") Long skuId) { skuManageService.cancelSale(skuId); return Result.ok(); } ``` ### 3.2.2 业务接口与实现类 在SkuManageService 接口中新增方法 ```java /** * 商品上架 * @param skuId */ void onSale(Long skuId); /** * 商品下架 * @param skuId */ void cancelSale(Long skuId); ``` 在SkuManageServiceImpl 实现类中实现上面方法 ```java /** * 将指定SKU进行上架 * TODO 同步更新索引库的上下架状态 * @param skuId * @return */ @Override public void onSale(Long skuId) { SkuInfo skuInfo = new SkuInfo(); skuInfo.setId(skuId); skuInfo.setIsSale(1); skuInfoMapper.updateById(skuInfo); } /** * 将指定SKU进行下架 * * @param skuId * @return */ @Override public void cancelSale(Long skuId) { SkuInfo skuInfo = new SkuInfo(); skuInfo.setId(skuId); LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); //设置更新字段 updateWrapper.set(SkuInfo::getIsSale, 0); //设置更新条件 updateWrapper.eq(SkuInfo::getId, skuId); skuInfoMapper.update(skuInfo, updateWrapper); } ``` # 4. 商品详情相关业务介绍 - 商品详情页,简单说就是以购物者的角度展现一个sku的详情信息。 - 用户点击不同的销售属性值切换不同的商品 - 点击添加购物车,将商品放入购物车列表中 ![img](assets/wps1.png) # 5. 模板技术Thymeleaf介绍 ## 5.1 Thymeleaf 简介 ​ Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比, Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用! 类似模板技术 - freemarker - [Velocity](http://velocity.apache.org/) 官方网站:https://www.thymeleaf.org/index.html ## 5.2 快速入门 1. 新建一个demo模块:thymeleaf-demo,依赖模块web,Thymeleaf.模板。 ![](assets/image-20221213104301629.png) 2. pom.xml ```xml 4.0.0 com.atguigu thymeleaf-demo 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 2.3.6.RELEASE org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test ``` 3. 启动类 ```java package com.atguigu.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author: atguigu * @create: 2022-12-13 10:45 */ @SpringBootApplication public class ThymeleafDemoApp { public static void main(String[] args) { SpringApplication.run(ThymeleafDemoApp.class, args); } } ``` 4. 创建application.yml ```yaml server: port: 9999 spring: thymeleaf: #关闭Thymeleaf的缓存 cache: false ``` 5. 不需要做任何配置,启动器已经帮我们把Thymeleaf的视图器配置完成: ![1526435647041](assets/1526435647041.png) 而且,还配置了模板文件(html)的位置,与jsp类似的前缀+ 视图名 + 后缀风格: ![1526435706301](assets/1526435706301.png) - 默认前缀:`classpath:/templates/` - 默认后缀:`.html` 所以如果我们返回视图:`users`,会指向到 `classpath:/templates/users.html` 6. 准备一个controller,控制视图跳转。**注意:控制器上要使用@Controller注解** ```java package com.atguigu.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; /** * @author: atguigu * @create: 2022-12-13 10:56 */ @Controller public class HelloController { @GetMapping("/hello") public String show1(Model model){ model.addAttribute("msg", "Hello, Thymeleaf!"); return "hello"; } } ``` 7. 在resources目录下新建文件夹:templates ![image-20221213105846344](assets/image-20221213105846344.png) 8. 在`templates`目录下新建一个html模板文件:hello.html ```html hello

你好

``` 9. 启动项目,访问页面: ![image-20221213110053156](assets/image-20221213110053156.png) ### 5.2.1 设置头文件 就像JSP的<%@Page %>一样 ,Thymeleaf的也要引入标签规范。不加这个虽然不影响程序运行,但是你的idea会不识别标签,不方便开发。 ```html ``` ### 5.2.2 赋值字符串拼接 > request.setAttribute("name", "刘德华"); ```html

``` ### 5.2.3 循环 > List list = Arrays.asList("郑爽", "刘德华", "张惠妹", "成龙"); > > request.setAttribute("list", list); ```html
``` | 语法关键字 | 解释 | | ---------- | ------------------------------------- | | stat | 称作状态变量 | | index | 当前迭代对象的 index(从 0 开始计算) | | count | 当前迭代对象的 index(从 1 开始计算) | | size | 被迭代对象的大小 | | even/odd | 布尔值,当前循环是否是偶数/奇数 | | first | 布尔值,当前循环是否是第一个 | | last | 布尔值,当前循环是否是最后一个 | ### 5.2.4 判断 th:if 条件成立显示 th:unless 条件不成立的时候才会显示内容 > model.addAttribute("age",18); ```html

判断 if

good
atguigu

判断 三元

``` ### 5.2.5 取session中的属性 > httpSession.setAttribute("addr","北京中南海"); ```html
``` ### 5.2.6 引用内嵌页 top.html内容如下 ```html hello 我是公共的内容组件 ``` 在hello.html中引入 ```html
``` ### 5.2.7 th:utext :解析样式 th:utext:识别html中的标签 > request.setAttribute("gname","绿色"); ```html

color

``` ### 5.2.8 点击链接传值 > ```java > @RequestMapping("list.html") > public String list(String category1Id, HttpServletRequest request){ > // 接收传递过来的数据 > System.out.println("获取到的数据:\t"+category1Id); > /*保存 category1Id*/ > request.setAttribute("category1Id",category1Id); > return category1Id; > } > ``` ```html 点我带你飞 ``` ### 5.2.9 多种存储方式 > ```java > HashMap map = new HashMap<>(); > map.put("stuNo","1000"); > map.put("stuName","张三"); > model.addAllAttributes(map); > ``` ```html

多种方式存储数据

```