Browse Source

spu和sku 自动代码生成

franky 3 years ago
parent
commit
279127298b
29 changed files with 1782 additions and 0 deletions
  1. 6 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
  2. 100 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/SkuController.java
  3. 46 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuBaseVO.java
  4. 14 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuCreateReqVO.java
  5. 47 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuExcelVO.java
  6. 47 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuExportReqVO.java
  7. 49 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuPageReqVO.java
  8. 19 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuRespVO.java
  9. 18 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuUpdateReqVO.java
  10. 100 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/SpuController.java
  11. 53 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuBaseVO.java
  12. 14 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuCreateReqVO.java
  13. 56 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuExcelVO.java
  14. 56 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuExportReqVO.java
  15. 58 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuPageReqVO.java
  16. 19 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuRespVO.java
  17. 18 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuUpdateReqVO.java
  18. 34 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/SkuConvert.java
  19. 34 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/SpuConvert.java
  20. 61 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/SkuDO.java
  21. 73 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/SpuDO.java
  22. 48 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/SkuMapper.java
  23. 54 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/SpuMapper.java
  24. 70 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/SkuService.java
  25. 82 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImpl.java
  26. 70 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/SpuService.java
  27. 82 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/SpuServiceImpl.java
  28. 215 0
      yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImplTest.java
  29. 239 0
      yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/SpuServiceImplTest.java

+ 6 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java

@@ -22,4 +22,10 @@ public interface ErrorCodeConstants {
 
     // ========== 规格值 1008004000 ==========
     ErrorCode ATTR_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "规格值不存在");
+
+    // ========== 商品spu 1008005000 ==========
+    ErrorCode SPU_NOT_EXISTS = new ErrorCode(1008005000, "商品spu不存在");
+
+    // ========== 商品sku 1008006000 ==========
+    ErrorCode SKU_NOT_EXISTS = new ErrorCode(1008006000, "商品sku不存在");
 }

+ 100 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/SkuController.java

