Преглед изворни кода

适配 mall 模块的 openapi

YunaiV пре 2 година
родитељ
комит
6bf5eb0133
100 измењених фајлова са 4201 додато и 6 уклоњено
  1. 2 2
      pom.xml
  2. 0 4
      yudao-module-mall/README.md
  3. 29 0
      yudao-module-mall/pom.xml
  4. BIN
      yudao-module-mall/yudao-module-mall.zip
  5. 34 0
      yudao-module-mall/yudao-module-product-api/pom.xml
  6. 4 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java
  7. 23 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApi.java
  8. 33 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/dto/ProductPropertyValueDetailRespDTO.java
  9. 40 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
  10. 95 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java
  11. 47 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java
  12. 24 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
  13. 127 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java
  14. 45 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
  15. 38 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentAuditStatusEnum.java
  16. 38 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/delivery/DeliveryTypeEnum.java
  17. 38 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/group/ProductGroupStyleEnum.java
  18. 37 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuSpecTypeEnum.java
  19. 48 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuStatusEnum.java
  20. 62 0
      yudao-module-mall/yudao-module-product-biz/pom.xml
  21. 1 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java
  22. 31 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApiImpl.java
  23. 49 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
  24. 38 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
  25. 82 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/ProductBrandController.java
  26. 34 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandBaseVO.java
  27. 14 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandCreateReqVO.java
  28. 13 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandListReqVO.java
  29. 30 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandPageReqVO.java
  30. 22 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandRespVO.java
  31. 20 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandUpdateReqVO.java
  32. 76 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/ProductCategoryController.java
  33. 38 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryBaseVO.java
  34. 14 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryCreateReqVO.java
  35. 13 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java
  36. 22 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryRespVO.java
  37. 20 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java
  38. 99 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java
  39. 70 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java
  40. 35 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java
  41. 22 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java
  42. 15 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java
  43. 15 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java
  44. 30 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java
  45. 22 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java
  46. 20 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java
  47. 27 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java
  48. 14 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java
  49. 22 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueDetailRespVO.java
  50. 24 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java
  51. 22 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java
  52. 20 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java
  53. 57 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java
  54. 75 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java
  55. 21 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java
  56. 30 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuOptionRespVO.java
  57. 28 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java
  58. 4 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.http
  59. 101 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java
  60. 76 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java
  61. 24 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuCreateReqVO.java
  62. 38 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java
  63. 45 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java
  64. 40 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java
  65. 26 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSimpleRespVO.java
  66. 29 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuUpdateReqVO.java
  67. 38 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/AppCategoryController.java
  68. 28 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/vo/AppCategoryRespVO.java
  69. 4 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/package-info.java
  70. 4 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/property/package-info.java
  71. 22 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/value/AppProductPropertyValueDetailRespVO.java
  72. 8 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http
  73. 79 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
  74. 91 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuDetailRespVO.java
  75. 39 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java
  76. 43 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java
  77. 33 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/brand/ProductBrandConvert.java
  78. 32 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/ProductCategoryConvert.java
  79. 48 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/property/ProductPropertyConvert.java
  80. 55 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/propertyvalue/ProductPropertyValueConvert.java
  81. 93 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
  82. 108 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
  83. 53 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java
  84. 67 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/ProductCategoryDO.java
  85. 129 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/comment/ProductCommentDO.java
  86. 30 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/delivery/DeliveryTemplateDO.java
  87. 45 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDO.java
  88. 43 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupBindDO.java
  89. 63 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupDO.java
  90. 38 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyDO.java
  91. 46 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyValueDO.java
  92. 38 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/search/ProductHotSearchDO.java
  93. 26 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/shop/ShopDO.java
  94. 137 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java
  95. 212 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java
  96. 34 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/brand/ProductBrandMapper.java
  97. 33 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/ProductCategoryMapper.java
  98. 32 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyMapper.java
  99. 43 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyValueMapper.java
  100. 75 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java

+ 2 - 2
pom.xml

@@ -17,10 +17,10 @@
         <module>yudao-module-system</module>
         <module>yudao-module-infra</module>
         <module>yudao-module-pay</module>
-        <module>yudao-module-bpm</module>
+<!--        <module>yudao-module-bpm</module>-->
 <!--        <module>yudao-module-visualization</module>-->
 <!--        <module>yudao-module-mp</module>-->
-        <!--        <module>yudao-module-mall</module>-->
+<!--        <module>yudao-module-mall</module>-->
         <!-- 示例项目 -->
         <module>yudao-example</module>
     </modules>

+ 0 - 4
yudao-module-mall/README.md

@@ -1,4 +0,0 @@
-mall 后端的代码,暂时归档成一个 yudao-module-mall 的压缩包。
-
-精力有限,近期还是以管理后台的工作流、大屏报表等功能为主!
-

+ 29 - 0
yudao-module-mall/pom.xml

@@ -0,0 +1,29 @@
+<?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">
+    <parent>
+        <artifactId>yudao</artifactId>
+        <groupId>cn.iocoder.boot</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>yudao-module-mall</artifactId>
+    <packaging>pom</packaging>
+
+    <name>${project.artifactId}</name>
+
+    <description>
+        商城大模块,由 product 商品、promotion 营销、trade 交易等组成
+    </description>
+    <modules>
+        <module>yudao-module-promotion-api</module>
+        <module>yudao-module-promotion-biz</module>
+        <module>yudao-module-product-api</module>
+        <module>yudao-module-product-biz</module>
+        <module>yudao-module-trade-api</module>
+        <module>yudao-module-trade-biz</module>
+    </modules>
+
+</project>

BIN
yudao-module-mall/yudao-module-mall.zip


+ 34 - 0
yudao-module-mall/yudao-module-product-api/pom.xml

@@ -0,0 +1,34 @@
+<?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>
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao-module-mall</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>yudao-module-product-api</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        product 模块 API,暴露给其它模块调用
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+
+        <!-- 参数校验 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

+ 4 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.product.api;

+ 23 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApi.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.product.api.property;
+
+import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 商品属性值 API 接口
+ *
+ * @author 芋道源码
+ */
+public interface ProductPropertyValueApi {
+
+    /**
+     * 根据编号数组,获得属性值列表
+     *
+     * @param ids 编号数组
+     * @return 属性值明细列表
+     */
+    List<ProductPropertyValueDetailRespDTO> getPropertyValueDetailList(Collection<Long> ids);
+
+}

+ 33 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/dto/ProductPropertyValueDetailRespDTO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.product.api.property.dto;
+
+import lombok.Data;
+
+/**
+ * 商品属性项的明细 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class ProductPropertyValueDetailRespDTO {
+
+    /**
+     * 属性的编号
+     */
+    private Long propertyId;
+
+    /**
+     * 属性的名称
+     */
+    private String propertyName;
+
+    /**
+     * 属性值的编号
+     */
+    private Long valueId;
+
+    /**
+     * 属性值的名称
+     */
+    private String valueName;
+
+}

+ 40 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.product.api.sku;
+
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 商品 SKU API 接口
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface ProductSkuApi {
+
+    /**
+     * 查询 SKU 信息
+     *
+     * @param id SKU 编号
+     * @return SKU 信息
+     */
+    ProductSkuRespDTO getSku(Long id);
+
+    /**
+     * 批量查询 SKU 数组
+     *
+     * @param ids SKU 编号列表
+     * @return SKU 数组
+     */
+    List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
+
+    /**
+     * 更新 SKU 库存
+     *
+     * @param updateStockReqDTO 更新请求
+     */
+    void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
+
+}

+ 95 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java

@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.product.api.sku.dto;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 商品 SKU 信息 Response DTO
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Data
+public class ProductSkuRespDTO {
+
+    /**
+     * 商品 SKU 编号,自增
+     */
+    private Long id;
+    /**
+     * SPU 编号
+     */
+    private Long spuId;
+    /**
+     * SPU 名字
+     */
+    private String spuName;
+
+    /**
+     * 属性数组,JSON 格式
+     */
+    private List<Property> properties;
+    /**
+     * 销售价格,单位:分
+     */
+    private Integer price;
+    /**
+     * 市场价,单位:分
+     */
+    private Integer marketPrice;
+    /**
+     * 成本价,单位:分
+     */
+    private Integer costPrice;
+    /**
+     * SKU 的条形码
+     */
+    private String barCode;
+    /**
+     * 图片地址
+     */
+    private String picUrl;
+    /**
+     * SKU 状态
+     * <p>
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 库存
+     */
+    private Integer stock;
+    /**
+     * 预警预存
+     */
+    private Integer warnStock;
+    /**
+     * 商品重量,单位:kg 千克
+     */
+    private Double weight;
+    /**
+     * 商品体积,单位:m^3 平米
+     */
+    private Double volume;
+
+    /**
+     * 商品属性
+     */
+    @Data
+    public static class Property {
+
+        /**
+         * 属性编号
+         */
+        private Long propertyId;
+        /**
+         * 属性值编号
+         */
+        private Long valueId;
+
+    }
+
+
+}

+ 47 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.product.api.sku.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 商品 SKU 更新库存 Request DTO
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductSkuUpdateStockReqDTO {
+
+    /**
+     * 商品 SKU
+     */
+    @NotNull(message = "商品 SKU 不能为空")
+    private List<Item> items;
+
+    @Data
+    public static class Item {
+
+        /**
+         * 商品 SKU 编号
+         */
+        @NotNull(message = "商品 SKU 编号不能为空")
+        private Long id;
+
+        /**
+         * 库存变化数量
+         *
+         * 正数:增加库存
+         * 负数:扣减库存
+         */
+        @NotNull(message = "库存变化数量不能为空")
+        private Integer incrCount;
+
+    }
+
+}

+ 24 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.product.api.spu;
+
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 商品 SPU API 接口
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface ProductSpuApi {
+
+    /**
+     * 批量查询 SPU 数组
+     *
+     * @param ids SPU 编号列表
+     * @return SPU 数组
+     */
+    List<ProductSpuRespDTO> getSpuList(Collection<Long> ids);
+
+}

+ 127 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java

