第4章-商品SKU管理.md 24 KB

第4章-商品SKU管理

学习目标:

  • 熟悉商品SKU相关的数据模型
  • 完成保存商品SKU功能(重难点)
  • 完成商品SKU列表功能
  • 完成商品SKU上下架功能
  • 了解商品详情业务
  • 模板技术Thymeleaf入门

1. 业务介绍

1.1 数据库表结构

根据以上的需求,以此将SKU关联的数据库表结构设计为如下:

img

【金山文档】 08商品SKU-相关表关系分析 https://kdocs.cn/l/cdWifG5v76ls

1.2 数据准备

1.2.1 平台属性添加

img

1.2.2 商品spu管理

img

添加销售属性信息

img

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

代码生成器执行需要填写的表:sku_info,sku_image,sku_attr_value,sku_sale_attr_value

2.2 商品图片列表

image-20221213011431420

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/243

2.2.1. 编写控制器SpuManageController

在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);
}

2.2.2. 业务接口以及实现类

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);
}

2.3 销售属性

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/235

image-20230609145051112

2.3.1. 控制器SpuManageController

/**
 * 根据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);
}

2.3.2. 业务接口与实现类

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>

2.4 保存SKU

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/227

2.4.1. 编写控制器SkuManageController

新建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();
    }
}

2.4.2. 业务接口与实现

创建业务接口: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);
        }
    }
}

3. SKU列表及上下架处理

3.1 SKU列表

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/179

3.1.1 编写控制器

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);
}

3.1.2 业务接口与实现类

在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);
}

3.2 上下架处理

YAPI接口地址:

3.2.1 编写控制器

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();
}

3.2.2 业务接口与实现类

在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异步消息通知搜索服务删除索引库文档
}

4. 商品详情相关业务介绍

  • 商品详情页,简单说就是以购物者的角度展现一个sku的详情信息。
  • 用户点击不同的销售属性值切换不同的商品
  • 点击添加购物车,将商品放入购物车列表中

img

5. 模板技术Thymeleaf介绍

5.1 Thymeleaf 简介

​ Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比, Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用!

类似模板技术

官方网站:https://www.thymeleaf.org/index.html

5.2 快速入门

  1. 新建一个demo模块:thymeleaf-demo,依赖模块web,Thymeleaf.模板。

  1. 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>
    
  2. 启动类

    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);
       }
    }
    
  3. 创建application.yml

    server:
     port: 9999
    spring:
     thymeleaf:
       #关闭Thymeleaf的缓存
       cache: false
    
  4. 不需要做任何配置,启动器已经帮我们把Thymeleaf的视图器配置完成:

    1526435647041

而且,还配置了模板文件(html)的位置,与jsp类似的前缀+ 视图名 + 后缀风格:

![1526435706301](assets/1526435706301.png)
  • 默认前缀:classpath:/templates/
  • 默认后缀:.html

所以如果我们返回视图:users,会指向到 classpath:/templates/users.html

  1. 准备一个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);
           }
       }
    }
    
  2. 在resources目录下新建文件夹:templates

image-20221213105846344

  1. 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>
    
  2. 启动项目,访问页面:

image-20221213110053156

5.2.1 设置头文件

就像JSP的<%@Page %>一样 ,Thymeleaf的也要引入标签规范。不加这个虽然不影响程序运行,但是你的idea会不识别标签,不方便开发。

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

5.2.2 赋值字符串拼接

request.setAttribute("name", "刘德华");

<p th:text="'hello' + ${name}"></p>

5.2.3 循环

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());


strings 操作字符串的工具类,还有 @dates ,#numbers 等工具类。

¥1,000.00 ``` 替换: 后台代码: request.setAttribute("str","atguigu"); 页面: http://localhost:8080/atguigu ```

aaa

bbb

```