@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.annotations.*;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.SkuDO;
+import cn.iocoder.yudao.module.product.convert.sku.SkuConvert;
+import cn.iocoder.yudao.module.product.service.sku.SkuService;
+
+@Api(tags = "管理后台 - 商品sku")
+@RestController
+@RequestMapping("/product/sku")
+@Validated
+public class SkuController {
+
+    @Resource
+    private SkuService skuService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建商品sku")
+    @PreAuthorize("@ss.hasPermission('product:sku:create')")
+    public CommonResult<Integer> createSku(@Valid @RequestBody SkuCreateReqVO createReqVO) {
+        return success(skuService.createSku(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新商品sku")
+    @PreAuthorize("@ss.hasPermission('product:sku:update')")
+    public CommonResult<Boolean> updateSku(@Valid @RequestBody SkuUpdateReqVO updateReqVO) {
+        skuService.updateSku(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除商品sku")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Integer.class)
+    @PreAuthorize("@ss.hasPermission('product:sku:delete')")
+    public CommonResult<Boolean> deleteSku(@RequestParam("id") Integer id) {
+        skuService.deleteSku(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得商品sku")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Integer.class)
+    @PreAuthorize("@ss.hasPermission('product:sku:query')")
+    public CommonResult<SkuRespVO> getSku(@RequestParam("id") Integer id) {
+        SkuDO sku = skuService.getSku(id);
+        return success(SkuConvert.INSTANCE.convert(sku));
+    }
+
+    @GetMapping("/list")
+    @ApiOperation("获得商品sku列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+    @PreAuthorize("@ss.hasPermission('product:sku:query')")
+    public CommonResult<List<SkuRespVO>> getSkuList(@RequestParam("ids") Collection<Integer> ids) {
+        List<SkuDO> list = skuService.getSkuList(ids);
+        return success(SkuConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得商品sku分页")
+    @PreAuthorize("@ss.hasPermission('product:sku:query')")
+    public CommonResult<PageResult<SkuRespVO>> getSkuPage(@Valid SkuPageReqVO pageVO) {
+        PageResult<SkuDO> pageResult = skuService.getSkuPage(pageVO);
+        return success(SkuConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出商品sku Excel")
+    @PreAuthorize("@ss.hasPermission('product:sku:export')")
+    @OperateLog(type = EXPORT)
+    public void exportSkuExcel(@Valid SkuExportReqVO exportReqVO,
+              HttpServletResponse response) throws IOException {
+        List<SkuDO> list = skuService.getSkuList(exportReqVO);
+        // 导出 Excel
+        List<SkuExcelVO> datas = SkuConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "商品sku.xls", "数据", SkuExcelVO.class, datas);
+    }
+
+}

+ 46 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuBaseVO.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+/**
+* 商品sku Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class SkuBaseVO {
+
+    @ApiModelProperty(value = "spu编号", required = true)
+    @NotNull(message = "spu编号不能为空")
+    private Integer spuId;
+
+    @ApiModelProperty(value = "状态: 1-正常 2-禁用")
+    private Integer skuStatus;
+
+    @ApiModelProperty(value = "规格值数组, 以逗号隔开", required = true)
+    @NotNull(message = "规格值数组, 以逗号隔开不能为空")
+    private String attrs;
+
+    @ApiModelProperty(value = "销售价格,单位:分", required = true)
+    @NotNull(message = "销售价格,单位:分不能为空")
+    private Integer price;
+
+    @ApiModelProperty(value = "原价, 单位: 分", required = true)
+    @NotNull(message = "原价, 单位: 分不能为空")
+    private Integer originalPrice;
+
+    @ApiModelProperty(value = "成本价,单位: 分", required = true)
+    @NotNull(message = "成本价,单位: 分不能为空")
+    private Integer costPrice;
+
+    @ApiModelProperty(value = "条形码", required = true)
+    @NotNull(message = "条形码不能为空")
+    private String barCode;
+
+    @ApiModelProperty(value = "图片地址", required = true)
+    @NotNull(message = "图片地址不能为空")
+    private String picUrl;
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 商品sku创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SkuCreateReqVO extends SkuBaseVO {
+
+}

+ 47 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuExcelVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+/**
+ * 商品sku Excel VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class SkuExcelVO {
+
+    @ExcelProperty("主键")
+    private Integer id;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+    @ExcelProperty("spu编号")
+    private Integer spuId;
+
+    @ExcelProperty("状态: 1-正常 2-禁用")
+    private Integer skuStatus;
+
+    @ExcelProperty("规格值数组, 以逗号隔开")
+    private String attrs;
+
+    @ExcelProperty("销售价格,单位:分")
+    private Integer price;
+
+    @ExcelProperty("原价, 单位: 分")
+    private Integer originalPrice;
+
+    @ExcelProperty("成本价,单位: 分")
+    private Integer costPrice;
+
+    @ExcelProperty("条形码")
+    private String barCode;
+
+    @ExcelProperty("图片地址")
+    private String picUrl;
+
+}

+ 47 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuExportReqVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "管理后台 - 商品sku Excel 导出 Request VO", description = "参数和 SkuPageReqVO 是一致的")
+@Data
+public class SkuExportReqVO {
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+    @ApiModelProperty(value = "spu编号")
+    private Integer spuId;
+
+    @ApiModelProperty(value = "状态: 1-正常 2-禁用")
+    private Integer skuStatus;
+
+    @ApiModelProperty(value = "规格值数组, 以逗号隔开")
+    private String attrs;
+
+    @ApiModelProperty(value = "销售价格,单位:分")
+    private Integer price;
+
+    @ApiModelProperty(value = "原价, 单位: 分")
+    private Integer originalPrice;
+
+    @ApiModelProperty(value = "成本价,单位: 分")
+    private Integer costPrice;
+
+    @ApiModelProperty(value = "条形码")
+    private String barCode;
+
+    @ApiModelProperty(value = "图片地址")
+    private String picUrl;
+
+}

+ 49 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuPageReqVO.java

@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 商品sku分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SkuPageReqVO extends PageParam {
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+    @ApiModelProperty(value = "spu编号")
+    private Integer spuId;
+
+    @ApiModelProperty(value = "状态: 1-正常 2-禁用")
+    private Integer skuStatus;
+
+    @ApiModelProperty(value = "规格值数组, 以逗号隔开")
+    private String attrs;
+
+    @ApiModelProperty(value = "销售价格,单位:分")
+    private Integer price;
+
+    @ApiModelProperty(value = "原价, 单位: 分")
+    private Integer originalPrice;
+
+    @ApiModelProperty(value = "成本价,单位: 分")
+    private Integer costPrice;
+
+    @ApiModelProperty(value = "条形码")
+    private String barCode;
+
+    @ApiModelProperty(value = "图片地址")
+    private String picUrl;
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 商品sku Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SkuRespVO extends SkuBaseVO {
+
+    @ApiModelProperty(value = "主键", required = true)
+    private Integer id;
+
+    @ApiModelProperty(value = "创建时间")
+    private Date createTime;
+
+}

+ 18 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/SkuUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 商品sku更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SkuUpdateReqVO extends SkuBaseVO {
+
+    @ApiModelProperty(value = "主键", required = true)
+    @NotNull(message = "主键不能为空")
+    private Integer id;
+
+}

+ 100 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/SpuController.java

@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.annotations.*;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.SpuDO;
+import cn.iocoder.yudao.module.product.convert.spu.SpuConvert;
+import cn.iocoder.yudao.module.product.service.spu.SpuService;
+
+@Api(tags = "管理后台 - 商品spu")
+@RestController
+@RequestMapping("/product/spu")
+@Validated
+public class SpuController {
+
+    @Resource
+    private SpuService spuService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建商品spu")
+    @PreAuthorize("@ss.hasPermission('product:spu:create')")
+    public CommonResult<Integer> createSpu(@Valid @RequestBody SpuCreateReqVO createReqVO) {
+        return success(spuService.createSpu(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新商品spu")
+    @PreAuthorize("@ss.hasPermission('product:spu:update')")
+    public CommonResult<Boolean> updateSpu(@Valid @RequestBody SpuUpdateReqVO updateReqVO) {
+        spuService.updateSpu(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除商品spu")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Integer.class)
+    @PreAuthorize("@ss.hasPermission('product:spu:delete')")
+    public CommonResult<Boolean> deleteSpu(@RequestParam("id") Integer id) {
+        spuService.deleteSpu(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得商品spu")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Integer.class)
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
+    public CommonResult<SpuRespVO> getSpu(@RequestParam("id") Integer id) {
+        SpuDO spu = spuService.getSpu(id);
+        return success(SpuConvert.INSTANCE.convert(spu));
+    }
+
+    @GetMapping("/list")
+    @ApiOperation("获得商品spu列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
+    public CommonResult<List<SpuRespVO>> getSpuList(@RequestParam("ids") Collection<Integer> ids) {
+        List<SpuDO> list = spuService.getSpuList(ids);
+        return success(SpuConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得商品spu分页")
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
+    public CommonResult<PageResult<SpuRespVO>> getSpuPage(@Valid SpuPageReqVO pageVO) {
+        PageResult<SpuDO> pageResult = spuService.getSpuPage(pageVO);
+        return success(SpuConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出商品spu Excel")
+    @PreAuthorize("@ss.hasPermission('product:spu:export')")
+    @OperateLog(type = EXPORT)
+    public void exportSpuExcel(@Valid SpuExportReqVO exportReqVO,
+              HttpServletResponse response) throws IOException {
+        List<SpuDO> list = spuService.getSpuList(exportReqVO);
+        // 导出 Excel
+        List<SpuExcelVO> datas = SpuConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "商品spu.xls", "数据", SpuExcelVO.class, datas);
+    }
+
+}

+ 53 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuBaseVO.java

@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+/**
+* 商品spu Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class SpuBaseVO {
+
+    @ApiModelProperty(value = "商品名称")
+    private String name;
+
+    @ApiModelProperty(value = "上下架状态: true 上架,false 下架")
+    private Boolean visible;
+
+    @ApiModelProperty(value = "卖点", required = true)
+    @NotNull(message = "卖点不能为空")
+    private String sellPoint;
+
+    @ApiModelProperty(value = "描述", required = true)
+    @NotNull(message = "描述不能为空")
+    private String description;
+
+    @ApiModelProperty(value = "分类id", required = true)
+    @NotNull(message = "分类id不能为空")
+    private Integer cid;
+
+    @ApiModelProperty(value = "列表图")
+    private String listPicUrl;
+
+    @ApiModelProperty(value = "商品主图地址, 数组,以逗号分隔, 最多上传15张", required = true)
+    @NotNull(message = "商品主图地址, 数组,以逗号分隔, 最多上传15张不能为空")
+    private String picUrls;
+
+    @ApiModelProperty(value = "排序字段", required = true)
+    @NotNull(message = "排序字段不能为空")
+    private Integer sort;
+
+    @ApiModelProperty(value = "点赞初始人数")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "价格")
+    private Integer price;
+
+    @ApiModelProperty(value = "库存数量")
+    private Integer quantity;
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 商品spu创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SpuCreateReqVO extends SpuBaseVO {
+
+}

+ 56 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuExcelVO.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+/**
+ * 商品spu Excel VO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class SpuExcelVO {
+
+    @ExcelProperty("主键")
+    private Integer id;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+    @ExcelProperty("商品名称")
+    private String name;
+
+    @ExcelProperty("上下架状态: true 上架,false 下架")
+    private Boolean visible;
+
+    @ExcelProperty("卖点")
+    private String sellPoint;
+
+    @ExcelProperty("描述")
+    private String description;
+
+    @ExcelProperty("分类id")
+    private Integer cid;
+
+    @ExcelProperty("列表图")
+    private String listPicUrl;
+
+    @ExcelProperty("商品主图地址, 数组,以逗号分隔, 最多上传15张")
+    private String picUrls;
+
+    @ExcelProperty("排序字段")
+    private Integer sort;
+
+    @ExcelProperty("点赞初始人数")
+    private Integer likeCount;
+
+    @ExcelProperty("价格")
+    private Integer price;
+
+    @ExcelProperty("库存数量")
+    private Integer quantity;
+
+}

+ 56 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuExportReqVO.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "管理后台 - 商品spu Excel 导出 Request VO", description = "参数和 SpuPageReqVO 是一致的")
+@Data
+public class SpuExportReqVO {
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+    @ApiModelProperty(value = "商品名称")
+    private String name;
+
+    @ApiModelProperty(value = "上下架状态: true 上架,false 下架")
+    private Boolean visible;
+
+    @ApiModelProperty(value = "卖点")
+    private String sellPoint;
+
+    @ApiModelProperty(value = "描述")
+    private String description;
+
+    @ApiModelProperty(value = "分类id")
+    private Integer cid;
+
+    @ApiModelProperty(value = "列表图")
+    private String listPicUrl;
+
+    @ApiModelProperty(value = "商品主图地址, 数组,以逗号分隔, 最多上传15张")
+    private String picUrls;
+
+    @ApiModelProperty(value = "排序字段")
+    private Integer sort;
+
+    @ApiModelProperty(value = "点赞初始人数")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "价格")
+    private Integer price;
+
+    @ApiModelProperty(value = "库存数量")
+    private Integer quantity;
+
+}

+ 58 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuPageReqVO.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 商品spu分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SpuPageReqVO extends PageParam {
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+    @ApiModelProperty(value = "商品名称")
+    private String name;
+
+    @ApiModelProperty(value = "上下架状态: true 上架,false 下架")
+    private Boolean visible;
+
+    @ApiModelProperty(value = "卖点")
+    private String sellPoint;
+
+    @ApiModelProperty(value = "描述")
+    private String description;
+
+    @ApiModelProperty(value = "分类id")
+    private Integer cid;
+
+    @ApiModelProperty(value = "列表图")
+    private String listPicUrl;
+
+    @ApiModelProperty(value = "商品主图地址, 数组,以逗号分隔, 最多上传15张")
+    private String picUrls;
+
+    @ApiModelProperty(value = "排序字段")
+    private Integer sort;
+
+    @ApiModelProperty(value = "点赞初始人数")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "价格")
+    private Integer price;
+
+    @ApiModelProperty(value = "库存数量")
+    private Integer quantity;
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 商品spu Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SpuRespVO extends SpuBaseVO {
+
+    @ApiModelProperty(value = "主键", required = true)
+    private Integer id;
+
+    @ApiModelProperty(value = "创建时间")
+    private Date createTime;
+
+}

+ 18 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 商品spu更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class SpuUpdateReqVO extends SpuBaseVO {
+
+    @ApiModelProperty(value = "主键", required = true)
+    @NotNull(message = "主键不能为空")
+    private Integer id;
+
+}

+ 34 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/SkuConvert.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.product.convert.sku;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.SkuDO;
+
+/**
+ * 商品sku Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface SkuConvert {
+
+    SkuConvert INSTANCE = Mappers.getMapper(SkuConvert.class);
+
+    SkuDO convert(SkuCreateReqVO bean);
+
+    SkuDO convert(SkuUpdateReqVO bean);
+
+    SkuRespVO convert(SkuDO bean);
+
+    List<SkuRespVO> convertList(List<SkuDO> list);
+
+    PageResult<SkuRespVO> convertPage(PageResult<SkuDO> page);
+
+    List<SkuExcelVO> convertList02(List<SkuDO> list);
+
+}

+ 34 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/SpuConvert.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.product.convert.spu;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.SpuDO;
+
+/**
+ * 商品spu Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface SpuConvert {
+
+    SpuConvert INSTANCE = Mappers.getMapper(SpuConvert.class);
+
+    SpuDO convert(SpuCreateReqVO bean);
+
+    SpuDO convert(SpuUpdateReqVO bean);
+
+    SpuRespVO convert(SpuDO bean);
+
+    List<SpuRespVO> convertList(List<SpuDO> list);
+
+    PageResult<SpuRespVO> convertPage(PageResult<SpuDO> page);
+
+    List<SpuExcelVO> convertList02(List<SpuDO> list);
+
+}

+ 61 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/SkuDO.java

@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.sku;
+
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 商品sku DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_sku")
+@KeySequence("product_sku_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SkuDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Integer id;
+    /**
+     * spu编号
+     */
+    private Integer spuId;
+    /**
+     * 状态: 1-正常 2-禁用
+     */
+    private Integer skuStatus;
+    /**
+     * 规格值数组, 以逗号隔开
+     */
+    private String attrs;
+    /**
+     * 销售价格,单位:分
+     */
+    private Integer price;
+    /**
+     * 原价, 单位: 分
+     */
+    private Integer originalPrice;
+    /**
+     * 成本价,单位: 分
+     */
+    private Integer costPrice;
+    /**
+     * 条形码
+     */
+    private String barCode;
+    /**
+     * 图片地址
+     */
+    private String picUrl;
+
+}

+ 73 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/SpuDO.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.spu;
+
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 商品spu DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_spu")
+@KeySequence("product_spu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SpuDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Integer id;
+    /**
+     * 商品名称
+     */
+    private String name;
+    /**
+     * 上下架状态: true 上架,false 下架
+     */
+    private Boolean visible;
+    /**
+     * 卖点
+     */
+    private String sellPoint;
+    /**
+     * 描述
+     */
+    private String description;
+    /**
+     * 分类id
+     */
+    private Integer cid;
+    /**
+     * 列表图
+     */
+    private String listPicUrl;
+    /**
+     * 商品主图地址, 数组,以逗号分隔, 最多上传15张
+     */
+    private String picUrls;
+    /**
+     * 排序字段
+     */
+    private Integer sort;
+    /**
+     * 点赞初始人数
+     */
+    private Integer likeCount;
+    /**
+     * 价格
+     */
+    private Integer price;
+    /**
+     * 库存数量
+     */
+    private Integer quantity;
+
+}

+ 48 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/SkuMapper.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.product.dal.mysql.sku;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.SkuDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.*;
+
+/**
+ * 商品sku Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface SkuMapper extends BaseMapperX<SkuDO> {
+
+    default PageResult<SkuDO> selectPage(SkuPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<SkuDO>()
+                .betweenIfPresent(SkuDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .eqIfPresent(SkuDO::getSpuId, reqVO.getSpuId())
+                .eqIfPresent(SkuDO::getSkuStatus, reqVO.getSkuStatus())
+                .eqIfPresent(SkuDO::getAttrs, reqVO.getAttrs())
+                .eqIfPresent(SkuDO::getPrice, reqVO.getPrice())
+                .eqIfPresent(SkuDO::getOriginalPrice, reqVO.getOriginalPrice())
+                .eqIfPresent(SkuDO::getCostPrice, reqVO.getCostPrice())
+                .eqIfPresent(SkuDO::getBarCode, reqVO.getBarCode())
+                .eqIfPresent(SkuDO::getPicUrl, reqVO.getPicUrl())
+                .orderByDesc(SkuDO::getId));
+    }
+
+    default List<SkuDO> selectList(SkuExportReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<SkuDO>()
+                .betweenIfPresent(SkuDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .eqIfPresent(SkuDO::getSpuId, reqVO.getSpuId())
+                .eqIfPresent(SkuDO::getSkuStatus, reqVO.getSkuStatus())
+                .eqIfPresent(SkuDO::getAttrs, reqVO.getAttrs())
+                .eqIfPresent(SkuDO::getPrice, reqVO.getPrice())
+                .eqIfPresent(SkuDO::getOriginalPrice, reqVO.getOriginalPrice())
+                .eqIfPresent(SkuDO::getCostPrice, reqVO.getCostPrice())
+                .eqIfPresent(SkuDO::getBarCode, reqVO.getBarCode())
+                .eqIfPresent(SkuDO::getPicUrl, reqVO.getPicUrl())
+                .orderByDesc(SkuDO::getId));
+    }
+
+}

+ 54 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/SpuMapper.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.product.dal.mysql.spu;
+
+import java.util.*;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.SpuDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+
+/**
+ * 商品spu Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface SpuMapper extends BaseMapperX<SpuDO> {
+
+    default PageResult<SpuDO> selectPage(SpuPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<SpuDO>()
+                .betweenIfPresent(SpuDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .likeIfPresent(SpuDO::getName, reqVO.getName())
+                .eqIfPresent(SpuDO::getVisible, reqVO.getVisible())
+                .eqIfPresent(SpuDO::getSellPoint, reqVO.getSellPoint())
+                .eqIfPresent(SpuDO::getDescription, reqVO.getDescription())
+                .eqIfPresent(SpuDO::getCid, reqVO.getCid())
+                .eqIfPresent(SpuDO::getListPicUrl, reqVO.getListPicUrl())
+                .eqIfPresent(SpuDO::getPicUrls, reqVO.getPicUrls())
+                .eqIfPresent(SpuDO::getSort, reqVO.getSort())
+                .eqIfPresent(SpuDO::getLikeCount, reqVO.getLikeCount())
+                .eqIfPresent(SpuDO::getPrice, reqVO.getPrice())
+                .eqIfPresent(SpuDO::getQuantity, reqVO.getQuantity())
+                .orderByDesc(SpuDO::getId));
+    }
+
+    default List<SpuDO> selectList(SpuExportReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<SpuDO>()
+                .betweenIfPresent(SpuDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .likeIfPresent(SpuDO::getName, reqVO.getName())
+                .eqIfPresent(SpuDO::getVisible, reqVO.getVisible())
+                .eqIfPresent(SpuDO::getSellPoint, reqVO.getSellPoint())
+                .eqIfPresent(SpuDO::getDescription, reqVO.getDescription())
+                .eqIfPresent(SpuDO::getCid, reqVO.getCid())
+                .eqIfPresent(SpuDO::getListPicUrl, reqVO.getListPicUrl())
+                .eqIfPresent(SpuDO::getPicUrls, reqVO.getPicUrls())
+                .eqIfPresent(SpuDO::getSort, reqVO.getSort())
+                .eqIfPresent(SpuDO::getLikeCount, reqVO.getLikeCount())
+                .eqIfPresent(SpuDO::getPrice, reqVO.getPrice())
+                .eqIfPresent(SpuDO::getQuantity, reqVO.getQuantity())
+                .orderByDesc(SpuDO::getId));
+    }
+
+}

+ 70 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/SkuService.java

@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.product.service.sku;
+
+import java.util.*;
+import javax.validation.*;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.SkuDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+/**
+ * 商品sku Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface SkuService {
+
+    /**
+     * 创建商品sku
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Integer createSku(@Valid SkuCreateReqVO createReqVO);
+
+    /**
+     * 更新商品sku
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateSku(@Valid SkuUpdateReqVO updateReqVO);
+
+    /**
+     * 删除商品sku
+     *
+     * @param id 编号
+     */
+    void deleteSku(Integer id);
+
+    /**
+     * 获得商品sku
+     *
+     * @param id 编号
+     * @return 商品sku
+     */
+    SkuDO getSku(Integer id);
+
+    /**
+     * 获得商品sku列表
+     *
+     * @param ids 编号
+     * @return 商品sku列表
+     */
+    List<SkuDO> getSkuList(Collection<Integer> ids);
+
+    /**
+     * 获得商品sku分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 商品sku分页
+     */
+    PageResult<SkuDO> getSkuPage(SkuPageReqVO pageReqVO);
+
+    /**
+     * 获得商品sku列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 商品sku列表
+     */
+    List<SkuDO> getSkuList(SkuExportReqVO exportReqVO);
+
+}

+ 82 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImpl.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.product.service.sku;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.SkuDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import cn.iocoder.yudao.module.product.convert.sku.SkuConvert;
+import cn.iocoder.yudao.module.product.dal.mysql.sku.SkuMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
+
+/**
+ * 商品sku Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class SkuServiceImpl implements SkuService {
+
+    @Resource
+    private SkuMapper skuMapper;
+
+    @Override
+    public Integer createSku(SkuCreateReqVO createReqVO) {
+        // 插入
+        SkuDO sku = SkuConvert.INSTANCE.convert(createReqVO);
+        skuMapper.insert(sku);
+        // 返回
+        return sku.getId();
+    }
+
+    @Override
+    public void updateSku(SkuUpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validateSkuExists(updateReqVO.getId());
+        // 更新
+        SkuDO updateObj = SkuConvert.INSTANCE.convert(updateReqVO);
+        skuMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteSku(Integer id) {
+        // 校验存在
+        this.validateSkuExists(id);
+        // 删除
+        skuMapper.deleteById(id);
+    }
+
+    private void validateSkuExists(Integer id) {
+        if (skuMapper.selectById(id) == null) {
+            throw exception(SKU_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public SkuDO getSku(Integer id) {
+        return skuMapper.selectById(id);
+    }
+
+    @Override
+    public List<SkuDO> getSkuList(Collection<Integer> ids) {
+        return skuMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<SkuDO> getSkuPage(SkuPageReqVO pageReqVO) {
+        return skuMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<SkuDO> getSkuList(SkuExportReqVO exportReqVO) {
+        return skuMapper.selectList(exportReqVO);
+    }
+
+}

+ 70 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/SpuService.java

@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.product.service.spu;
+
+import java.util.*;
+import javax.validation.*;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.SpuDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+/**
+ * 商品spu Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface SpuService {
+
+    /**
+     * 创建商品spu
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Integer createSpu(@Valid SpuCreateReqVO createReqVO);
+
+    /**
+     * 更新商品spu
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateSpu(@Valid SpuUpdateReqVO updateReqVO);
+
+    /**
+     * 删除商品spu
+     *
+     * @param id 编号
+     */
+    void deleteSpu(Integer id);
+
+    /**
+     * 获得商品spu
+     *
+     * @param id 编号
+     * @return 商品spu
+     */
+    SpuDO getSpu(Integer id);
+
+    /**
+     * 获得商品spu列表
+     *
+     * @param ids 编号
+     * @return 商品spu列表
+     */
+    List<SpuDO> getSpuList(Collection<Integer> ids);
+
+    /**
+     * 获得商品spu分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 商品spu分页
+     */
+    PageResult<SpuDO> getSpuPage(SpuPageReqVO pageReqVO);
+
+    /**
+     * 获得商品spu列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 商品spu列表
+     */
+    List<SpuDO> getSpuList(SpuExportReqVO exportReqVO);
+
+}

+ 82 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/SpuServiceImpl.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.product.service.spu;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.*;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.SpuDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import cn.iocoder.yudao.module.product.convert.spu.SpuConvert;
+import cn.iocoder.yudao.module.product.dal.mysql.spu.SpuMapper;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
+
+/**
+ * 商品spu Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class SpuServiceImpl implements SpuService {
+
+    @Resource
+    private SpuMapper spuMapper;
+
+    @Override
+    public Integer createSpu(SpuCreateReqVO createReqVO) {
+        // 插入
+        SpuDO spu = SpuConvert.INSTANCE.convert(createReqVO);
+        spuMapper.insert(spu);
+        // 返回
+        return spu.getId();
+    }
+
+    @Override
+    public void updateSpu(SpuUpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validateSpuExists(updateReqVO.getId());
+        // 更新
+        SpuDO updateObj = SpuConvert.INSTANCE.convert(updateReqVO);
+        spuMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteSpu(Integer id) {
+        // 校验存在
+        this.validateSpuExists(id);
+        // 删除
+        spuMapper.deleteById(id);
+    }
+
+    private void validateSpuExists(Integer id) {
+        if (spuMapper.selectById(id) == null) {
+            throw exception(SPU_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public SpuDO getSpu(Integer id) {
+        return spuMapper.selectById(id);
+    }
+
+    @Override
+    public List<SpuDO> getSpuList(Collection<Integer> ids) {
+        return spuMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<SpuDO> getSpuPage(SpuPageReqVO pageReqVO) {
+        return spuMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<SpuDO> getSpuList(SpuExportReqVO exportReqVO) {
+        return spuMapper.selectList(exportReqVO);
+    }
+
+}

+ 215 - 0
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImplTest.java

@@ -0,0 +1,215 @@
+package cn.iocoder.yudao.module.product.service.sku;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.SkuDO;
+import cn.iocoder.yudao.module.product.dal.mysql.sku.SkuMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+* {@link SkuServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(SkuServiceImpl.class)
+public class SkuServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private SkuServiceImpl skuService;
+
+    @Resource
+    private SkuMapper skuMapper;
+
+    @Test
+    public void testCreateSku_success() {
+        // 准备参数
+        SkuCreateReqVO reqVO = randomPojo(SkuCreateReqVO.class);
+
+        // 调用
+        Integer skuId = skuService.createSku(reqVO);
+        // 断言
+        assertNotNull(skuId);
+        // 校验记录的属性是否正确
+        SkuDO sku = skuMapper.selectById(skuId);
+        assertPojoEquals(reqVO, sku);
+    }
+
+    @Test
+    public void testUpdateSku_success() {
+        // mock 数据
+        SkuDO dbSku = randomPojo(SkuDO.class);
+        skuMapper.insert(dbSku);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        SkuUpdateReqVO reqVO = randomPojo(SkuUpdateReqVO.class, o -> {
+            o.setId(dbSku.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        skuService.updateSku(reqVO);
+        // 校验是否更新正确
+        SkuDO sku = skuMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, sku);
+    }
+
+    @Test
+    public void testUpdateSku_notExists() {
+        // 准备参数
+        SkuUpdateReqVO reqVO = randomPojo(SkuUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> skuService.updateSku(reqVO), SKU_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteSku_success() {
+        // mock 数据
+        SkuDO dbSku = randomPojo(SkuDO.class);
+        skuMapper.insert(dbSku);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Integer id = dbSku.getId();
+
+        // 调用
+        skuService.deleteSku(id);
+       // 校验数据不存在了
+       assertNull(skuMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteSku_notExists() {
+        // 准备参数
+        Integer id = 1;
+
+        // 调用, 并断言异常spu
+        assertServiceException(() -> skuService.deleteSku(id), SKU_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSkuPage() {
+       // mock 数据
+       SkuDO dbSku = randomPojo(SkuDO.class, o -> { // 等会查询到
+           o.setCreateTime(null);
+           o.setSpuId(null);
+           o.setSkuStatus(null);
+           o.setAttrs(null);
+           o.setPrice(null);
+           o.setOriginalPrice(null);
+           o.setCostPrice(null);
+           o.setBarCode(null);
+           o.setPicUrl(null);
+       });
+       skuMapper.insert(dbSku);
+       // 测试 createTime 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setCreateTime(null)));
+       // 测试 spuId 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setSpuId(null)));
+       // 测试 skuStatus 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setSkuStatus(null)));
+       // 测试 attrs 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setAttrs(null)));
+       // 测试 price 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setPrice(null)));
+       // 测试 originalPrice 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setOriginalPrice(null)));
+       // 测试 costPrice 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setCostPrice(null)));
+       // 测试 barCode 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setBarCode(null)));
+       // 测试 picUrl 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setPicUrl(null)));
+       // 准备参数
+       SkuPageReqVO reqVO = new SkuPageReqVO();
+       reqVO.setBeginCreateTime(null);
+       reqVO.setEndCreateTime(null);
+       reqVO.setSpuId(null);
+       reqVO.setSkuStatus(null);
+       reqVO.setAttrs(null);
+       reqVO.setPrice(null);
+       reqVO.setOriginalPrice(null);
+       reqVO.setCostPrice(null);
+       reqVO.setBarCode(null);
+       reqVO.setPicUrl(null);
+
+       // 调用
+       PageResult<SkuDO> pageResult = skuService.getSkuPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbSku, pageResult.getList().get(0));
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSkuList() {
+       // mock 数据
+       SkuDO dbSku = randomPojo(SkuDO.class, o -> { // 等会查询到
+           o.setCreateTime(null);
+           o.setSpuId(null);
+           o.setSkuStatus(null);
+           o.setAttrs(null);
+           o.setPrice(null);
+           o.setOriginalPrice(null);
+           o.setCostPrice(null);
+           o.setBarCode(null);
+           o.setPicUrl(null);
+       });
+       skuMapper.insert(dbSku);
+       // 测试 createTime 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setCreateTime(null)));
+       // 测试 spuId 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setSpuId(null)));
+       // 测试 skuStatus 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setSkuStatus(null)));
+       // 测试 attrs 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setAttrs(null)));
+       // 测试 price 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setPrice(null)));
+       // 测试 originalPrice 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setOriginalPrice(null)));
+       // 测试 costPrice 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setCostPrice(null)));
+       // 测试 barCode 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setBarCode(null)));
+       // 测试 picUrl 不匹配
+       skuMapper.insert(cloneIgnoreId(dbSku, o -> o.setPicUrl(null)));
+       // 准备参数
+       SkuExportReqVO reqVO = new SkuExportReqVO();
+       reqVO.setBeginCreateTime(null);
+       reqVO.setEndCreateTime(null);
+       reqVO.setSpuId(null);
+       reqVO.setSkuStatus(null);
+       reqVO.setAttrs(null);
+       reqVO.setPrice(null);
+       reqVO.setOriginalPrice(null);
+       reqVO.setCostPrice(null);
+       reqVO.setBarCode(null);
+       reqVO.setPicUrl(null);
+
+       // 调用
+       List<SkuDO> list = skuService.getSkuList(reqVO);
+       // 断言
+       assertEquals(1, list.size());
+       assertPojoEquals(dbSku, list.get(0));
+    }
+
+}

+ 239 - 0
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/SpuServiceImplTest.java

@@ -0,0 +1,239 @@
+package cn.iocoder.yudao.module.product.service.spu;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.SpuDO;
+import cn.iocoder.yudao.module.product.dal.mysql.spu.SpuMapper;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+* {@link SpuServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(SpuServiceImpl.class)
+public class SpuServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private SpuServiceImpl spuService;
+
+    @Resource
+    private SpuMapper spuMapper;
+
+    @Test
+    public void testCreateSpu_success() {
+        // 准备参数
+        SpuCreateReqVO reqVO = randomPojo(SpuCreateReqVO.class);
+
+        // 调用
+        Integer spuId = spuService.createSpu(reqVO);
+        // 断言
+        assertNotNull(spuId);
+        // 校验记录的属性是否正确
+        SpuDO spu = spuMapper.selectById(spuId);
+        assertPojoEquals(reqVO, spu);
+    }
+
+    @Test
+    public void testUpdateSpu_success() {
+        // mock 数据
+        SpuDO dbSpu = randomPojo(SpuDO.class);
+        spuMapper.insert(dbSpu);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        SpuUpdateReqVO reqVO = randomPojo(SpuUpdateReqVO.class, o -> {
+            o.setId(dbSpu.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        spuService.updateSpu(reqVO);
+        // 校验是否更新正确
+        SpuDO spu = spuMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, spu);
+    }
+
+    @Test
+    public void testUpdateSpu_notExists() {
+        // 准备参数
+        SpuUpdateReqVO reqVO = randomPojo(SpuUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> spuService.updateSpu(reqVO), SPU_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteSpu_success() {
+        // mock 数据
+        SpuDO dbSpu = randomPojo(SpuDO.class);
+        spuMapper.insert(dbSpu);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Integer id = dbSpu.getId();
+
+        // 调用
+        spuService.deleteSpu(id);
+       // 校验数据不存在了
+       assertNull(spuMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteSpu_notExists() {
+        // 准备参数
+        Integer id = 1;
+
+        // 调用, 并断言异常
+        assertServiceException(() -> spuService.deleteSpu(id), SPU_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSpuPage() {
+       // mock 数据
+       SpuDO dbSpu = randomPojo(SpuDO.class, o -> { // 等会查询到
+           o.setCreateTime(null);
+           o.setName(null);
+           o.setVisible(null);
+           o.setSellPoint(null);
+           o.setDescription(null);
+           o.setCid(null);
+           o.setListPicUrl(null);
+           o.setPicUrls(null);
+           o.setSort(null);
+           o.setLikeCount(null);
+           o.setPrice(null);
+           o.setQuantity(null);
+       });
+       spuMapper.insert(dbSpu);
+       // 测试 createTime 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setCreateTime(null)));
+       // 测试 name 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setName(null)));
+       // 测试 visible 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setVisible(null)));
+       // 测试 sellPoint 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setSellPoint(null)));
+       // 测试 description 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setDescription(null)));
+       // 测试 cid 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setCid(null)));
+       // 测试 listPicUrl 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setListPicUrl(null)));
+       // 测试 picUrls 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setPicUrls(null)));
+       // 测试 sort 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setSort(null)));
+       // 测试 likeCount 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setLikeCount(null)));
+       // 测试 price 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setPrice(null)));
+       // 测试 quantity 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setQuantity(null)));
+       // 准备参数
+       SpuPageReqVO reqVO = new SpuPageReqVO();
+       reqVO.setBeginCreateTime(null);
+       reqVO.setEndCreateTime(null);
+       reqVO.setName(null);
+       reqVO.setVisible(null);
+       reqVO.setSellPoint(null);
+       reqVO.setDescription(null);
+       reqVO.setCid(null);
+       reqVO.setListPicUrl(null);
+       reqVO.setPicUrls(null);
+       reqVO.setSort(null);
+       reqVO.setLikeCount(null);
+       reqVO.setPrice(null);
+       reqVO.setQuantity(null);
+
+       // 调用
+       PageResult<SpuDO> pageResult = spuService.getSpuPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbSpu, pageResult.getList().get(0));
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetSpuList() {
+       // mock 数据
+       SpuDO dbSpu = randomPojo(SpuDO.class, o -> { // 等会查询到
+           o.setCreateTime(null);
+           o.setName(null);
+           o.setVisible(null);
+           o.setSellPoint(null);
+           o.setDescription(null);
+           o.setCid(null);
+           o.setListPicUrl(null);
+           o.setPicUrls(null);
+           o.setSort(null);
+           o.setLikeCount(null);
+           o.setPrice(null);
+           o.setQuantity(null);
+       });
+       spuMapper.insert(dbSpu);
+       // 测试 createTime 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setCreateTime(null)));
+       // 测试 name 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setName(null)));
+       // 测试 visible 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setVisible(null)));
+       // 测试 sellPoint 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setSellPoint(null)));
+       // 测试 description 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setDescription(null)));
+       // 测试 cid 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setCid(null)));
+       // 测试 listPicUrl 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setListPicUrl(null)));
+       // 测试 picUrls 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setPicUrls(null)));
+       // 测试 sort 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setSort(null)));
+       // 测试 likeCount 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setLikeCount(null)));
+       // 测试 price 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setPrice(null)));
+       // 测试 quantity 不匹配
+       spuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setQuantity(null)));
+       // 准备参数
+       SpuExportReqVO reqVO = new SpuExportReqVO();
+       reqVO.setBeginCreateTime(null);
+       reqVO.setEndCreateTime(null);
+       reqVO.setName(null);
+       reqVO.setVisible(null);
+       reqVO.setSellPoint(null);
+       reqVO.setDescription(null);
+       reqVO.setCid(null);
+       reqVO.setListPicUrl(null);
+       reqVO.setPicUrls(null);
+       reqVO.setSort(null);
+       reqVO.setLikeCount(null);
+       reqVO.setPrice(null);
+       reqVO.setQuantity(null);
+
+       // 调用
+       List<SpuDO> list = spuService.getSpuList(reqVO);
+       // 断言
+       assertEquals(1, list.size());
+       assertPojoEquals(dbSpu, list.get(0));
+    }
+
+}