@@ -0,0 +1,127 @@
+package cn.iocoder.yudao.module.product.api.spu.dto;
+
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import lombok.Data;
+
+import java.util.List;
+
+// TODO @LeeYan9: ProductSpuRespDTO
+/**
+ * 商品 SPU 信息 Response DTO
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Data
+public class ProductSpuRespDTO {
+
+    /**
+     * 商品 SPU 编号,自增
+     */
+    private Long id;
+
+    // ========== 基本信息 =========
+
+    /**
+     * 商品名称
+     */
+    private String name;
+    /**
+     * 商品编码
+     */
+    private String code;
+    /**
+     * 促销语
+     */
+    private String sellPoint;
+    /**
+     * 商品详情
+     */
+    private String description;
+    /**
+     * 商品分类编号
+     */
+    private Long categoryId;
+    /**
+     * 商品品牌编号
+     */
+    private Long brandId;
+    /**
+     * 商品图片的数组
+     * <p>
+     * 1. 第一张图片将作为商品主图,支持同时上传多张图;
+     * 2. 建议使用尺寸 800x800 像素以上、大小不超过 1M 的正方形图片;
+     * 3. 至少 1 张,最多上传 10 张
+     */
+    private List<String> picUrls;
+    /**
+     * 商品视频
+     */
+    private String videoUrl;
+
+    /**
+     * 排序字段
+     */
+    private Integer sort;
+    /**
+     * 商品状态
+     * <p>
+     * 枚举 {@link ProductSpuStatusEnum}
+     */
+    private Integer status;
+
+    // ========== SKU 相关字段 =========
+
+    /**
+     * 规格类型
+     * <p>
+     * 枚举 {@link ProductSpuSpecTypeEnum}
+     */
+    private Integer specType;
+    /**
+     * 最小价格,单位使用:分
+     * <p>
+     * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最小值
+     */
+    private Integer minPrice;
+    /**
+     * 最大价格,单位使用:分
+     * <p>
+     * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最大值
+     */
+    private Integer maxPrice;
+    /**
+     * 市场价,单位使用:分
+     * <p>
+     * 基于其对应的 {@link ProductSkuRespDTO#getMarketPrice()} 最大值
+     */
+    private Integer marketPrice;
+    /**
+     * 总库存
+     * <p>
+     * 基于其对应的 {@link ProductSkuRespDTO#getStock()} 求和
+     */
+    private Integer totalStock;
+    /**
+     * 是否展示库存
+     */
+    private Boolean showStock;
+
+    // ========== 统计相关字段 =========
+
+    /**
+     * 商品销量
+     */
+    private Integer salesCount;
+    /**
+     * 虚拟销量
+     */
+    private Integer virtualSalesCount;
+    /**
+     * 商品点击量
+     */
+    private Integer clickCount;
+
+}

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

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * Product 错误码枚举类
+ *
+ * product 系统,使用 1-008-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+    // ========== 商品分类相关 1008001000 ============
+    ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1008001000, "商品分类不存在");
+    ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1008001001, "父分类不存在");
+    ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, "父分类不能是二级分类");
+    ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, "存在子分类,无法删除");
+    ErrorCode CATEGORY_DISABLED = new ErrorCode(1008001004, "商品分类({})已禁用,无法使用");
+
+    // ========== 商品品牌相关编号 1008002000 ==========
+    ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1008002000, "品牌不存在");
+    ErrorCode BRAND_DISABLED = new ErrorCode(1008002001, "品牌不存在");
+    ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1008002002, "品牌名称已存在");
+
+    // ========== 商品属性项 1008003000 ==========
+    ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1008003000, "属性项不存在");
+    ErrorCode PROPERTY_EXISTS = new ErrorCode(1008003001, "属性项的名称已存在");
+    ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1008003002, "属性项下存在属性值,无法删除");
+
+    // ========== 商品属性值 1008004000 ==========
+    ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "属性值不存在");
+    ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1008004001, "属性值的名称已存在");
+
+    // ========== 商品 SPU 1008005000 ==========
+    ErrorCode SPU_NOT_EXISTS = new ErrorCode(1008005000, "商品 SPU 不存在");
+    ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1008005001, "商品分类不正确,原因:必须使用第三级的商品分类下");
+    ErrorCode SPU_NOT_ENABLE = new ErrorCode(1008005002, "商品 SPU 不处于上架状态");
+
+    // ========== 商品 SKU 1008006000 ==========
+    ErrorCode SKU_NOT_EXISTS = new ErrorCode(1008006000, "商品 SKU 不存在");
+    ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复");
+    ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU,其属性项必须一致");
+    ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU,必须不重复");
+    ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentAuditStatusEnum.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.enums.comment;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 商品评论的审批状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ProductCommentAuditStatusEnum implements IntArrayValuable {
+
+    NONE(1, "待审核"),
+    APPROVE(2, "审批通过"),
+    REJECT(2, "审批不通过"),;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductCommentAuditStatusEnum::getStatus).toArray();
+
+    /**
+     * 审批状态
+     */
+    private final Integer status;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/delivery/DeliveryTypeEnum.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.enums.delivery;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 配送方式枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum DeliveryTypeEnum implements IntArrayValuable {
+
+    // TODO 芋艿:英文单词,需要再想下;
+    EXPRESS(1, "快递发货"),
+    USER(2, "用户自提"),;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DeliveryTypeEnum::getMode).toArray();
+
+    /**
+     * 配送方式
+     */
+    private final Integer mode;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/group/ProductGroupStyleEnum.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.enums.group;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 商品分组的样式枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ProductGroupStyleEnum implements IntArrayValuable {
+
+    ONE(1, "每列一个"),
+    TWO(2, "每列两个"),
+    THREE(2, "每列三个"),;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductGroupStyleEnum::getStyle).toArray();
+
+    /**
+     * 列表样式
+     */
+    private final Integer style;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 37 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuSpecTypeEnum.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.product.enums.spu;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 商品 SPU 规格类型
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ProductSpuSpecTypeEnum implements IntArrayValuable {
+
+    RECYCLE(1, "统一规格"),
+    DISABLE(2, "多规格");
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductSpuSpecTypeEnum::getType).toArray();
+
+    /**
+     * 规格类型
+     */
+    private final Integer type;
+    /**
+     * 规格名称
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 48 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuStatusEnum.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.product.enums.spu;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 商品 SPU 状态
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ProductSpuStatusEnum implements IntArrayValuable {
+
+    RECYCLE(-1, "回收站"),
+    DISABLE(0, "下架"),
+    ENABLE(1, "上架"),;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductSpuStatusEnum::getStatus).toArray();
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+    /**
+     * 判断是否处于【上架】状态
+     *
+     * @param status 状态
+     * @return 是否处于【上架】状态
+     */
+    public static boolean isEnable(Integer status) {
+        return ENABLE.getStatus().equals(status);
+    }
+
+}

+ 62 - 0
yudao-module-mall/yudao-module-product-biz/pom.xml

@@ -0,0 +1,62 @@
+<?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">
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao-module-mall</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>yudao-module-product-biz</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        product 模块,主要实现商品相关功能
+        例如:品牌、商品分类、spu、sku等功能。
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-product-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
+        </dependency>
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- DB 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-test</artifactId>
+        </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-excel</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 1 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.product.api;

+ 31 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApiImpl.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.product.api.property;
+
+import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
+import cn.iocoder.yudao.module.product.convert.propertyvalue.ProductPropertyValueConvert;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 商品属性值 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class ProductPropertyValueApiImpl implements ProductPropertyValueApi {
+
+    @Resource
+    private ProductPropertyValueService productPropertyValueService;
+
+    @Override
+    public List<ProductPropertyValueDetailRespDTO> getPropertyValueDetailList(Collection<Long> ids) {
+        return ProductPropertyValueConvert.INSTANCE.convertList02(
+                productPropertyValueService.getPropertyValueDetailList(ids));
+    }
+
+}

+ 49 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java

@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.product.api.sku;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * TODO LeeYan9: 类注释;
+ * @author LeeYan9
+ * @since 2022-09-06
+ */
+@Service
+@Validated
+public class ProductSkuApiImpl implements ProductSkuApi {
+
+    @Resource
+    private ProductSkuService productSkuService;
+
+    @Override
+    public ProductSkuRespDTO getSku(Long id) {
+        // TODO TODO LeeYan9: 需要实现
+        return null;
+    }
+
+    @Override
+    public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        List<ProductSkuDO> skus = productSkuService.getSkuList(ids);
+        return ProductSkuConvert.INSTANCE.convertList04(skus);
+    }
+
+    @Override
+    public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
+        productSkuService.updateSkuStock(updateStockReqDTO);
+    }
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.api.spu;
+
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * TODO LeeYan9: 类注释;
+ *
+ * @author LeeYan9
+ * @since 2022-09-06
+ */
+@Service
+@Validated
+public class ProductSpuApiImpl implements ProductSpuApi {
+
+    @Resource
+    private ProductSpuMapper productSpuMapper;
+
+    @Override
+    public List<ProductSpuRespDTO> getSpuList(Collection<Long> spuIds) {
+        // TODO TODO LeeYan9: AllEmpty?
+        if (CollectionUtils.isAnyEmpty(spuIds)) {
+            return Collections.emptyList();
+        }
+        List<ProductSpuDO> productSpuDOList = productSpuMapper.selectBatchIds(spuIds);
+        return ProductSpuConvert.INSTANCE.convertList2(productSpuDOList);
+    }
+}

