学习目标:
根据以上的需求,以此将SKU关联的数据库表结构设计为如下:
【金山文档】 08商品SKU-相关表关系分析 https://kdocs.cn/l/cdWifG5v76ls
添加销售属性信息
在代码生成器中,生成商品SKU相关表基础代码到service-product
商品微服务模块,相关表名称如下
但是这里我们不建议生成以上相关表三层类,这样同时生成的三层类过于多,而以上的6张表都跟商品SKU相关,故建议只提供对应的实体类跟持久层接口。业务层/持久层我们提供一个抽取SkuManagerXxx
注意:在mybatis-plus-code
代码生成器模块中CodeGenerator
设置禁用模板来达到目标
代码生成器执行需要填写的表:sku_info,sku_image,sku_attr_value,sku_sale_attr_value
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/243
在SpuManageController处理查询SPU图片列表方法
/**
* 根据spuId 获取spu图片集合
* @param spuId
* @return
*/
@ApiOperation("根据spuId 获取spu图片集合")
@GetMapping("/spuImageList/{spuId}")
public Result getSpuImageList(@PathVariable("spuId") Long spuId){
List<SpuImage> list = spuManageService.getSpuImageList(spuId);
return Result.ok(list);
}
SpuManageService中增加接口
/**
* 根据spuId 获取spu图片集合
* @param spuId
* @return
*/
List<SpuImage> getSpuImageList(Long spuId);
SpuManageServiceImpl实现类中实现该方法
/**
* 根据商品spuID查询当前商品所有商品图片
*
* @param spuId
* @return
*/
@Override
public List<SpuImage> getSpuImageList(Long spuId) {
//1.构建查询条件
LambdaQueryWrapper<SpuImage> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SpuImage::getSpuId, spuId);
queryWrapper.select(SpuImage::getId, SpuImage::getSpuId, SpuImage::getImgName, SpuImage::getImgUrl);
//2.执行条件查询
return spuImageService.list(queryWrapper);
}
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/235
/**
* 根据spuId 查询销售属性
* @param spuId
* @return
*/
@ApiOperation("根据spuId 查询销售属性")
@GetMapping("/spuSaleAttrList/{spuId}")
public Result getSpuSaleAttrList(@PathVariable("spuId") Long spuId){
List<SpuSaleAttr> list = spuManageService.getSpuSaleAttrList(spuId);
return Result.ok(list);
}
在SpuManageService
接口中新增方法
/**
* 根据spuId 查询销售属性
* @param spuId
* @return
*/
List<SpuSaleAttr> getSpuSaleAttrList(Long spuId);
在SpuManageServiceImpl
中实现上面方法
/**
* 根据spuId 查询销售属性
* @param spuId
* @return
*/
@Override
public List<SpuSaleAttr> getSpuSaleAttrList(Long spuId) {
//通过spu销售属性业务层直接获取对应持久层接口
SpuSaleAttrMapper spuSaleAttrMapper = (SpuSaleAttrMapper) spuSaleAttrService.getBaseMapper();
return spuSaleAttrMapper.getSpuSaleAttrList(spuId);
}
SpuSaleAttrMapper中增加自定义接口
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 2023-08-30
*/
public interface SpuSaleAttrMapper extends BaseMapper<SpuSaleAttr> {
/**
* 根据spuID查询当前spu商品所有销售属性以及销售属性值
* @param spuId
* @return
*/
List<SpuSaleAttr> getSpuSaleAttrList(@Param("spuId") Long spuId);
}
对应的在resource/mapper目录下创建映射文件SpuSaleAttrMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.gmall.product.mapper.SpuSaleAttrMapper">
<!--销售属性销售属性一对多配置-->
<resultMap id="spuSaleAttrMap" type="com.atguigu.gmall.product.model.SpuSaleAttr" autoMapping="true">
<id column="id" property="id"></id>
<!--配置多方集合-->
<collection property="spuSaleAttrValueList" ofType="com.atguigu.gmall.product.model.SpuSaleAttrValue" autoMapping="true">
<id column="spu_sale_attr_value_id" property="id"></id>
</collection>
</resultMap>
<select id="getSpuSaleAttrList" resultMap="spuSaleAttrMap">
SELECT
ssa.id,
ssa.spu_id,
ssa.base_sale_attr_id,
ssa.sale_attr_name,
ssav.id spu_sale_attr_value_id,
ssav.sale_attr_value_name
FROM
spu_sale_attr ssa
INNER JOIN spu_sale_attr_value ssav ON ssav.spu_id = ssa.spu_id
AND ssav.base_sale_attr_id = ssa.base_sale_attr_id
WHERE
ssa.spu_id = #{spuId} and ssa.is_deleted = 0 and ssav.is_deleted = 0
</select>
</mapper>
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/227
新建SkuManageController中提供处理保存SKU方法
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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiOperation;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.ReactiveSubscription;
import org.springframework.web.bind.annotation.*;
/**
* @author: atguigu
* @create: 2023-09-01 11:43
*/
@RestController
@RequestMapping("/admin/product")
public class SkuManageController {
@Autowired
private SkuManageService skuManageService;
/**
* 保存sku商品
*
* @param skuInfo
* @return
*/
@ApiOperation("保存SKU商品")
@PostMapping("/saveSkuInfo")
public Result saveSkuInfo(@RequestBody SkuInfo skuInfo) {
skuManageService.saveSkuInfo(skuInfo);
return Result.ok();
}
}
创建业务接口:SkuManageService接口中增加方法
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实现类中实现上面方法
package com.atguigu.gmall.product.service.impl;
import com.atguigu.gmall.product.model.SkuAttrValue;
import com.atguigu.gmall.product.model.SkuImage;
import com.atguigu.gmall.product.model.SkuInfo;
import com.atguigu.gmall.product.model.SkuSaleAttrValue;
import com.atguigu.gmall.product.service.*;
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: 2023-09-01 11:43
*/
@Service
public class SkuManageServiceImpl implements SkuManageService {
@Autowired
private SkuInfoService skuInfoService;
@Autowired
private SkuImageService skuImageService;
@Autowired
private SkuAttrValueService skuAttrValueService;
@Autowired
private SkuSaleAttrValueService skuSaleAttrValueService;
/**
* 保存sku商品
* 1.将前端提交sku商品基本信息封装到SkuInfo对象,向sku_info新增一条记录
* 2.将提交多张图片列表封装SkuImage集合中,批量向sku_image表新增多条记录
* 3.将提交多个平台属性列表封装SkuAttrValue集合中,批量向sku_attr_value表新增多条记录
* 4.将提交多个销售属性列表封装SkuSaleAttrValue集合中,批量向sku_sale_attr_value表新增多条记录
*
* @param skuInfo
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void saveSkuInfo(SkuInfo skuInfo) {
//1.将前端提交sku商品基本信息封装到SkuInfo对象,向sku_info新增一条记录
skuInfoService.save(skuInfo);
Long skuId = skuInfo.getId();
//2.将提交多张图片列表封装SkuImage集合中,批量向sku_image表新增多条记录
List<SkuImage> skuImageList = skuInfo.getSkuImageList();
if (!CollectionUtils.isEmpty(skuImageList)) {
//2.1 将商品图片关联SKUID
for (SkuImage skuImage : skuImageList) {
skuImage.setSkuId(skuId);
}
//2.2 批量保存
skuImageService.saveBatch(skuImageList);
}
//3.将提交多个平台属性列表封装SkuAttrValue集合中,批量向sku_attr_value表新增多条记录
List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
if (!CollectionUtils.isEmpty(skuAttrValueList)) {
//3.1 将平台属性关联SKUID
for (SkuAttrValue skuAttrValue : skuAttrValueList) {
skuAttrValue.setSkuId(skuId);
}
//3.2 批量保存
skuAttrValueService.saveBatch(skuAttrValueList);
}
//4.将提交多个销售属性列表封装SkuSaleAttrValue集合中,批量向sku_sale_attr_value表新增多条记录
List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
if (!CollectionUtils.isEmpty(skuSaleAttrValueList)) {
//4.1 将销售属性关联SKUID,SPUID
for (SkuSaleAttrValue skuSaleAttrValue : skuSaleAttrValueList) {
skuSaleAttrValue.setSkuId(skuId);
skuSaleAttrValue.setSpuId(skuInfo.getSpuId());
}
//4.2 批量保存
skuSaleAttrValueService.saveBatch(skuSaleAttrValueList);
}
}
}
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/179
SkuManageController 控制器
/**
* 分页查询商品SKU列表
*
* @param page
* @param limit
* @param category3Id
* @return
*/
@ApiOperation("分页查询商品SKU列表")
@GetMapping("/list/{page}/{limit}")
public Result getSkuList(@PathVariable("page") int page, @PathVariable("limit") int limit, @RequestParam("category3Id") Long category3Id) {
Page<SkuInfo> skuInfoPage = new Page<>(page, limit);
skuInfoPage = skuManageService.getSkuList(skuInfoPage, category3Id);
return Result.ok(skuInfoPage);
}
在SkuManageService 接口中添加方法
/**
* 分页查询商品SKU列表
*
* @param skuInfoPage 分页对象
* @param category3Id 三级分类ID
* @return
*/
Page<SkuInfo> getSkuList(Page<SkuInfo> skuInfoPage, Long category3Id);
在SkuManageServiceImpl 实现类中实现上面方法
/**
* 分页查询商品SKU列表
*
* @param skuInfoPage 分页对象
* @param category3Id 三级分类ID
* @return
*/
@Override
public Page<SkuInfo> getSkuList(Page<SkuInfo> skuInfoPage, Long category3Id) {
//1.构建条件对象
LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SkuInfo::getCategory3Id, category3Id);
queryWrapper.select(SkuInfo::getId, SkuInfo::getSkuName,SkuInfo::getPrice, SkuInfo::getSkuDefaultImg, SkuInfo::getSkuDesc, SkuInfo::getIsSale, SkuInfo::getSpuId);
queryWrapper.orderByDesc(SkuInfo::getId, SkuInfo::getUpdateTime);
//2.执行分页查询
return skuInfoService.page(skuInfoPage, queryWrapper);
}
YAPI接口地址:
SkuManageController 控制器
/**
* 对商品SKU上架处理
*
* @param skuId
* @return
*/
@GetMapping("/onSale/{skuId}")
public Result onSale(@PathVariable("skuId") Long skuId) {
skuManageService.onSale(skuId);
return Result.ok();
}
/**
* 对商品SKU下架处理
*
* @param skuId
* @return
*/
@GetMapping("/cancelSale/{skuId}")
public Result cancelSale(@PathVariable("skuId") Long skuId) {
skuManageService.cancelSale(skuId);
return Result.ok();
}
在SkuManageService 接口中新增方法
/**
* 对商品SKU上架处理
*
* @param skuId
* @return
*/
void onSale(Long skuId);
/**
* 对商品SKU下架处理
*
* @param skuId
* @return
*/
void cancelSale(Long skuId);
在SkuManageServiceImpl 实现类中实现上面方法
/**
* 上架商品
* @param skuId
*/
@Override
public void onSale(Long skuId) {
//1.修改商品表中商品状态
SkuInfo skuInfo = new SkuInfo(skuId);
skuInfo.setIsSale(1);
skuInfoService.updateById(skuInfo);
//2.TODO 基于MQ异步消息通知搜索服务新增索引库文档
}
/**
* 下架商品
* @param skuId
*/
@Override
public void cancelSale(Long skuId) {
//1.修改商品表中商品状态 update sku_info set is_sale=? where id = ?
LambdaUpdateWrapper<SkuInfo> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(SkuInfo::getIsSale, 0);
updateWrapper.eq(SkuInfo::getId, skuId);
skuInfoService.update(updateWrapper);
//2.TODO 基于MQ异步消息通知搜索服务删除索引库文档
}
Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比, Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用!
类似模板技术
官方网站:https://www.thymeleaf.org/index.html
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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>thymeleaf-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--引入spring boot 父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
启动类
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);
}
}
创建application.yml
server:
port: 9999
spring:
thymeleaf:
#关闭Thymeleaf的缓存
cache: false
不需要做任何配置,启动器已经帮我们把Thymeleaf的视图器配置完成:
而且,还配置了模板文件(html)的位置,与jsp类似的前缀+ 视图名 + 后缀风格:
![1526435706301](assets/1526435706301.png)
classpath:/templates/
.html
所以如果我们返回视图:users
,会指向到 classpath:/templates/users.html
准备一个controller,控制视图跳转。注意:控制器上要使用@Controller注解
package com.atguigu.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.ResultSet;
/**
* @author: atguigu
* @create: 2023-09-01 15:39
*/
@Controller
public class HelloController {
/**
* 方式一:动态在服务端渲染静态页面
*
* @param msg
* @return
*/
@GetMapping("/hello")
public String show1(Model model, @RequestParam("msg") String msg) {
model.addAttribute("msg", msg);
return "hello";
}
@Autowired
private TemplateEngine templateEngine;
/**
* 方式二:将渲染后页面,保存到本地磁盘上(让Nginx进行负载静态页)
*
* @param model
* @param msg
*/
@GetMapping("/createHtml")
@ResponseBody
public void createHtml(Model model, @RequestParam("msg") String msg) {
try {
//1.创建上下文对象Context 封装动态数据
Context context = new Context();
context.setVariable("msg", msg);
//2.创建写文件对象
String fileName = "D:\\tmp\\" + msg + ".html";
FileWriter fileWriter = new FileWriter(fileName);
//3.调用模板引擎生成html文件(动态变量赋值,生成文件)
String temlateName = "hello";
templateEngine.process(temlateName, context, fileWriter);
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
在resources目录下新建文件夹:templates
在templates
目录下新建一个html模板文件:hello.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
<h1 th:text="${msg}">你好</h1>
</body>
</html>
启动项目,访问页面:
就像JSP的<%@Page %>一样 ,Thymeleaf的也要引入标签规范。不加这个虽然不影响程序运行,但是你的idea会不识别标签,不方便开发。
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
request.setAttribute("name", "刘德华");
<p th:text="'hello' + ${name}"></p>
List list = Arrays.asList("郑爽", "刘德华", "张惠妹", "成龙");
request.setAttribute("list", list);
<table> <!--s 表示集合中的元素 ${list}表示后台存储的集合 --> <tr th:each="s,stat: ${list}"> <td th:text="${s}"></td> <td th:text="${stat.index}"></td> <td th:text="${stat.count}"></td> <td th:text="${stat.size}"></td> <td th:text="${stat.even}"></td> <td th:text="${stat.odd}"></td> <td th:text="${stat.first}"></td> <td th:text="${stat.last}"></td> </tr> </table>
语法关键字 解释 stat 称作状态变量 index 当前迭代对象的 index(从 0 开始计算) count 当前迭代对象的 index(从 1 开始计算) size 被迭代对象的大小 even/odd 布尔值,当前循环是否是偶数/奇数 first 布尔值,当前循环是否是第一个 last 布尔值,当前循环是否是最后一个 5.2.4 判断
th:if 条件成立显示
th:unless 条件不成立的时候才会显示内容
model.addAttribute("age",18);
<h2>判断 if</h2> <div th:if="${age}>=18" th:text="success">good</div> <a th:unless="${age != 18}" th:text="success" >atguigu</a> <h2>判断 三元</h2> <div th:text="${age}>=18?'success':'failure'"></div>
5.2.5 取session中的属性
httpSession.setAttribute("addr","北京中南海");
<div th:text="${session.addr}"> </div>
5.2.6 引用内嵌页
top.html内容如下
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>hello</title> </head> <body> <html> 我是公共的内容组件 </html>
在hello.html中引入
<div th:include="top"/>
5.2.7 th:utext :解析样式
th:utext:识别html中的标签
request.setAttribute("gname","绿色");
<p th:utext="${gname}">color</p>
5.2.8 点击链接传值
> @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<String, Object> map = new HashMap<>(); > map.put("stuNo","1000"); > map.put("stuName","张三"); > model.addAllAttributes(map); > ```
html
多种方式存储数据
### 5.2.10 字符串替换
request.setAttribute("today", new Date());
¥1,000.00 ``` 替换: 后台代码: request.setAttribute("str","atguigu"); 页面: http://localhost:8080/atguigu ```strings 操作字符串的工具类,还有 @dates ,#numbers 等工具类。
aaa
bbb
```