+ 82 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/ProductBrandController.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.*;
+import cn.iocoder.yudao.module.product.convert.brand.ProductBrandConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO;
+import cn.iocoder.yudao.module.product.service.brand.ProductBrandService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 商品品牌")
+@RestController
+@RequestMapping("/product/brand")
+@Validated
+public class ProductBrandController {
+
+    @Resource
+    private ProductBrandService brandService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建品牌")
+    @PreAuthorize("@ss.hasPermission('product:brand:create')")
+    public CommonResult<Long> createBrand(@Valid @RequestBody ProductBrandCreateReqVO createReqVO) {
+        return success(brandService.createBrand(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新品牌")
+    @PreAuthorize("@ss.hasPermission('product:brand:update')")
+    public CommonResult<Boolean> updateBrand(@Valid @RequestBody ProductBrandUpdateReqVO updateReqVO) {
+        brandService.updateBrand(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除品牌")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('product:brand:delete')")
+    public CommonResult<Boolean> deleteBrand(@RequestParam("id") Long id) {
+        brandService.deleteBrand(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得品牌")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('product:brand:query')")
+    public CommonResult<ProductBrandRespVO> getBrand(@RequestParam("id") Long id) {
+        ProductBrandDO brand = brandService.getBrand(id);
+        return success(ProductBrandConvert.INSTANCE.convert(brand));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得品牌分页")
+    @PreAuthorize("@ss.hasPermission('product:brand:query')")
+    public CommonResult<PageResult<ProductBrandRespVO>> getBrandPage(@Valid ProductBrandPageReqVO pageVO) {
+        PageResult<ProductBrandDO> pageResult = brandService.getBrandPage(pageVO);
+        return success(ProductBrandConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得品牌列表")
+    @PreAuthorize("@ss.hasPermission('product:brand:query')")
+    public CommonResult<List<ProductBrandRespVO>> getBrandList(@Valid ProductBrandListReqVO listVO) {
+        List<ProductBrandDO> list = brandService.getBrandList(listVO);
+        list.sort(Comparator.comparing(ProductBrandDO::getSort));
+        return success(ProductBrandConvert.INSTANCE.convertList(list));
+    }
+
+}

+ 34 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandBaseVO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+* 商品品牌 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class ProductBrandBaseVO {
+
+    @Schema(description = "品牌名称", required = true, example = "芋道")
+    @NotNull(message = "品牌名称不能为空")
+    private String name;
+
+    @Schema(description = "品牌图片", required = true)
+    @NotNull(message = "品牌图片不能为空")
+    private String picUrl;
+
+    @Schema(description = "品牌排序", required = true, example = "1")
+    @NotNull(message = "品牌排序不能为空")
+    private Integer sort;
+
+    @Schema(description = "品牌描述", example = "描述")
+    private String description;
+
+    @Schema(description = "状态", required = true, example = "0")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+}

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

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品品牌创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductBrandCreateReqVO extends ProductBrandBaseVO {
+
+}

+ 13 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandListReqVO.java

@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 商品品牌分页 Request VO")
+@Data
+public class ProductBrandListReqVO {
+
+    @Schema(description = "品牌名称", example = "芋道")
+    private String name;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 商品品牌分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductBrandPageReqVO extends PageParam {
+
+    @Schema(description = "品牌名称", example = "芋道")
+    private String name;
+
+    @Schema(description = "状态", example = "0")
+    private Integer status;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Schema(description = "创建时间")
+    private LocalDateTime[] createTime;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 品牌 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductBrandRespVO extends ProductBrandBaseVO {
+
+    @Schema(description = "品牌编号", required = true, example = "1")
+    private Long id;
+
+    @Schema(description = "创建时间", required = true)
+    private LocalDateTime createTime;
+
+}

+ 20 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandUpdateReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 商品品牌更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductBrandUpdateReqVO extends ProductBrandBaseVO {
+
+    @Schema(description = "品牌编号", required = true, example = "1")
+    @NotNull(message = "品牌编号不能为空")
+    private Long id;
+
+}

+ 76 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/ProductCategoryController.java

@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.product.controller.admin.category;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;
+import cn.iocoder.yudao.module.product.convert.category.ProductCategoryConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
+import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 商品分类")
+@RestController
+@RequestMapping("/product/category")
+@Validated
+public class ProductCategoryController {
+
+    @Resource
+    private ProductCategoryService categoryService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建商品分类")
+    @PreAuthorize("@ss.hasPermission('product:category:create')")
+    public CommonResult<Long> createCategory(@Valid @RequestBody ProductCategoryCreateReqVO createReqVO) {
+        return success(categoryService.createCategory(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新商品分类")
+    @PreAuthorize("@ss.hasPermission('product:category:update')")
+    public CommonResult<Boolean> updateCategory(@Valid @RequestBody ProductCategoryUpdateReqVO updateReqVO) {
+        categoryService.updateCategory(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除商品分类")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('product:category:delete')")
+    public CommonResult<Boolean> deleteCategory(@RequestParam("id") Long id) {
+        categoryService.deleteCategory(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得商品分类")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('product:category:query')")
+    public CommonResult<ProductCategoryRespVO> getCategory(@RequestParam("id") Long id) {
+        ProductCategoryDO category = categoryService.getCategory(id);
+        return success(ProductCategoryConvert.INSTANCE.convert(category));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得商品分类列表")
+    @PreAuthorize("@ss.hasPermission('product:category:query')")
+    public CommonResult<List<ProductCategoryRespVO>> getCategoryList(@Valid ProductCategoryListReqVO treeListReqVO) {
+        List<ProductCategoryDO> list = categoryService.getEnableCategoryList(treeListReqVO);
+        list.sort(Comparator.comparing(ProductCategoryDO::getSort));
+        return success(ProductCategoryConvert.INSTANCE.convertList(list));
+    }
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryBaseVO.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+* 商品分类 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class ProductCategoryBaseVO {
+
+    @Schema(description = "父分类编号", required = true, example = "1")
+    @NotNull(message = "父分类编号不能为空")
+    private Long parentId;
+
+    @Schema(description = "分类名称", required = true, example = "办公文具")
+    @NotBlank(message = "分类名称不能为空")
+    private String name;
+
+    @Schema(description = "分类图片", required = true)
+    @NotBlank(message = "分类图片不能为空")
+    private String picUrl;
+
+    @Schema(description = "分类排序", required = true, example = "1")
+    private Integer sort;
+
+    @Schema(description = "分类描述", required = true, example = "描述")
+    private String description;
+
+    @Schema(description = "开启状态", required = true, example = "0")
+    @NotNull(message = "开启状态不能为空")
+    private Integer status;
+
+}

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

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品分类创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductCategoryCreateReqVO extends ProductCategoryBaseVO {
+
+}

+ 13 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java

@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 商品分类列表查询 Request VO")
+@Data
+public class ProductCategoryListReqVO {
+
+    @Schema(description = "分类名称", example = "办公文具")
+    private String name;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 商品分类 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductCategoryRespVO extends ProductCategoryBaseVO {
+
+    @Schema(description = "分类编号", required = true, example = "2")
+    private Long id;
+
+    @Schema(description = "创建时间", required = true)
+    private LocalDateTime createTime;
+
+}

+ 20 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 商品分类更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductCategoryUpdateReqVO extends ProductCategoryBaseVO {
+
+    @Schema(description = "分类编号", required = true, example = "2")
+    @NotNull(message = "分类编号不能为空")
+    private Long id;
+
+}

+ 99 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java

@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.module.product.controller.admin.property;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.*;
+import cn.iocoder.yudao.module.product.convert.property.ProductPropertyConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - 商品属性项")
+@RestController
+@RequestMapping("/product/property")
+@Validated
+public class ProductPropertyController {
+
+    @Resource
+    private ProductPropertyService productPropertyService;
+    @Resource
+    private ProductPropertyValueService productPropertyValueService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建属性项")
+    @PreAuthorize("@ss.hasPermission('product:property:create')")
+    public CommonResult<Long> createProperty(@Valid @RequestBody ProductPropertyCreateReqVO createReqVO) {
+        return success(productPropertyService.createProperty(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新属性项")
+    @PreAuthorize("@ss.hasPermission('product:property:update')")
+    public CommonResult<Boolean> updateProperty(@Valid @RequestBody ProductPropertyUpdateReqVO updateReqVO) {
+        productPropertyService.updateProperty(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除属性项")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('product:property:delete')")
+    public CommonResult<Boolean> deleteProperty(@RequestParam("id") Long id) {
+        productPropertyService.deleteProperty(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得属性项")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('product:property:query')")
+    public CommonResult<ProductPropertyRespVO> getProperty(@RequestParam("id") Long id) {
+        return success(ProductPropertyConvert.INSTANCE.convert(productPropertyService.getProperty(id)));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得属性项列表")
+    @PreAuthorize("@ss.hasPermission('product:property:query')")
+    public CommonResult<List<ProductPropertyRespVO>> getPropertyList(@Valid ProductPropertyListReqVO listReqVO) {
+        return success(ProductPropertyConvert.INSTANCE.convertList(productPropertyService.getPropertyList(listReqVO)));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得属性项分页")
+    @PreAuthorize("@ss.hasPermission('product:property:query')")
+    public CommonResult<PageResult<ProductPropertyRespVO>> getPropertyPage(@Valid ProductPropertyPageReqVO pageVO) {
+        return success(ProductPropertyConvert.INSTANCE.convertPage(productPropertyService.getPropertyPage(pageVO)));
+    }
+
+    @GetMapping("/get-value-list")
+    @Operation(summary = "获得属性项列表")
+    @PreAuthorize("@ss.hasPermission('product:property:query')")
+    public CommonResult<List<ProductPropertyAndValueRespVO>> getPropertyAndValueList(@Valid ProductPropertyListReqVO listReqVO) {
+        // 查询属性项
+        List<ProductPropertyDO> keys = productPropertyService.getPropertyList(listReqVO);
+        if (CollUtil.isEmpty(keys)) {
+            return success(Collections.emptyList());
+        }
+        // 查询属性值
+        List<ProductPropertyValueDO> values = productPropertyValueService.getPropertyValueListByPropertyId(
+                convertSet(keys, ProductPropertyDO::getId));
+        return success(ProductPropertyConvert.INSTANCE.convertList(keys, values));
+    }
+
+}

+ 70 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java

@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.product.controller.admin.property;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
+import cn.iocoder.yudao.module.product.convert.propertyvalue.ProductPropertyValueConvert;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 商品属性值")
+@RestController
+@RequestMapping("/product/property/value")
+@Validated
+public class ProductPropertyValueController {
+
+    @Resource
+    private ProductPropertyValueService productPropertyValueService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建属性值")
+    @PreAuthorize("@ss.hasPermission('product:property:create')")
+    public CommonResult<Long> createProperty(@Valid @RequestBody ProductPropertyValueCreateReqVO createReqVO) {
+        return success(productPropertyValueService.createPropertyValue(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新属性值")
+    @PreAuthorize("@ss.hasPermission('product:property:update')")
+    public CommonResult<Boolean> updateProperty(@Valid @RequestBody ProductPropertyValueUpdateReqVO updateReqVO) {
+        productPropertyValueService.updatePropertyValue(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除属性值")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('product:property:delete')")
+    public CommonResult<Boolean> deleteProperty(@RequestParam("id") Long id) {
+        productPropertyValueService.deletePropertyValue(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得属性值")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('product:property:query')")
+    public CommonResult<ProductPropertyValueRespVO> getProperty(@RequestParam("id") Long id) {
+        return success(ProductPropertyValueConvert.INSTANCE.convert(productPropertyValueService.getPropertyValue(id)));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得属性值分页")
+    @PreAuthorize("@ss.hasPermission('product:property:query')")
+    public CommonResult<PageResult<ProductPropertyValueRespVO>> getPropertyValuePage(@Valid ProductPropertyValuePageReqVO pageVO) {
+        return success(ProductPropertyValueConvert.INSTANCE.convertPage(productPropertyValueService.getPropertyValuePage(pageVO)));
+    }
+}

+ 35 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品属性项 + 属性值 Response VO")
+@Data
+public class ProductPropertyAndValueRespVO {
+
+    @Schema(description = "属性项的编号", required = true, example = "1024")
+    private Long id;
+
+    @Schema(description = "属性项的名称", required = true, example = "颜色")
+    private String name;
+
+    /**
+     * 属性值的集合
+     */
+    private List<Value> values;
+
+    @Schema(description = "管理后台 - 属性值的简单 Response VO")
+    @Data
+    public static class Value {
+
+        @Schema(description = "属性值的编号", required = true, example = "2048")
+        private Long id;
+
+        @Schema(description = "属性值的名称", required = true, example = "红色")
+        private String name;
+
+    }
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 商品属性项 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class ProductPropertyBaseVO {
+
+    @Schema(description = "名称", required = true, example = "颜色")
+    @NotBlank(message = "名称不能为空")
+    private String name;
+
+    @Schema(description = "备注", example = "颜色")
+    private String remark;
+
+}

+ 15 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 属性项创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductPropertyCreateReqVO extends ProductPropertyBaseVO {
+
+
+}

+ 15 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 属性项 List Request VO")
+@Data
+@ToString(callSuper = true)
+public class ProductPropertyListReqVO {
+
+    @Schema(description = "名称", example = "颜色")
+    private String name;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 属性项 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductPropertyPageReqVO extends PageParam {
+
+    @Schema(description = "名称", example = "颜色")
+    private String name;
+
+    @Schema(description = "状态", required = true, example = "1")
+    private Integer status;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Schema(description = "创建时间")
+    private LocalDateTime[] createTime;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 属性项 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductPropertyRespVO extends ProductPropertyBaseVO {
+
+    @Schema(description = "编号", required = true, example = "1024")
+    private Long id;
+
+    @Schema(description = "创建时间", required = true)
+    private LocalDateTime createTime;
+
+}

+ 20 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 属性项更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductPropertyUpdateReqVO extends ProductPropertyBaseVO {
+
+    @Schema(description = "主键", required = true, example = "1")
+    @NotNull(message = "主键不能为空")
+    private Long id;
+
+}

+ 27 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+* 属性值 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class ProductPropertyValueBaseVO {
+
+    @Schema(description = "属性项的编号", required = true, example = "1024")
+    @NotNull(message = "属性项的编号不能为空")
+    private Long propertyId;
+
+    @Schema(description = "名称", required = true, example = "红色")
+    @NotEmpty(message = "名称名字不能为空")
+    private String name;
+
+    @Schema(description = "备注", example = "颜色")
+    private String remark;
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品属性值创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductPropertyValueCreateReqVO extends ProductPropertyValueBaseVO {
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueDetailRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 商品属性值的明细 Response VO")
+@Data
+public class ProductPropertyValueDetailRespVO {
+
+    @Schema(description = "属性的编号", required = true, example = "1")
+    private Long propertyId;
+
+    @Schema(description = "属性的名称", required = true, example = "颜色")
+    private String propertyName;
+
+    @Schema(description = "属性值的编号", required = true, example = "1024")
+    private Long valueId;
+
+    @Schema(description = "属性值的名称", required = true, example = "红色")
+    private String valueName;
+
+}

+ 24 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品属性值分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductPropertyValuePageReqVO extends PageParam {
+
+    @Schema(description = "属性项的编号", example = "1024")
+    private String propertyId;
+
+    @Schema(description = "名称", example = "红色")
+    private String name;
+
+    @Schema(description = "状态", required = true, example = "1")
+    private Integer status;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 商品属性值 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductPropertyValueRespVO extends ProductPropertyValueBaseVO {
+
+    @Schema(description = "编号", required = true, example = "10")
+    private Long id;
+
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+}

+ 20 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 商品属性值更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductPropertyValueUpdateReqVO extends ProductPropertyValueBaseVO {
+
+    @Schema(description = "主键", required = true, example = "1024")
+    @NotNull(message = "主键不能为空")
+    private Long id;
+
+}

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

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - 商品 sku")
+@RestController
+@RequestMapping("/product/sku")
+@Validated
+public class ProductSkuController {
+
+    @Resource
+    private ProductSkuService productSkuService;
+    @Resource
+    private ProductSpuService productSpuService;
+
+    @GetMapping("/get-option-list")
+    @Operation(summary = "获得商品 SKU 选项的列表")
+//    @PreAuthorize("@ss.hasPermission('product:sku:query')")
+    public CommonResult<List<ProductSkuOptionRespVO>> getSkuOptionList() {
+        // 获得 SKU 列表
+        List<ProductSkuDO> skus = productSkuService.getSkuList();
+        if (CollUtil.isEmpty(skus)) {
+            return success(Collections.emptyList());
+        }
+
+        // 获得对应的 SPU 映射
+        Map<Long, ProductSpuDO> spuMap = productSpuService.getSpuMap(convertSet(skus, ProductSkuDO::getSpuId));
+        // 转换为返回结果
+        List<ProductSkuOptionRespVO> skuVOs = ProductSkuConvert.INSTANCE.convertList05(skus);
+        skuVOs.forEach(sku -> MapUtils.findAndThen(spuMap, sku.getSpuId(),
+                spu -> sku.setSpuId(spu.getId()).setSpuName(spu.getName())));
+        return success(skuVOs);
+    }
+
+}

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

@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+* 商品 SKU Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class ProductSkuBaseVO {
+
+    @Schema(description = "商品 SKU 名字", required = true, example = "芋道")
+    @NotEmpty(message = "商品 SKU 名字不能为空")
+    private String name;
+
+    @Schema(description = "销售价格,单位:分", required = true, example = "1024")
+    @NotNull(message = "销售价格,单位:分不能为空")
+    private Integer price;
+
+    @Schema(description = "市场价", example = "1024")
+    private Integer marketPrice;
+
+    @Schema(description = "成本价", example = "1024")
+    private Integer costPrice;
+
+    @Schema(description = "条形码", example = "haha")
+    private String barCode;
+
+    @Schema(description = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
+    @NotNull(message = "图片地址不能为空")
+    private String picUrl;
+
+    @Schema(description = "SKU 状态", required = true, example = "1")
+    @NotNull(message = "SKU 状态不能为空")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "库存", required = true, example = "1")
+    @NotNull(message = "库存不能为空")
+    private Integer stock;
+
+    @Schema(description = "预警预存", example = "1")
+    private Integer warnStock;
+
+    @Schema(description = "商品重量", example = "1") // 单位:kg 千克
+    private Double weight;
+
+    @Schema(description = "商品体积", example = "1024") // 单位:m^3 平米
+    private Double volume;
+
+    @Schema(description = "商品属性")
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Property {
+
+        @Schema(description = "属性编号", required = true, example = "1")
+        @NotNull(message = "属性编号不能为空")
+        private Long propertyId;
+
+        @Schema(description = "属性值编号", required = true, example = "1024")
+        @NotNull(message = "属性值编号不能为空")
+        private Long valueId;
+
+    }
+
+}

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

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SKU 创建/更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSkuCreateOrUpdateReqVO extends ProductSkuBaseVO {
+
+    /**
+     * 属性数组
+     */
+    private List<Property> properties;
+
+}

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

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 商品 SKU 选项 Response VO") // 用于前端 SELECT 选项
+@Data
+public class ProductSkuOptionRespVO {
+
+    @Schema(description = "主键", required = true, example = "1024")
+    private Long id;
+
+    @Schema(description = "商品 SKU 名字", example = "红色")
+    private String name;
+
+    @Schema(description = "销售价格", required = true, example = "100")
+    private String price;
+
+    @Schema(description = "库存", required = true, example = "100")
+    private Integer stock;
+
+    // ========== 商品 SPU 信息 ==========
+
+    @Schema(description = "商品 SPU 编号", required = true, example = "1")
+    private Long spuId;
+
+    @Schema(description = "商品 SPU 名字", required = true, example = "iPhone 11")
+    private String spuName;
+
+}

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

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SKU Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSkuRespVO extends ProductSkuBaseVO {
+
+    @Schema(description = "主键", required = true, example = "1024")
+    private Long id;
+
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    /**
+     * 属性数组
+     */
+    private List<Property> properties;
+
+}

+ 4 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.http

@@ -0,0 +1,4 @@
+### 获得商品 SPU 明细
+GET {{baseUrl}}/product/spu/get-detail?id=4
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}

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

@@ -0,0 +1,101 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
+
+@Tag(name = "管理后台 - 商品 SPU")
+@RestController
+@RequestMapping("/product/spu")
+@Validated
+public class ProductSpuController {
+
+    @Resource
+    private ProductSpuService productSpuService;
+    @Resource
+    private ProductSkuService productSkuService;
+    @Resource
+    private ProductPropertyValueService productPropertyValueService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建商品 SPU")
+    @PreAuthorize("@ss.hasPermission('product:spu:create')")
+    public CommonResult<Long> createProductSpu(@Valid @RequestBody ProductSpuCreateReqVO createReqVO) {
+        return success(productSpuService.createSpu(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新商品 SPU")
+    @PreAuthorize("@ss.hasPermission('product:spu:update')")
+    public CommonResult<Boolean> updateSpu(@Valid @RequestBody ProductSpuUpdateReqVO updateReqVO) {
+        productSpuService.updateSpu(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除商品 SPU")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('product:spu:delete')")
+    public CommonResult<Boolean> deleteSpu(@RequestParam("id") Long id) {
+        productSpuService.deleteSpu(id);
+        return success(true);
+    }
+
+    @GetMapping("/get-detail")
+    @Operation(summary = "获得商品 SPU 明细")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
+    public CommonResult<ProductSpuDetailRespVO> getSpuDetail(@RequestParam("id") Long id) {
+        // 获得商品 SPU
+        ProductSpuDO spu = productSpuService.getSpu(id);
+        if (spu == null) {
+            throw exception(SPU_NOT_EXISTS);
+        }
+
+        // 查询商品 SKU
+        List<ProductSkuDO> skus = productSkuService.getSkuListBySpuIdAndStatus(spu.getId(), null);
+        // 查询商品属性
+        List<ProductPropertyValueDetailRespBO> propertyValues = productPropertyValueService
+                .getPropertyValueDetailList(ProductSkuConvert.INSTANCE.convertPropertyValueIds(skus));
+        // 拼接
+        return success(ProductSpuConvert.INSTANCE.convert03(spu, skus, propertyValues));
+    }
+
+    @GetMapping("/get-simple-list")
+    @Operation(summary = "获得商品 SPU 精简列表")
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
+    public CommonResult<List<ProductSpuSimpleRespVO>> getSpuSimpleList() {
+        List<ProductSpuDO> list = productSpuService.getSpuList();
+        return success(ProductSpuConvert.INSTANCE.convertList02(list));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得商品 SPU 分页")
+    @PreAuthorize("@ss.hasPermission('product:spu:query')")
+    public CommonResult<PageResult<ProductSpuRespVO>> getSpuPage(@Valid ProductSpuPageReqVO pageVO) {
+        return success(ProductSpuConvert.INSTANCE.convertPage(productSpuService.getSpuPage(pageVO)));
+    }
+
+}

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

@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+* 商品 SPU Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class ProductSpuBaseVO {
+
+    @Schema(description = "商品名称", required = true, example = "芋道")
+    @NotEmpty(message = "商品名称不能为空")
+    private String name;
+
+    @Schema(description = "商品编码", example = "yudaoyuanma")
+    private String code;
+
+    @Schema(description = "促销语", example = "好吃!")
+    private String sellPoint;
+
+    @Schema(description = "商品详情", required = true, example = "我是商品描述")
+    @NotNull(message = "商品详情不能为空")
+    private String description;
+
+    @Schema(description = "商品分类编号", required = true, example = "1")
+    @NotNull(message = "商品分类编号不能为空")
+    private Long categoryId;
+
+    @Schema(description = "商品品牌编号", example = "1")
+    private Long brandId;
+
+    @Schema(description = "商品图片的数组", required = true)
+    @NotNull(message = "商品图片的数组不能为空")
+    private List<String> picUrls;
+
+    @Schema(description = "商品视频", required = true)
+    private String videoUrl;
+
+    @Schema(description = "排序字段", required = true, example = "1")
+    private Integer sort;
+
+    @Schema(description = "商品状态", required = true, example = "1")
+    @NotNull(message = "商品状态不能为空")
+    @InEnum(ProductSpuStatusEnum.class)
+    private Integer status;
+
+    // ========== SKU 相关字段 =========
+
+    @Schema(description = "规格类型", required = true, example = "1")
+    @NotNull(message = "规格类型不能为空")
+    @InEnum(ProductSpuSpecTypeEnum.class)
+    private Integer specType;
+
+    @Schema(description = "是否展示库存", required = true, example = "true")
+    @NotNull(message = "是否展示库存不能为空")
+    private Boolean showStock;
+
+    @Schema(description = "市场价", example = "1024")
+    private Integer marketPrice;
+
+    // ========== 统计相关字段 =========
+
+    @Schema(description = "虚拟销量", required = true, example = "1024")
+    @NotNull(message = "虚拟销量不能为空")
+    private Integer virtualSalesCount;
+
+}

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

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SPU 创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuCreateReqVO extends ProductSpuBaseVO {
+
+    /**
+     * SKU 数组
+     */
+    @Valid
+    private List<ProductSkuCreateOrUpdateReqVO> skus;
+
+}

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

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SPU 详细 Response VO") // 包括关联的 SKU 等信息
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuDetailRespVO extends ProductSpuRespVO {
+
+    // ========== SKU 相关字段 =========
+
+    /**
+     * SKU 数组
+     */
+    private List<Sku> skus;
+
+    @Schema(description = "管理后台 - 商品 SKU 详细 Response VO")
+    @Data
+    @EqualsAndHashCode(callSuper = true)
+    @ToString(callSuper = true)
+    public static class Sku extends ProductSkuBaseVO {
+
+        /**
+         * 属性数组
+         */
+        private List<ProductPropertyValueDetailRespVO> properties;
+
+    }
+
+}

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

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品 SPU 分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuPageReqVO extends PageParam {
+
+    @Schema(description = "商品名称", example = "yutou")
+    private String name;
+
+    @Schema(description = "商品编码", example = "yudaoyuanma")
+    private String code;
+
+    @Schema(description = "分类编号", example = "1")
+    private Long categoryId;
+
+    @Schema(description = "商品品牌编号", example = "1")
+    private Long brandId;
+
+    @Schema(description = "上下架状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "销量最小值", example = "1")
+    private Integer salesCountMin;
+
+    @Schema(description = "销量最大值", example = "1024")
+    private Integer salesCountMax;
+
+    @Schema(description = "市场价最小值", example = "1")
+    private Integer marketPriceMin;
+
+    @Schema(description = "市场价最大值", example = "1024")
+    private Integer marketPriceMax;
+
+    @Schema(description = "是否库存告警", example = "true")
+    private Boolean alarmStock;
+
+}

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

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 商品 SPU Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuRespVO extends ProductSpuBaseVO {
+
+    @Schema(description = "主键", required = true, example = "1")
+    private Long id;
+
+    @Schema(description = "创建时间")
+    private LocalDateTime createTime;
+
+    // ========== SKU 相关字段 =========
+
+    @Schema(description = "库存", required = true, example = "true")
+    private Integer totalStock;
+
+    @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
+    private Integer minPrice;
+
+    @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
+    private Integer maxPrice;
+
+    @Schema(description = "商品销量", example = "1024")
+    private Integer salesCount;
+
+    // ========== 统计相关字段 =========
+
+    @Schema(description = "点击量", example = "1024")
+    private Integer clickCount;
+}

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

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品 SPU 精简 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuSimpleRespVO extends ProductSpuBaseVO {
+
+    @Schema(description = "主键", required = true, example = "1")
+    private Long id;
+
+    @Schema(description = "商品名称", required = true, example = "芋道")
+    private String name;
+
+    @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
+    private Integer minPrice;
+
+    @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
+    private Integer maxPrice;
+
+}

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

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SPU 更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuUpdateReqVO extends ProductSpuBaseVO {
+
+    @Schema(description = "商品编号", required = true, example = "1")
+    @NotNull(message = "商品编号不能为空")
+    private Long id;
+
+    /**
+     * SKU 数组
+     */
+    @Valid
+    private List<ProductSkuCreateOrUpdateReqVO> skus;
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/AppCategoryController.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.controller.app.category;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.product.controller.app.category.vo.AppCategoryRespVO;
+import cn.iocoder.yudao.module.product.convert.category.ProductCategoryConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
+import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 APP - 商品分类")
+@RestController
+@RequestMapping("/product/category")
+@Validated
+public class AppCategoryController {
+
+    @Resource
+    private ProductCategoryService categoryService;
+
+    @GetMapping("/list")
+    @Operation(summary = "获得商品分类列表")
+    public CommonResult<List<AppCategoryRespVO>> getProductCategoryList() {
+        List<ProductCategoryDO> list = categoryService.getEnableCategoryList();
+        list.sort(Comparator.comparing(ProductCategoryDO::getSort));
+        return success(ProductCategoryConvert.INSTANCE.convertList03(list));
+    }
+
+}

+ 28 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/vo/AppCategoryRespVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.product.controller.app.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+@Schema(description = "用户 APP - 商品分类 Response VO")
+public class AppCategoryRespVO {
+
+    @Schema(description = "分类编号", required = true, example = "2")
+    private Long id;
+
+    @Schema(description = "父分类编号", required = true, example = "1")
+    @NotNull(message = "父分类编号不能为空")
+    private Long parentId;
+
+    @Schema(description = "分类名称", required = true, example = "办公文具")
+    @NotBlank(message = "分类名称不能为空")
+    private String name;
+
+    @Schema(description = "分类图片", required = true)
+    @NotBlank(message = "分类图片不能为空")
+    private String picUrl;
+
+}

+ 4 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位符,无时间作用,避免 package 缩进
+ */
+package cn.iocoder.yudao.module.product.controller.app.property;

+ 4 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/property/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位符,无时间作用,避免 package 缩进
+ */
+package cn.iocoder.yudao.module.product.controller.app.property.vo.property;

+ 22 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/value/AppProductPropertyValueDetailRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.app.property.vo.value;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 商品属性值的明细 Response VO")
+@Data
+public class AppProductPropertyValueDetailRespVO {
+
+    @Schema(description = "属性的编号", required = true, example = "1")
+    private Long propertyId;
+
+    @Schema(description = "属性的名称", required = true, example = "颜色")
+    private String propertyName;
+
+    @Schema(description = "属性值的编号", required = true, example = "1024")
+    private Long valueId;
+
+    @Schema(description = "属性值的名称", required = true, example = "红色")
+    private String valueName;
+
+}

+ 8 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http

@@ -0,0 +1,8 @@
+### 获得订单交易的分页 TODO
+GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10
+Authorization: Bearer {{appToken}}
+tenant-id: {{appTenentId}}
+
+### 获得商品 SPU 明细
+GET {{appApi}}/product/spu/get-detail?id=4
+tenant-id: {{appTenentId}}

+ 79 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java

@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.product.controller.app.spu;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_ENABLE;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
+
+@Tag(name = "用户 APP - 商品 SPU")
+@RestController
+@RequestMapping("/product/spu")
+@Validated
+public class AppProductSpuController {
+
+    @Resource
+    private ProductSpuService productSpuService;
+    @Resource
+    private ProductSkuService productSkuService;
+    @Resource
+    private ProductPropertyValueService productPropertyValueService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得商品 SPU 分页")
+    public CommonResult<PageResult<AppProductSpuPageItemRespVO>> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) {
+        PageResult<ProductSpuDO> pageResult = productSpuService.getSpuPage(pageVO, ProductSpuStatusEnum.ENABLE.getStatus());
+        return success(ProductSpuConvert.INSTANCE.convertPage02(pageResult));
+    }
+
+    @GetMapping("/get-detail")
+    @Operation(summary = "获得商品 SPU 明细")
+    @Parameter(name = "id", description = "编号", required = true)
+    public CommonResult<AppProductSpuDetailRespVO> getSpuDetail(@RequestParam("id") Long id) {
+        // 获得商品 SPU
+        ProductSpuDO spu = productSpuService.getSpu(id);
+        if (spu == null) {
+            throw exception(SPU_NOT_EXISTS);
+        }
+        if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
+            throw exception(SPU_NOT_ENABLE);
+        }
+
+        // 查询商品 SKU
+        List<ProductSkuDO> skus = productSkuService.getSkuListBySpuIdAndStatus(spu.getId(),
+                CommonStatusEnum.ENABLE.getStatus());
+        // 查询商品属性
+        List<ProductPropertyValueDetailRespBO> propertyValues = productPropertyValueService
+                .getPropertyValueDetailList(ProductSkuConvert.INSTANCE.convertPropertyValueIds(skus));
+        // 拼接
+        return success(ProductSpuConvert.INSTANCE.convert(spu, skus, propertyValues));
+    }
+
+}

+ 91 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuDetailRespVO.java

@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "用户 App - 商品 SPU 明细 Response VO")
+@Data
+public class AppProductSpuDetailRespVO {
+
+    @Schema(description = "商品 SPU 编号", required = true, example = "1")
+    private Long id;
+
+    // ========== 基本信息 =========
+
+    @Schema(description = "商品名称", required = true, example = "芋道")
+    private String name;
+
+    @Schema(description = "促销语", example = "好吃!")
+    private String sellPoint;
+
+    @Schema(description = "商品详情", required = true, example = "我是商品描述")
+    private String description;
+
+    @Schema(description = "商品分类编号", required = true, example = "1")
+    private Long categoryId;
+
+    @Schema(description = "商品图片的数组", required = true)
+    private List<String> picUrls;
+
+    @Schema(description = "商品视频", required = true)
+    private String videoUrl;
+
+    // ========== SKU 相关字段 =========
+
+    @Schema(description = "规格类型", required = true, example = "1")
+    private Integer specType;
+
+    @Schema(description = "是否展示库存", required = true, example = "true")
+    private Boolean showStock;
+
+    @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
+    private Integer minPrice;
+
+    @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
+    private Integer maxPrice;
+
+    /**
+     * SKU 数组
+     */
+    private List<Sku> skus;
+
+    // ========== 统计相关字段 =========
+
+    @Schema(description = "商品销量", required = true, example = "1024")
+    private Integer salesCount;
+
+    @Schema(description = "用户 App - 商品 SPU 明细的 SKU 信息")
+    @Data
+    public static class Sku {
+
+        @Schema(description = "商品 SKU 编号", example = "1")
+        private Long id;
+
+        /**
+         * 商品属性数组
+         */
+        private List<AppProductPropertyValueDetailRespVO> properties;
+
+        @Schema(description = "销售价格,单位:分", required = true, example = "1024")
+        private Integer price;
+
+        @Schema(description = "市场价", example = "1024")
+        private Integer marketPrice;
+
+        @Schema(description = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
+        private String picUrl;
+
+        @Schema(description = "库存", required = true, example = "1")
+        private Integer stock;
+
+        @Schema(description = "商品重量", example = "1") // 单位:kg 千克
+        private Double weight;
+
+        @Schema(description = "商品体积", example = "1024") // 单位:m^3 平米
+        private Double volume;
+    }
+
+}

+ 39 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "用户 App - 商品 SPU 分页项 Response VO")
+@Data
+public class AppProductSpuPageItemRespVO {
+
+    @Schema(description = "商品 SPU 编号", required = true, example = "1")
+    private Long id;
+
+    @Schema(description = "商品名称", required = true, example = "芋道")
+    @NotEmpty(message = "商品名称不能为空")
+    private String name;
+
+    @Schema(description = "分类编号", required = true)
+    @NotNull(message = "分类编号不能为空")
+    private Long categoryId;
+
+    @Schema(description = "商品图片的数组", required = true)
+    private List<String> picUrls;
+
+    @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
+    private Integer minPrice;
+
+    @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
+    private Integer maxPrice;
+
+    // ========== 统计相关字段 =========
+
+    @Schema(description = "商品销量", example = "1024")
+    private Integer salesCount;
+
+}

+ 43 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.AssertTrue;
+
+@Schema(description = "用户 App - 商品 SPU 分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppProductSpuPageReqVO extends PageParam {
+
+    public static final String SORT_FIELD_PRICE = "price";
+    public static final String SORT_FIELD_SALES_COUNT = "salesCount";
+
+    @Schema(description = "分类编号", example = "1")
+    private Long categoryId;
+
+    @Schema(description = "关键字", example = "好看")
+    private String keyword;
+
+    @Schema(description = "排序字段", example = "price") // 参见 AppSpuPageReqVO.SORT_FIELD_XXX 常量
+    private String sortField;
+
+    @Schema(description = "排序方式", example = "true")
+    private Boolean sortAsc;
+
+    @AssertTrue(message = "排序字段不合法")
+    @JsonIgnore
+    public boolean isSortFieldValid() {
+        if (StrUtil.isEmpty(sortField)) {
+            return true;
+        }
+        return StrUtil.equalsAny(sortField, SORT_FIELD_PRICE, SORT_FIELD_SALES_COUNT);
+    }
+
+}

+ 33 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/brand/ProductBrandConvert.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.product.convert.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandUpdateReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 品牌 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductBrandConvert {
+
+    ProductBrandConvert INSTANCE = Mappers.getMapper(ProductBrandConvert.class);
+
+    ProductBrandDO convert(ProductBrandCreateReqVO bean);
+
+    ProductBrandDO convert(ProductBrandUpdateReqVO bean);
+
+    ProductBrandRespVO convert(ProductBrandDO bean);
+
+    List<ProductBrandRespVO> convertList(List<ProductBrandDO> list);
+
+    PageResult<ProductBrandRespVO> convertPage(PageResult<ProductBrandDO> page);
+
+}

+ 32 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/ProductCategoryConvert.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.product.convert.category;
+
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;
+import cn.iocoder.yudao.module.product.controller.app.category.vo.AppCategoryRespVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 商品分类 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductCategoryConvert {
+
+    ProductCategoryConvert INSTANCE = Mappers.getMapper(ProductCategoryConvert.class);
+
+    ProductCategoryDO convert(ProductCategoryCreateReqVO bean);
+
+    ProductCategoryDO convert(ProductCategoryUpdateReqVO bean);
+
+    ProductCategoryRespVO convert(ProductCategoryDO bean);
+
+    List<ProductCategoryRespVO> convertList(List<ProductCategoryDO> list);
+
+    List<AppCategoryRespVO> convertList03(List<ProductCategoryDO> list);
+}

+ 48 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/property/ProductPropertyConvert.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.product.convert.property;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyAndValueRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyUpdateReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 属性项 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductPropertyConvert {
+
+    ProductPropertyConvert INSTANCE = Mappers.getMapper(ProductPropertyConvert.class);
+
+    ProductPropertyDO convert(ProductPropertyCreateReqVO bean);
+
+    ProductPropertyDO convert(ProductPropertyUpdateReqVO bean);
+
+    ProductPropertyRespVO convert(ProductPropertyDO bean);
+
+    List<ProductPropertyRespVO> convertList(List<ProductPropertyDO> list);
+
+    PageResult<ProductPropertyRespVO> convertPage(PageResult<ProductPropertyDO> page);
+
+    default List<ProductPropertyAndValueRespVO> convertList(List<ProductPropertyDO> keys, List<ProductPropertyValueDO> values) {
+        Map<Long, List<ProductPropertyValueDO>> valueMap = CollectionUtils.convertMultiMap(values, ProductPropertyValueDO::getPropertyId);
+        return CollectionUtils.convertList(keys, key -> {
+            ProductPropertyAndValueRespVO respVO = convert02(key);
+            respVO.setValues(convertList02(valueMap.get(key.getId())));
+            return respVO;
+        });
+    }
+    ProductPropertyAndValueRespVO convert02(ProductPropertyDO bean);
+    List<ProductPropertyAndValueRespVO.Value> convertList02(List<ProductPropertyValueDO> list);
+
+}

+ 55 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/propertyvalue/ProductPropertyValueConvert.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.product.convert.propertyvalue;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 属性值 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductPropertyValueConvert {
+
+    ProductPropertyValueConvert INSTANCE = Mappers.getMapper(ProductPropertyValueConvert.class);
+
+    ProductPropertyValueDO convert(ProductPropertyValueCreateReqVO bean);
+
+    ProductPropertyValueDO convert(ProductPropertyValueUpdateReqVO bean);
+
+    ProductPropertyValueRespVO convert(ProductPropertyValueDO bean);
+
+    List<ProductPropertyValueRespVO> convertList(List<ProductPropertyValueDO> list);
+
+    PageResult<ProductPropertyValueRespVO> convertPage(PageResult<ProductPropertyValueDO> page);
+
+    default List<ProductPropertyValueDetailRespBO> convertList(List<ProductPropertyValueDO> values, List<ProductPropertyDO> keys) {
+        Map<Long, ProductPropertyDO> keyMap = convertMap(keys, ProductPropertyDO::getId);
+        return CollectionUtils.convertList(values, value -> {
+            ProductPropertyValueDetailRespBO valueDetail = new ProductPropertyValueDetailRespBO()
+                    .setValueId(value.getId()).setValueName(value.getName());
+            // 设置属性项
+            MapUtils.findAndThen(keyMap, value.getPropertyId(),
+                    key -> valueDetail.setPropertyId(key.getId()).setPropertyName(key.getName()));
+            return valueDetail;
+        });
+    }
+
+    List<ProductPropertyValueDetailRespDTO> convertList02(List<ProductPropertyValueDetailRespBO> list);
+
+}

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

@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.product.convert.sku;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuDetailRespVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 商品 SKU Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductSkuConvert {
+
+    ProductSkuConvert INSTANCE = Mappers.getMapper(ProductSkuConvert.class);
+
+    ProductSkuDO convert(ProductSkuCreateOrUpdateReqVO bean);
+
+    ProductSkuRespVO convert(ProductSkuDO bean);
+
+    List<ProductSkuRespVO> convertList(List<ProductSkuDO> list);
+
+    List<ProductSkuDO> convertList06(List<ProductSkuCreateOrUpdateReqVO> list);
+
+    default List<ProductSkuDO> convertList06(List<ProductSkuCreateOrUpdateReqVO> list, Long spuId, String spuName) {
+        List<ProductSkuDO> result = convertList06(list);
+        result.forEach(item -> item.setSpuId(spuId).setSpuName(spuName));
+        return result;
+    }
+
+    ProductSkuRespDTO convert02(ProductSkuDO bean);
+
+    List<ProductSpuDetailRespVO.Sku> convertList03(List<ProductSkuDO> list);
+
+    List<ProductSkuRespDTO> convertList04(List<ProductSkuDO> list);
+
+    List<ProductSkuOptionRespVO> convertList05(List<ProductSkuDO> skus);
+
+    /**
+     * 获得 SPU 的库存变化 Map
+     *
+     * @param items SKU 库存变化
+     * @param skus SKU 列表
+     * @return SPU 的库存变化 Map
+     */
+    default Map<Long, Integer> convertSpuStockMap(List<ProductSkuUpdateStockReqDTO.Item> items,
+                                                  List<ProductSkuDO> skus) {
+        Map<Long, Long> skuIdAndSpuIdMap = convertMap(skus, ProductSkuDO::getId, ProductSkuDO::getSpuId); // SKU 与 SKU 编号的 Map 关系
+        Map<Long, Integer> spuIdAndStockMap = new HashMap<>(); // SPU 的库存变化 Map 关系
+        items.forEach(item -> {
+            Long spuId = skuIdAndSpuIdMap.get(item.getId());
+            if (spuId == null) {
+                return;
+            }
+            Integer stock = spuIdAndStockMap.getOrDefault(spuId, 0) + item.getIncrCount();
+            spuIdAndStockMap.put(spuId, stock);
+        });
+        return spuIdAndStockMap;
+    }
+
+    default Collection<Long> convertPropertyValueIds(List<ProductSkuDO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return new HashSet<>();
+        }
+        return list.stream().filter(item -> item.getProperties() != null)
+                .flatMap(p -> p.getProperties().stream()) // 遍历多个 Property 属性
+                .map(ProductSkuDO.Property::getValueId) // 将每个 Property 转换成对应的 propertyId,最后形成集合
+                .collect(Collectors.toSet());
+    }
+
+    default String buildPropertyKey(ProductSkuDO bean) {
+        if (CollUtil.isEmpty(bean.getProperties())) {
+            return StrUtil.EMPTY;
+        }
+        List<ProductSkuDO.Property> properties = new ArrayList<>(bean.getProperties());
+        properties.sort(Comparator.comparing(ProductSkuDO.Property::getValueId));
+        return properties.stream().map(m -> String.valueOf(m.getValueId())).collect(Collectors.joining());
+    }
+
+}

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

@@ -0,0 +1,108 @@
+package cn.iocoder.yudao.module.product.convert.spu;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 商品 SPU Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductSpuConvert {
+
+    ProductSpuConvert INSTANCE = Mappers.getMapper(ProductSpuConvert.class);
+
+    ProductSpuDO convert(ProductSpuCreateReqVO bean);
+
+    ProductSpuDO convert(ProductSpuUpdateReqVO bean);
+
+    List<ProductSpuDO> convertList(List<ProductSpuDO> list);
+
+    PageResult<ProductSpuRespVO> convertPage(PageResult<ProductSpuDO> page);
+
+    ProductSpuPageReqVO convert(AppProductSpuPageReqVO bean);
+
+    List<ProductSpuRespDTO> convertList2(List<ProductSpuDO> list);
+
+    List<ProductSpuSimpleRespVO> convertList02(List<ProductSpuDO> list);
+
+    default AppProductSpuDetailRespVO convert(ProductSpuDO spu, List<ProductSkuDO> skus,
+                                              List<ProductPropertyValueDetailRespBO> propertyValues) {
+        AppProductSpuDetailRespVO spuVO = convert02(spu)
+                .setSalesCount(spu.getSalesCount() + defaultIfNull(spu.getVirtualSalesCount(), 0));
+        spuVO.setSkus(convertList03(skus));
+        // 处理商品属性
+        Map<Long, ProductPropertyValueDetailRespBO> propertyValueMap = convertMap(propertyValues, ProductPropertyValueDetailRespBO::getValueId);
+        for (int i = 0; i < skus.size(); i++) {
+            List<ProductSkuDO.Property> properties = skus.get(i).getProperties();
+            if (CollUtil.isEmpty(properties)) {
+                continue;
+            }
+            AppProductSpuDetailRespVO.Sku sku = spuVO.getSkus().get(i);
+            sku.setProperties(new ArrayList<>(properties.size()));
+            // 遍历每个 properties,设置到 AppSpuDetailRespVO.Sku 中
+            properties.forEach(property -> {
+                ProductPropertyValueDetailRespBO propertyValue = propertyValueMap.get(property.getValueId());
+                if (propertyValue == null) {
+                    return;
+                }
+                sku.getProperties().add(convert03(propertyValue));
+            });
+        }
+        return spuVO;
+    }
+    AppProductSpuDetailRespVO convert02(ProductSpuDO spu);
+    List<AppProductSpuDetailRespVO.Sku> convertList03(List<ProductSkuDO> skus);
+    AppProductPropertyValueDetailRespVO convert03(ProductPropertyValueDetailRespBO propertyValue);
+
+    PageResult<AppProductSpuPageItemRespVO> convertPage02(PageResult<ProductSpuDO> page);
+
+    default ProductSpuDetailRespVO convert03(ProductSpuDO spu, List<ProductSkuDO> skus,
+                                             List<ProductPropertyValueDetailRespBO> propertyValues) {
+        ProductSpuDetailRespVO spuVO = convert03(spu);
+        spuVO.setSkus(convertList04(skus));
+        // 处理商品属性
+        Map<Long, ProductPropertyValueDetailRespBO> propertyValueMap = convertMap(propertyValues, ProductPropertyValueDetailRespBO::getValueId);
+        for (int i = 0; i < skus.size(); i++) {
+            List<ProductSkuDO.Property> properties = skus.get(i).getProperties();
+            if (CollUtil.isEmpty(properties)) {
+                continue;
+            }
+            ProductSpuDetailRespVO.Sku sku = spuVO.getSkus().get(i);
+            sku.setProperties(new ArrayList<>(properties.size()));
+            // 遍历每个 properties,设置到 AppSpuDetailRespVO.Sku 中
+            properties.forEach(property -> {
+                ProductPropertyValueDetailRespBO propertyValue = propertyValueMap.get(property.getValueId());
+                if (propertyValue == null) {
+                    return;
+                }
+                sku.getProperties().add(convert04(propertyValue));
+            });
+        }
+        return spuVO;
+    }
+    ProductSpuDetailRespVO convert03(ProductSpuDO spu);
+    List<ProductSpuDetailRespVO.Sku> convertList04(List<ProductSkuDO> skus);
+    ProductPropertyValueDetailRespVO convert04(ProductPropertyValueDetailRespBO propertyValue);
+
+}

+ 53 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java

@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.brand;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品品牌 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_brand")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductBrandDO extends BaseDO {
+
+    /**
+     * 品牌编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 品牌名称
+     */
+    private String name;
+    /**
+     * 品牌图片
+     */
+    private String picUrl;
+    /**
+     * 品牌排序
+     */
+    private Integer sort;
+    /**
+     * 品牌描述
+     */
+    private String description;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+    // TODO 芋艿:firstLetter 首字母
+
+}

+ 67 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/ProductCategoryDO.java

@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.category;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品分类 DO
+ *
+ * 商品分类一共两类:
+ * 1)一级分类:{@link #parentId} 等于 0
+ * 2)二级 + 三级分类:{@link #parentId} 不等于 0
+ *
+ * @author 芋道源码
+ */
+@TableName("product_category")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductCategoryDO extends BaseDO {
+
+    /**
+     * 父分类编号 - 根分类
+     */
+    public static final Long PARENT_ID_NULL = 0L;
+
+    /**
+     * 分类编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 父分类编号
+     */
+    private Long parentId;
+    /**
+     * 分类名称
+     */
+    private String name;
+    /**
+     * 分类图片
+     *
+     * 一级分类:推荐 200 x 100 分辨率
+     * 二级 + 三级分类:推荐 100 x 100 分辨率
+     */
+    private String picUrl;
+    /**
+     * 分类排序
+     */
+    private Integer sort;
+    /**
+     * 分类描述
+     */
+    private String description;
+    /**
+     * 开启状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+}

+ 129 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/comment/ProductCommentDO.java

@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.comment;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.enums.comment.ProductCommentAuditStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 商品评论 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_comment")
+@KeySequence("product_comment_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductCommentDO extends BaseDO {
+
+    /**
+     * 评论编号,主键自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 商品 SPU 编号
+     *
+     * 关联 {@link ProductSpuDO#getId()}
+     */
+    private Long spuId;
+    /**
+     * 交易订单编号
+     *
+     * 关联 TradeOrderDO 的 id 编号
+     */
+    private Long orderId;
+    /**
+     * 交易订单项编号
+     *
+     * 关联 TradeOrderItemDO 的 id 编号
+     */
+    private Long orderItemId;
+    /**
+     * 审核状态
+     *
+     * 枚举 {@link ProductCommentAuditStatusEnum}
+     */
+    private Integer auditStatus;
+
+    /**
+     * 用户编号
+     *
+     * 关联 MemberUserDO 的 id 编号
+     */
+    private Long userId;
+    /**
+     * 用户 IP
+     */
+    private String userIp;
+    /**
+     * 是否匿名
+     */
+    private Boolean anonymous;
+    /**
+     * 评论内容
+     */
+    private String content;
+    /**
+     * 评论图片地址数组
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<String> picUrls;
+    /**
+     * 描述相符星级
+     *
+     * 1-5 星
+     */
+    private Integer descriptionScore;
+    /**
+     * 商品评论星级
+     *
+     * 1-5 星
+     */
+    private Integer productScore;
+    /**
+     * 服务评论星级
+     *
+     * 1-5 星
+     */
+    private Integer serviceScore;
+    /**
+     * 物流评论星级
+     *
+     * 1-5 星
+     */
+    private Integer expressComment;
+
+    /**
+     * 商家是否回复
+     */
+    private Boolean replied;
+    /**
+     * 商家回复内容
+     */
+    private String replyContent;
+    /**
+     * 商家回复时间
+     */
+    private LocalDateTime replyTime;
+
+    /**
+     * 有用的计数
+     *
+     * 其他用户看到评论时,可点击「有用」按钮
+     */
+    private Integer usefulCount;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/delivery/DeliveryTemplateDO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.delivery;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 配送模板 SPU DO
+ *
+ * @author 芋道源码
+ */
+@TableName("delivery_template")
+@KeySequence("delivery_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeliveryTemplateDO extends BaseDO {
+
+    /**
+     * 编号,自增
+     */
+    @TableId
+    private Long id;
+
+}

+ 45 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.favorite;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品收藏 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_favorite")
+@KeySequence("product_favorite_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductFavoriteDO extends BaseDO {
+
+    /**
+     * 编号,主键自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 用户编号
+     *
+     * 关联 MemberUserDO 的 id 编号
+     */
+    private Long userId;
+    /**
+     * 商品 SPU 编号
+     *
+     * 关联 {@link ProductSpuDO#getId()}
+     */
+    private Long spuId;
+
+    // TODO 芋艿:type 1 收藏;2 点赞
+
+}

+ 43 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupBindDO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.group;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品分组的绑定 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_group_bind")
+@KeySequence("product_group_bind_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductGroupBindDO extends BaseDO {
+
+    /**
+     * 编号,自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 商品分组编号
+     *
+     * 关联 {@link ProductGroupDO#getId()}
+     */
+    private Long groupId;
+    /**
+     * 商品 SPU 编号
+     *
+     * 关联 {@link ProductSpuDO#getId()}
+     */
+    private Long spuId;
+
+}

+ 63 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupDO.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.group;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.enums.group.ProductGroupStyleEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品分组 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_group")
+@KeySequence("product_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductGroupDO extends BaseDO {
+
+    /**
+     * 商品分组编号,自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 分组名称
+     */
+    private String name;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 商品数量
+     */
+    private Integer count;
+    /**
+     * 排序
+     */
+    private Integer sort;
+    /**
+     * 风格,用于 APP 首页展示商品的样式
+     *
+     * 枚举 {@link ProductGroupStyleEnum}
+     */
+    private Integer style;
+    /**
+     * 是否默认
+     *
+     * true - 系统默认,不允许删除
+     * false - 自定义,允许删除
+     */
+    private Boolean defaulted;
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyDO.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.property;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品属性项 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_property")
+@KeySequence("product_property_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductPropertyDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 名称
+     */
+    private String name;
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 46 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyValueDO.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.property;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+
+/**
+ * 商品属性值 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_property_value")
+@KeySequence("product_property_value_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductPropertyValueDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 属性项的编号
+     *
+     * 关联 {@link ProductPropertyDO#getId()}
+     */
+    private Long propertyId;
+    /**
+     * 名称
+     */
+    private String name;
+    /**
+     * 备注
+     *
+     */
+    private String remark;
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/search/ProductHotSearchDO.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.search;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品热搜关键字 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_hot_search")
+@KeySequence("product_hot_search_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductHotSearchDO extends BaseDO {
+
+    /**
+     * 编号,主键自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 关键字
+     */
+    private String name;
+    /**
+     * 内容
+     */
+    private String content;
+
+}

+ 26 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/shop/ShopDO.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.shop;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+// TODO 芋艿:待设计
+/**
+ * 店铺 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("shop")
+@KeySequence("shop_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ShopDO extends BaseDO {
+
+    private Long id;
+
+}

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

@@ -0,0 +1,137 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.sku;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * 商品 SKU DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "product_sku",autoResultMap = true)
+@KeySequence("product_sku_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductSkuDO extends BaseDO {
+
+    /**
+     * 商品 SKU 编号,自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * SPU 编号
+     * <p>
+     * 关联 {@link ProductSpuDO#getId()}
+     */
+    private Long spuId;
+    /**
+     * SPU 名字
+     *
+     * 冗余 {@link ProductSkuDO#getSpuName()}
+     */
+    private String spuName;
+    /**
+     * 属性数组,JSON 格式
+     */
+    @TableField(typeHandler = PropertyTypeHandler.class)
+    private List<Property> properties;
+    /**
+     * 销售价格,单位:分
+     */
+    private Integer price;
+    /**
+     * 市场价,单位:分
+     */
+    private Integer marketPrice;
+    /**
+     * 成本价,单位:分
+     */
+    private Integer costPrice;
+    /**
+     * SKU 的条形码
+     */
+    private String barCode;
+    /**
+     * 图片地址
+     */
+    private String picUrl;
+    /**
+     * SKU 状态
+     * <p>
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 库存
+     */
+    private Integer stock;
+    /**
+     * 预警预存
+     */
+    private Integer warnStock;
+    /**
+     * 商品重量,单位:kg 千克
+     */
+    private Double weight;
+    /**
+     * 商品体积,单位:m^3 平米
+     */
+    private Double volume;
+
+    /**
+     * 商品属性
+     */
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Property {
+
+        /**
+         * 属性编号
+         * <p>
+         * 关联 {@link ProductPropertyDO#getId()}
+         */
+        private Long propertyId;
+        /**
+         * 属性值编号
+         * <p>
+         * 关联 {@link ProductPropertyValueDO#getId()}
+         */
+        private Long valueId;
+
+    }
+
+    // TODO @芋艿:可以找一些新的思路
+    public static class PropertyTypeHandler extends AbstractJsonTypeHandler<Object> {
+
+        @Override
+        protected Object parse(String json) {
+            return JsonUtils.parseArray(json, Property.class);
+        }
+
+        @Override
+        protected String toJson(Object obj) {
+            return JsonUtils.toJsonString(obj);
+        }
+
+    }
+
+}
+

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

@@ -0,0 +1,212 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.spu;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * 商品 SPU DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "product_spu", autoResultMap = true)
+@KeySequence("product_spu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductSpuDO extends BaseDO {
+
+    /**
+     * 商品 SPU 编号,自增
+     */
+    @TableId
+    private Long id;
+
+    // ========== 基本信息 =========
+
+    /**
+     * 商品名称
+     */
+    private String name;
+    /**
+     * 商品编码
+     */
+    private String code;
+    /**
+     * 促销语
+     */
+    private String sellPoint;
+    /**
+     * 商品详情
+     */
+    private String description;
+    /**
+     * 商品分类编号
+     *
+     * 关联 {@link ProductCategoryDO#getId()}
+     */
+    private Long categoryId;
+    /**
+     * 商品品牌编号
+     *
+     * 关联 {@link ProductBrandDO#getId()}
+     */
+    private Long brandId;
+    /**
+     * 商品图片的数组
+     *
+     * 1. 第一张图片将作为商品主图,支持同时上传多张图;
+     * 2. 建议使用尺寸 800x800 像素以上、大小不超过 1M 的正方形图片;
+     * 3. 至少 1 张,最多上传 10 张
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<String> picUrls;
+    /**
+     * 商品视频
+     */
+    private String videoUrl;
+
+    /**
+     * 排序字段
+     */
+    private Integer sort;
+    /**
+     * 商品状态
+     *
+     * 枚举 {@link ProductSpuStatusEnum}
+     */
+    private Integer status;
+
+    // ========== SKU 相关字段 =========
+
+    /**
+     * 规格类型
+     *
+     * 枚举 {@link ProductSpuSpecTypeEnum}
+     */
+    private Integer specType;
+    /**
+     * 最小价格,单位使用:分
+     *
+     * 基于其对应的 {@link ProductSkuDO#getPrice()} 最小值
+     */
+    private Integer minPrice;
+    /**
+     * 最大价格,单位使用:分
+     *
+     * 基于其对应的 {@link ProductSkuDO#getPrice()} 最大值
+     */
+    private Integer maxPrice;
+    /**
+     * 市场价,单位使用:分
+     *
+     * 基于其对应的 {@link ProductSkuDO#getMarketPrice()} 最大值
+     */
+    private Integer marketPrice;
+    /**
+     * 总库存
+     *
+     * 基于其对应的 {@link ProductSkuDO#getStock()} 求和
+     */
+    private Integer totalStock;
+    /**
+     * 是否展示库存
+     */
+    private Boolean showStock;
+
+    // ========== 统计相关字段 =========
+
+    /**
+     * 商品销量
+     */
+    private Integer salesCount;
+    /**
+     * 虚拟销量
+     */
+    private Integer virtualSalesCount;
+    /**
+     * 商品点击量
+     */
+    private Integer clickCount;
+
+    // ========== 物流相关字段 =========
+
+    // TODO 芋艿:稍后完善物流的字段
+//    /**
+//     * 配送方式
+//     *
+//     * 枚举 {@link DeliveryModeEnum}
+//     */
+//    private Integer deliveryMode;
+//    /**
+//     * 配置模板编号
+//     *
+//     * 关联 {@link DeliveryTemplateDO#getId()}
+//     */
+//    private Long deliveryTemplateId;
+
+    // TODO ========== 待定字段:yv =========
+    // TODO vip_price 会员价格
+    // TODO postage 邮费
+    // TODO is_postage 是否包邮
+    // TODO unit_name 单位
+    // TODO is_new 商户是否代理
+    // TODO give_integral 获得积分
+    // TODO is_integral 是开启积分兑换
+    // TODO integral 所需积分
+    // TODO is_seckill 秒杀状态
+    // TODO is_bargain 砍价状态
+    // TODO code_path 产品二维码地址
+    // TODO is_sub 是否分佣
+
+    // TODO ↓↓ 芋艿 ↓↓ 看起来走分组更合适?
+    // TODO is_hot 是否热卖
+    // TODO is_benefit 是否优惠
+    // TODO is_best 是否精品
+    // TODO is_new 是否新品
+    // TODO is_good 是否优品推荐
+
+    // TODO ========== 待定字段:cf =========
+    // TODO source_link 淘宝京东1688类型
+    // TODO activity 活动显示排序 0=默认 1=秒 2=砍价 3=拼团
+
+    // TODO ========== 待定字段:lf =========
+
+    // TODO free_shipping_type:运费类型:1-包邮;2-统一运费;3-运费模板
+    // TODO free_shipping:统一运费金额
+    // TODO free_shipping_template_id:运费模板
+    // TODO is_commission:分销佣金:1-开启;0-不开启;first_ratio second_ratio three_ratio
+    // TODO is_share_bouns:区域股东分红:1-开启;0-不开启;region_ratio;shareholder_ratio
+
+    // TODO is_new:新品推荐:1-是;0-否
+    // TODO is_best:好物优选:1-是;0-否
+    // TODO is_like:猜你喜欢:1-是;0-否
+
+    // TODO is_team:是否开启拼团[0=否, 1=是]
+    // TODO is_integral:积分抵扣:1-开启;0-不开启
+    // TODO is_member:会员价:1-开启;0-不开启
+    // TODO give_integral_type:赠送积分类型:0-不赠送;1-赠送固定积分;2-按比例赠送积分
+    // TODO give_integral:赠送积分;
+
+    // TODO poster:商品自定义海报
+
+    // TODO ========== 待定字段:laoji =========
+    // TODO productType 1 - 普通商品 2 - 预售商品;可能和 type 合并不错
+    // TODO productUnit 商品单位
+    // TODO extJson 扩展信息;例如说,预售商品的信息
+
+}

+ 34 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/brand/ProductBrandMapper.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.product.dal.mysql.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandListReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandPageReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ProductBrandMapper extends BaseMapperX<ProductBrandDO> {
+
+    default PageResult<ProductBrandDO> selectPage(ProductBrandPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ProductBrandDO>()
+                .likeIfPresent(ProductBrandDO::getName, reqVO.getName())
+                .eqIfPresent(ProductBrandDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(ProductBrandDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(ProductBrandDO::getId));
+    }
+
+
+    default List<ProductBrandDO> selectList(ProductBrandListReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<ProductBrandDO>()
+                .likeIfPresent(ProductBrandDO::getName, reqVO.getName()));
+    }
+
+    default ProductBrandDO selectByName(String name) {
+        return selectOne(ProductBrandDO::getName, name);
+    }
+
+}

+ 33 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/ProductCategoryMapper.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.product.dal.mysql.category;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 商品分类 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductCategoryMapper extends BaseMapperX<ProductCategoryDO> {
+
+    default List<ProductCategoryDO> selectList(ProductCategoryListReqVO listReqVO) {
+        return selectList(new LambdaQueryWrapperX<ProductCategoryDO>()
+                .likeIfPresent(ProductCategoryDO::getName, listReqVO.getName())
+                .orderByDesc(ProductCategoryDO::getId));
+    }
+
+    default Long selectCountByParentId(Long parentId) {
+        return selectCount(ProductCategoryDO::getParentId, parentId);
+    }
+
+    default List<ProductCategoryDO> selectListByStatus(Integer status) {
+        return selectList(ProductCategoryDO::getStatus, status);
+    }
+
+}

+ 32 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyMapper.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.product.dal.mysql.property;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyListReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyPageReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ProductPropertyMapper extends BaseMapperX<ProductPropertyDO> {
+
+    default PageResult<ProductPropertyDO> selectPage(ProductPropertyPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ProductPropertyDO>()
+                .likeIfPresent(ProductPropertyDO::getName, reqVO.getName())
+                .betweenIfPresent(ProductPropertyDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(ProductPropertyDO::getId));
+    }
+
+    default ProductPropertyDO selectByName(String name) {
+        return selectOne(ProductPropertyDO::getName, name);
+    }
+
+    default List<ProductPropertyDO> selectList(ProductPropertyListReqVO listReqVO) {
+        return selectList(new LambdaQueryWrapperX<ProductPropertyDO>()
+                .eqIfPresent(ProductPropertyDO::getName, listReqVO.getName()));
+    }
+
+}

+ 43 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyValueMapper.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.product.dal.mysql.property;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+@Mapper
+public interface ProductPropertyValueMapper extends BaseMapperX<ProductPropertyValueDO> {
+
+    default List<ProductPropertyValueDO> selectListByPropertyId(Collection<Long> propertyIds) {
+        return selectList(new LambdaQueryWrapperX<ProductPropertyValueDO>()
+                .inIfPresent(ProductPropertyValueDO::getPropertyId, propertyIds));
+    }
+
+    default ProductPropertyValueDO selectByName(Long propertyId, String name) {
+        return selectOne(new LambdaQueryWrapperX<ProductPropertyValueDO>()
+                .eq(ProductPropertyValueDO::getPropertyId, propertyId)
+                .eq(ProductPropertyValueDO::getName, name));
+    }
+
+    default void deleteByPropertyId(Long propertyId) {
+        delete(new LambdaQueryWrapperX<ProductPropertyValueDO>()
+                .eq(ProductPropertyValueDO::getPropertyId, propertyId));
+    }
+
+    default PageResult<ProductPropertyValueDO> selectPage(ProductPropertyValuePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ProductPropertyValueDO>()
+                .eqIfPresent(ProductPropertyValueDO::getPropertyId, reqVO.getPropertyId())
+                .likeIfPresent(ProductPropertyValueDO::getName, reqVO.getName())
+                .orderByDesc(ProductPropertyValueDO::getId));
+    }
+
+    default Integer selectCountByPropertyId(Long propertyId) {
+        return selectCount(ProductPropertyValueDO::getPropertyId, propertyId).intValue();
+    }
+
+}

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

@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.product.dal.mysql.sku;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 商品 SKU Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> {
+
+    default List<ProductSkuDO> selectListBySpuId(Long spuId) {
+        return selectList(ProductSkuDO::getSpuId, spuId);
+    }
+
+    default List<ProductSkuDO> selectListBySpuIdAndStatus(Long spuId,
+                                                          Integer status) {
+        return selectList(new LambdaQueryWrapperX<ProductSkuDO>()
+                .eq(ProductSkuDO::getSpuId, spuId)
+                .eqIfPresent(ProductSkuDO::getStatus, status));
+    }
+
+    default List<ProductSkuDO> selectListBySpuId(Collection<Long> spuIds) {
+        return selectList(ProductSkuDO::getSpuId, spuIds);
+    }
+
+    default void deleteBySpuId(Long spuId) {
+        delete(new LambdaQueryWrapperX<ProductSkuDO>().eq(ProductSkuDO::getSpuId, spuId));
+    }
+
+    /**
+     * 更新 SKU 库存(增加)
+     *
+     * @param id 编号
+     * @param incrCount 增加库存(正数)
+     */
+    default void updateStockIncr(Long id, Integer incrCount) {
+        Assert.isTrue(incrCount > 0);
+        LambdaUpdateWrapper<ProductSkuDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()
+                .setSql(" stock = stock + " + incrCount)
+                .eq(ProductSkuDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新 SKU 库存(减少)
+     *
+     * @param id 编号
+     * @param incrCount 减少库存(负数)
+     * @return 更新条数
+     */
+    default int updateStockDecr(Long id, Integer incrCount) {
+        Assert.isTrue(incrCount < 0);
+        LambdaUpdateWrapper<ProductSkuDO> updateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()
+                .setSql(" stock = stock + " + incrCount) // 负数,所以使用 + 号
+                .eq(ProductSkuDO::getId, id)
+                .ge(ProductSkuDO::getStock, -incrCount); // cas 逻辑
+        return update(null, updateWrapper);
+    }
+
+    default List<ProductSkuDO> selectListByAlarmStock(){
+       return selectList(new QueryWrapper<ProductSkuDO>().apply("stock <= warn_stock"));
+    }
+
+}

Неке датотеке нису приказане због велике количине промена