getSpu(@RequestParam("id") Long id);
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java
new file mode 100644
index 000000000..078c20d6e
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java
@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.product.api.spu.dto;
+
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import lombok.Data;
+
+// TODO @LeeYan9: ProductSpuRespDTO
+
+/**
+ * 商品 SPU 信息 Response DTO
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Data
+public class ProductSpuRespDTO {
+
+ /**
+ * 商品 SPU 编号,自增
+ */
+ private Long id;
+
+ // ========== 基本信息 =========
+
+ /**
+ * 商品名称
+ */
+ private String name;
+ /**
+ * 单位
+ *
+ * 对应 product_unit 数据字典
+ */
+ private Integer unit;
+
+ /**
+ * 商品分类编号
+ */
+ private Long categoryId;
+ /**
+ * 商品封面图
+ */
+ private String picUrl;
+
+ /**
+ * 商品状态
+ *
+ * 枚举 {@link ProductSpuStatusEnum}
+ */
+ private Integer status;
+
+ // ========== SKU 相关字段 =========
+
+ /**
+ * 规格类型
+ *
+ * false - 单规格
+ * true - 多规格
+ */
+ private Boolean specType;
+ /**
+ * 商品价格,单位使用:分
+ */
+ private Integer price;
+ /**
+ * 市场价,单位使用:分
+ */
+ private Integer marketPrice;
+ /**
+ * 成本价,单位使用:分
+ */
+ private Integer costPrice;
+ /**
+ * 库存
+ */
+ private Integer stock;
+
+ // ========== 物流相关字段 =========
+
+ /**
+ * 物流配置模板编号
+ *
+ * 对应 TradeDeliveryExpressTemplateDO 的 id 编号
+ */
+ private Long deliveryTemplateId;
+
+ // ========== 营销相关字段 =========
+
+ /**
+ * 赠送积分
+ */
+ private Integer giveIntegral;
+
+ // ========== 分销相关字段 =========
+
+ /**
+ * 分销类型
+ *
+ * false - 默认
+ * true - 自行设置
+ */
+ private Boolean subCommissionType;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ApiConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ApiConstants.java
new file mode 100644
index 000000000..1846e69da
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ApiConstants.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.product.enums;
+
+import cn.iocoder.yudao.framework.common.enums.RpcConstants;
+
+/**
+ * API 相关的枚举
+ *
+ * @author 芋道源码
+ */
+public class ApiConstants {
+
+ /**
+ * 服务名
+ *
+ * 注意,需要保证和 spring.application.name 保持一致
+ */
+ public static final String NAME = "product-server";
+
+ public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/product";
+
+ public static final String VERSION = "1.0.0";
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/DictTypeConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/DictTypeConstants.java
new file mode 100644
index 000000000..85725a18e
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/DictTypeConstants.java
@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.product.enums;
+
+/**
+ * product 字典类型的枚举类
+ *
+ * @author HUIHUI
+ */
+public interface DictTypeConstants {
+
+ String PRODUCT_UNIT = "product_unit"; // 商品单位
+ String PRODUCT_SPU_STATUS = "product_spu_status"; // 商品 SPU 状态
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
new file mode 100644
index 000000000..1d0ea189f
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.product.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * Product 错误码枚举类
+ *
+ * product 系统,使用 1-008-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 商品分类相关 1-008-001-000 ============
+ ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_008_001_000, "商品分类不存在");
+ ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1_008_001_001, "父分类不存在");
+ ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_008_001_002, "父分类不能是二级分类");
+ ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_008_001_003, "存在子分类,无法删除");
+ ErrorCode CATEGORY_DISABLED = new ErrorCode(1_008_001_004, "商品分类({})已禁用,无法使用");
+ ErrorCode CATEGORY_HAVE_BIND_SPU = new ErrorCode(1_008_001_005, "类别下存在商品,无法删除");
+
+ // ========== 商品品牌相关编号 1-008-002-000 ==========
+ ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1_008_002_000, "品牌不存在");
+ ErrorCode BRAND_DISABLED = new ErrorCode(1_008_002_001, "品牌已禁用");
+ ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1_008_002_002, "品牌名称已存在");
+
+ // ========== 商品属性项 1-008-003-000 ==========
+ ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1_008_003_000, "属性项不存在");
+ ErrorCode PROPERTY_EXISTS = new ErrorCode(1_008_003_001, "属性项的名称已存在");
+ ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1_008_003_002, "属性项下存在属性值,无法删除");
+
+ // ========== 商品属性值 1-008-004-000 ==========
+ ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1_008_004_000, "属性值不存在");
+ ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1_008_004_001, "属性值的名称已存在");
+
+ // ========== 商品 SPU 1-008-005-000 ==========
+ ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在");
+ ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
+ ErrorCode SPU_SAVE_FAIL_COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_005_002, "商品 SPU 保存失败,原因:优惠卷不存在");
+ ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_003, "商品 SPU【{}】不处于上架状态");
+ ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_004, "商品 SPU 不处于回收站状态");
+
+ // ========== 商品 SKU 1-008-006-000 ==========
+ ErrorCode SKU_NOT_EXISTS = new ErrorCode(1_008_006_000, "商品 SKU 不存在");
+ ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1_008_006_001, "商品 SKU 的属性组合存在重复");
+ ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1_008_006_002, "一个 SPU 下的每个 SKU,其属性项必须一致");
+ ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1_008_006_003, "一个 SPU 下的每个 SKU,必须不重复");
+ ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1_008_006_004, "商品 SKU 库存不足");
+
+ // ========== 商品 评价 1-008-007-000 ==========
+ ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1_008_007_000, "商品评价不存在");
+ ErrorCode COMMENT_ORDER_EXISTS = new ErrorCode(1_008_007_001, "订单的商品评价已存在");
+
+ // ========== 商品 收藏 1-008-008-000 ==========
+ ErrorCode FAVORITE_EXISTS = new ErrorCode(1_008_008_000, "该商品已经被收藏");
+ ErrorCode FAVORITE_NOT_EXISTS = new ErrorCode(1_008_008_001, "商品收藏不存在");
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ProductConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ProductConstants.java
new file mode 100644
index 000000000..f3570c589
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ProductConstants.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.product.enums;
+
+/**
+ * Product 常量
+ *
+ * @author HUIHUI
+ */
+public interface ProductConstants {
+
+ /**
+ * 警戒库存 TODO 警戒库存暂时为 10,后期需要使用常量或者数据库配置替换
+ */
+ int ALERT_STOCK = 10;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentAuditStatusEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentAuditStatusEnum.java
new file mode 100644
index 000000000..276839daf
--- /dev/null
+++ b/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;
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentScoresEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentScoresEnum.java
new file mode 100644
index 000000000..a114e1ab8
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentScoresEnum.java
@@ -0,0 +1,41 @@
+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 wangzhs
+ */
+@Getter
+@AllArgsConstructor
+public enum ProductCommentScoresEnum implements IntArrayValuable {
+
+ ONE(1, "1星"),
+ TWO(2, "2星"),
+ THREE(3, "3星"),
+ FOUR(4, "4星"),
+ FIVE(5, "5星");
+
+ public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductCommentScoresEnum::getScores).toArray();
+
+ /**
+ * 星级
+ */
+ private final Integer scores;
+
+ /**
+ * 星级名
+ */
+ private final String name;
+
+ @Override
+ public int[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuStatusEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuStatusEnum.java
new file mode 100644
index 000000000..4ba6124e0
--- /dev/null
+++ b/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);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/Dockerfile b/yudao-module-mall/yudao-module-product-biz/Dockerfile
new file mode 100644
index 000000000..c375d0d56
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/Dockerfile
@@ -0,0 +1,19 @@
+## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
+## 感谢复旦核博士的建议!灰子哥,牛皮!
+FROM eclipse-temurin:8-jre
+
+## 创建目录,并使用它作为工作目录
+RUN mkdir -p /yudao-module-product-biz
+WORKDIR /yudao-module-product-biz
+## 将后端项目的 Jar 文件,复制到镜像中
+COPY ./target/yudao-module-product-biz.jar app.jar
+
+## 设置 TZ 时区
+## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
+ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
+
+## 暴露后端项目的 48080 端口
+EXPOSE 48100
+
+## 启动后端项目
+CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
diff --git a/yudao-module-mall/yudao-module-product-biz/pom.xml b/yudao-module-mall/yudao-module-product-biz/pom.xml
new file mode 100644
index 000000000..eb720dbeb
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/pom.xml
@@ -0,0 +1,107 @@
+
+
+
+ cn.iocoder.cloud
+ yudao-module-mall
+ ${revision}
+
+ 4.0.0
+ yudao-module-product-biz
+ jar
+
+ ${project.artifactId}
+
+ product 模块,主要实现商品相关功能
+ 例如:品牌、商品分类、spu、sku等功能。
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-env
+
+
+
+
+ cn.iocoder.cloud
+ yudao-module-product-api
+ ${revision}
+
+
+ cn.iocoder.cloud
+ yudao-module-member-api
+ ${revision}
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-operatelog
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-dict
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-web
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-security
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mybatis
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-rpc
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-test
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-excel
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-monitor
+
+
+
+
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/ProductServerApplication.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/ProductServerApplication.java
new file mode 100644
index 000000000..651481ba0
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/ProductServerApplication.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * 项目的启动类
+ *
+ * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ *
+ * @author 芋道源码
+ */
+@SpringBootApplication
+public class ProductServerApplication {
+
+ public static void main(String[] args) {
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+
+ SpringApplication.run(ProductServerApplication.class, args);
+
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java
new file mode 100644
index 000000000..c80b0fa8a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.product.api.category;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 商品分类 API 接口实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class ProductCategoryApiImpl implements ProductCategoryApi {
+
+ @Resource
+ private ProductCategoryService productCategoryService;
+
+ @Override
+ public CommonResult validateCategoryList(Collection ids) {
+ productCategoryService.validateCategoryList(ids);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApiImpl.java
new file mode 100644
index 000000000..2383b8fbe
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApiImpl.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.api.comment;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
+import cn.iocoder.yudao.module.product.service.comment.ProductCommentService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 商品评论 API 实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+@Validated
+public class ProductCommentApiImpl implements ProductCommentApi {
+
+ @Resource
+ private ProductCommentService productCommentService;
+
+ @Override
+ public CommonResult createComment(ProductCommentCreateReqDTO createReqDTO) {
+ return success(productCommentService.createComment(createReqDTO));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java
new file mode 100644
index 000000000..162453c3c
--- /dev/null
+++ b/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;
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
new file mode 100644
index 000000000..603a536d9
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.product.api.sku;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 商品 SKU API 实现类
+ *
+ * @author LeeYan9
+ * @since 2022-09-06
+ */
+@Service
+@Validated
+public class ProductSkuApiImpl implements ProductSkuApi {
+
+ @Resource
+ private ProductSkuService productSkuService;
+
+ @Override
+ public CommonResult getSku(Long id) {
+ ProductSkuDO sku = productSkuService.getSku(id);
+ return success(ProductSkuConvert.INSTANCE.convert02(sku));
+ }
+
+ @Override
+ public CommonResult> getSkuList(Collection ids) {
+ List skus = productSkuService.getSkuList(ids);
+ return success(ProductSkuConvert.INSTANCE.convertList04(skus));
+ }
+
+ @Override
+ public CommonResult> getSkuListBySpuId(Collection spuIds) {
+ List skus = productSkuService.getSkuListBySpuId(spuIds);
+ return success(ProductSkuConvert.INSTANCE.convertList04(skus));
+ }
+
+ @Override
+ public CommonResult updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
+ productSkuService.updateSkuStock(updateStockReqDTO);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
new file mode 100644
index 000000000..3d2950070
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.product.api.spu;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.service.spu.ProductSpuService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 商品 SPU API 接口实现类
+ *
+ * @author LeeYan9
+ * @since 2022-09-06
+ */
+@Service
+@Validated
+public class ProductSpuApiImpl implements ProductSpuApi {
+
+ @Resource
+ private ProductSpuService spuService;
+
+ @Override
+ public CommonResult> getSpuList(Collection ids) {
+ return success(ProductSpuConvert.INSTANCE.convertList2(spuService.getSpuList(ids)));
+ }
+
+ @Override
+ public CommonResult> validateSpuList(Collection ids) {
+ return success(ProductSpuConvert.INSTANCE.convertList2(spuService.validateSpuList(ids)));
+ }
+
+ @Override
+ public CommonResult getSpu(Long id) {
+ return success(ProductSpuConvert.INSTANCE.convert02(spuService.getSpu(id)));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/ProductBrandController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/ProductBrandController.java
new file mode 100644
index 000000000..a7c954124
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/ProductBrandController.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand;
+
+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.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 createBrand(@Valid @RequestBody ProductBrandCreateReqVO createReqVO) {
+ return success(brandService.createBrand(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新品牌")
+ @PreAuthorize("@ss.hasPermission('product:brand:update')")
+ public CommonResult 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 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 getBrand(@RequestParam("id") Long id) {
+ ProductBrandDO brand = brandService.getBrand(id);
+ return success(ProductBrandConvert.INSTANCE.convert(brand));
+ }
+
+ @GetMapping("/list-all-simple")
+ @Operation(summary = "获取品牌精简信息列表", description = "主要用于前端的下拉选项")
+ public CommonResult> getSimpleBrandList() {
+ // 获取品牌列表,只要开启状态的
+ List list = brandService.getBrandListByStatus(CommonStatusEnum.ENABLE.getStatus());
+ // 排序后,返回给前端
+ return success(ProductBrandConvert.INSTANCE.convertList1(list));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得品牌分页")
+ @PreAuthorize("@ss.hasPermission('product:brand:query')")
+ public CommonResult> getBrandPage(@Valid ProductBrandPageReqVO pageVO) {
+ PageResult pageResult = brandService.getBrandPage(pageVO);
+ return success(ProductBrandConvert.INSTANCE.convertPage(pageResult));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得品牌列表")
+ @PreAuthorize("@ss.hasPermission('product:brand:query')")
+ public CommonResult> getBrandList(@Valid ProductBrandListReqVO listVO) {
+ List list = brandService.getBrandList(listVO);
+ list.sort(Comparator.comparing(ProductBrandDO::getSort));
+ return success(ProductBrandConvert.INSTANCE.convertList(list));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandBaseVO.java
new file mode 100644
index 000000000..a148d52de
--- /dev/null
+++ b/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 = "品牌名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "苹果")
+ @NotNull(message = "品牌名称不能为空")
+ private String name;
+
+ @Schema(description = "品牌图片", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "品牌图片不能为空")
+ private String picUrl;
+
+ @Schema(description = "品牌排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "品牌排序不能为空")
+ private Integer sort;
+
+ @Schema(description = "品牌描述", example = "描述")
+ private String description;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "状态不能为空")
+ private Integer status;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandCreateReqVO.java
new file mode 100644
index 000000000..dc85a476b
--- /dev/null
+++ b/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 {
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandListReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandListReqVO.java
new file mode 100644
index 000000000..ed93ff090
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandPageReqVO.java
new file mode 100644
index 000000000..3a6efc93f
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandRespVO.java
new file mode 100644
index 000000000..486fe764b
--- /dev/null
+++ b/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 = "品牌编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandSimpleRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandSimpleRespVO.java
new file mode 100644
index 000000000..6379a5fb1
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandSimpleRespVO.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.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Schema(description = "管理后台 - 品牌精简信息 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductBrandSimpleRespVO {
+
+ @Schema(description = "品牌编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "品牌名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "苹果")
+ private String name;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandUpdateReqVO.java
new file mode 100644
index 000000000..a39a6830d
--- /dev/null
+++ b/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 = "品牌编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "品牌编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/ProductCategoryController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/ProductCategoryController.java
new file mode 100644
index 000000000..dc3a57a38
--- /dev/null
+++ b/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 createCategory(@Valid @RequestBody ProductCategoryCreateReqVO createReqVO) {
+ return success(categoryService.createCategory(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新商品分类")
+ @PreAuthorize("@ss.hasPermission('product:category:update')")
+ public CommonResult 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 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 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> getCategoryList(@Valid ProductCategoryListReqVO treeListReqVO) {
+ List list = categoryService.getEnableCategoryList(treeListReqVO);
+ list.sort(Comparator.comparing(ProductCategoryDO::getSort));
+ return success(ProductCategoryConvert.INSTANCE.convertList(list));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryBaseVO.java
new file mode 100644
index 000000000..5a8363167
--- /dev/null
+++ b/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 = "父分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "父分类编号不能为空")
+ private Long parentId;
+
+ @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "办公文具")
+ @NotBlank(message = "分类名称不能为空")
+ private String name;
+
+ @Schema(description = "移动端分类图", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotBlank(message = "移动端分类图不能为空")
+ private String picUrl;
+
+ @Schema(description = "PC 端分类图")
+ private String bigPicUrl;
+
+ @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer sort;
+
+ @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "开启状态不能为空")
+ private Integer status;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryCreateReqVO.java
new file mode 100644
index 000000000..cd02ddbd0
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryCreateReqVO.java
@@ -0,0 +1,19 @@
+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.NotBlank;
+
+@Schema(description = "管理后台 - 商品分类创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductCategoryCreateReqVO extends ProductCategoryBaseVO {
+
+ @Schema(description = "分类描述", example = "描述")
+ private String description;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java
new file mode 100644
index 000000000..16f5df857
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java
@@ -0,0 +1,19 @@
+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;
+
+ @Schema(description = "开启状态", example = "0")
+ private Integer status;
+
+ @Schema(description = "父分类编号", example = "1")
+ private Long parentId;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryRespVO.java
new file mode 100644
index 000000000..8f46ff60f
--- /dev/null
+++ b/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 = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java
new file mode 100644
index 000000000..ffcdc3f5c
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java
@@ -0,0 +1,24 @@
+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.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 商品分类更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductCategoryUpdateReqVO extends ProductCategoryBaseVO {
+
+ @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "分类编号不能为空")
+ private Long id;
+
+ @Schema(description = "分类描述", example = "描述")
+ private String description;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.http b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.http
new file mode 100644
index 000000000..e69de29bb
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java
new file mode 100644
index 000000000..70e95cd9a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.product.controller.admin.comment;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.comment.vo.*;
+import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
+import cn.iocoder.yudao.module.product.service.comment.ProductCommentService;
+import io.swagger.v3.oas.annotations.Operation;
+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;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - 商品评价")
+@RestController
+@RequestMapping("/product/comment")
+@Validated
+public class ProductCommentController {
+
+ @Resource
+ private ProductCommentService productCommentService;
+
+ @GetMapping("/page")
+ @Operation(summary = "获得商品评价分页")
+ @PreAuthorize("@ss.hasPermission('product:comment:query')")
+ public CommonResult> getCommentPage(@Valid ProductCommentPageReqVO pageVO) {
+ PageResult pageResult = productCommentService.getCommentPage(pageVO);
+ return success(ProductCommentConvert.INSTANCE.convertPage(pageResult));
+ }
+
+ @PutMapping("/update-visible")
+ @Operation(summary = "显示 / 隐藏评论")
+ @PreAuthorize("@ss.hasPermission('product:comment:update')")
+ public CommonResult updateCommentVisible(@Valid @RequestBody ProductCommentUpdateVisibleReqVO updateReqVO) {
+ productCommentService.updateCommentVisible(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/reply")
+ @Operation(summary = "商家回复")
+ @PreAuthorize("@ss.hasPermission('product:comment:update')")
+ public CommonResult commentReply(@Valid @RequestBody ProductCommentReplyReqVO replyVO) {
+ productCommentService.replyComment(replyVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PostMapping("/create")
+ @Operation(summary = "添加自评")
+ @PreAuthorize("@ss.hasPermission('product:comment:update')")
+ public CommonResult createComment(@Valid @RequestBody ProductCommentCreateReqVO createReqVO) {
+ productCommentService.createComment(createReqVO);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentBaseVO.java
new file mode 100644
index 000000000..f88f5ed68
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentBaseVO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.product.controller.admin.comment.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+@Data
+public class ProductCommentBaseVO {
+
+ @Schema(description = "评价人", requiredMode = Schema.RequiredMode.REQUIRED, example = "16868")
+ private Long userId;
+
+ @Schema(description = "评价订单项", requiredMode = Schema.RequiredMode.REQUIRED, example = "19292")
+ private Long orderItemId;
+
+ @Schema(description = "评价人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小姑凉")
+ @NotNull(message = "评价人名称不能为空")
+ private String userNickname;
+
+ @Schema(description = "评价人头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
+ @NotNull(message = "评价人头像不能为空")
+ private String userAvatar;
+
+ @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "商品 SKU 编号不能为空")
+ private Long skuId;
+
+ @Schema(description = "描述星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ @NotNull(message = "描述星级不能为空")
+ private Integer descriptionScores;
+
+ @Schema(description = "服务星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ @NotNull(message = "服务星级分不能为空")
+ private Integer benefitScores;
+
+ @Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "穿起来非常丝滑凉快")
+ @NotNull(message = "评论内容不能为空")
+ private String content;
+
+ @Schema(description = "评论图片地址数组,以逗号分隔最多上传 9 张", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/xx.png]")
+ @Size(max = 9, message = "评论图片地址数组长度不能超过 9 张")
+ private List picUrls;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentCreateReqVO.java
new file mode 100644
index 000000000..f976b756d
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentCreateReqVO.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.comment.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 ProductCommentCreateReqVO extends ProductCommentBaseVO {
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentPageReqVO.java
new file mode 100644
index 000000000..3791f572e
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentPageReqVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.controller.admin.comment.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum;
+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 ProductCommentPageReqVO extends PageParam {
+
+ @Schema(description = "评价人名称", example = "王二狗")
+ private String userNickname;
+
+ @Schema(description = "交易订单编号", example = "24428")
+ private Long orderId;
+
+ @Schema(description = "商品SPU编号", example = "29502")
+ private Long spuId;
+
+ @Schema(description = "商品SPU名称", example = "感冒药")
+ private String spuName;
+
+ @Schema(description = "评分星级 1-5 分", example = "5")
+ @InEnum(ProductCommentScoresEnum.class)
+ private Integer scores;
+
+ @Schema(description = "商家是否回复", example = "true")
+ private Boolean replyStatus;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentReplyReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentReplyReqVO.java
new file mode 100644
index 000000000..97ad39d7a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentReplyReqVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.product.controller.admin.comment.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 商品评价的商家回复 Request VO")
+@Data
+@ToString(callSuper = true)
+public class ProductCommentReplyReqVO {
+
+ @Schema(description = "评价编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15721")
+ @NotNull(message = "评价编号不能为空")
+ private Long id;
+
+ @Schema(description = "商家回复内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "谢谢亲")
+ @NotEmpty(message = "商家回复内容不能为空")
+ private String replyContent;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentRespVO.java
new file mode 100644
index 000000000..d03359f7f
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentRespVO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.product.controller.admin.comment.vo;
+
+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 javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品评价 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductCommentRespVO extends ProductCommentBaseVO {
+
+ @Schema(description = "订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24965")
+ private Long id;
+
+ @Schema(description = "是否匿名", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+ private Boolean anonymous;
+
+ @Schema(description = "交易订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24428")
+ private Long orderId;
+
+ @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean visible;
+
+ @Schema(description = "商家是否回复", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Boolean replyStatus;
+
+ @Schema(description = "回复管理员编号", example = "9527")
+ private Long replyUserId;
+
+ @Schema(description = "商家回复内容", example = "感谢好评哦亲(づ ̄3 ̄)づ╭❤~")
+ private String replyContent;
+
+ @Schema(description = "商家回复时间", example = "2023-08-08 12:20:55")
+ private LocalDateTime replyTime;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ @Schema(description = "评分星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ private Integer scores;
+
+ @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉丝滑透气小短袖")
+ @NotNull(message = "商品 SPU 编号不能为空")
+ private Long spuId;
+
+ @Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+ @NotNull(message = "商品 SPU 名称不能为空")
+ private String spuName;
+
+ @Schema(description = "商品 SKU 图片地址", example = "https://www.iocoder.cn/yudao.jpg")
+ private String skuPicUrl;
+
+ @Schema(description = "商品 SKU 规格值数组")
+ private List skuProperties;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentUpdateVisibleReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentUpdateVisibleReqVO.java
new file mode 100644
index 000000000..c88fbaf34
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentUpdateVisibleReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.admin.comment.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 商品评价可见修改 Request VO")
+@Data
+@ToString(callSuper = true)
+public class ProductCommentUpdateVisibleReqVO {
+
+ @Schema(description = "评价编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15721")
+ @NotNull(message = "评价编号不能为空")
+ private Long id;
+
+ @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+ @NotNull(message = "是否可见不能为空")
+ private Boolean visible;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java
new file mode 100644
index 000000000..831319ed5
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java
@@ -0,0 +1,100 @@
+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 createProperty(@Valid @RequestBody ProductPropertyCreateReqVO createReqVO) {
+ return success(productPropertyService.createProperty(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新属性项")
+ @PreAuthorize("@ss.hasPermission('product:property:update')")
+ public CommonResult 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 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 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> getPropertyList(@Valid ProductPropertyListReqVO listReqVO) {
+ return success(ProductPropertyConvert.INSTANCE.convertList(productPropertyService.getPropertyList(listReqVO)));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得属性项分页")
+ @PreAuthorize("@ss.hasPermission('product:property:query')")
+ public CommonResult> getPropertyPage(@Valid ProductPropertyPageReqVO pageVO) {
+ return success(ProductPropertyConvert.INSTANCE.convertPage(productPropertyService.getPropertyPage(pageVO)));
+ }
+
+ @PostMapping("/get-value-list")
+ @Operation(summary = "获得属性项列表")
+ @PreAuthorize("@ss.hasPermission('product:property:query')")
+ public CommonResult> getPropertyAndValueList(
+ @Valid @RequestBody ProductPropertyListReqVO listReqVO) {
+ // 查询属性项
+ List keys = productPropertyService.getPropertyList(listReqVO);
+ if (CollUtil.isEmpty(keys)) {
+ return success(Collections.emptyList());
+ }
+ // 查询属性值
+ List values = productPropertyValueService.getPropertyValueListByPropertyId(
+ convertSet(keys, ProductPropertyDO::getId));
+ return success(ProductPropertyConvert.INSTANCE.convertList(keys, values));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java
new file mode 100644
index 000000000..54ce881d1
--- /dev/null
+++ b/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.property.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 createPropertyValue(@Valid @RequestBody ProductPropertyValueCreateReqVO createReqVO) {
+ return success(productPropertyValueService.createPropertyValue(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新属性值")
+ @PreAuthorize("@ss.hasPermission('product:property:update')")
+ public CommonResult updatePropertyValue(@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 deletePropertyValue(@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 getPropertyValue(@RequestParam("id") Long id) {
+ return success(ProductPropertyValueConvert.INSTANCE.convert(productPropertyValueService.getPropertyValue(id)));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得属性值分页")
+ @PreAuthorize("@ss.hasPermission('product:property:query')")
+ public CommonResult> getPropertyValuePage(@Valid ProductPropertyValuePageReqVO pageVO) {
+ return success(ProductPropertyValueConvert.INSTANCE.convertPage(productPropertyValueService.getPropertyValuePage(pageVO)));
+ }
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java
new file mode 100644
index 000000000..6ef051451
--- /dev/null
+++ b/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 = "属性项的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "属性项的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "颜色")
+ private String name;
+
+ /**
+ * 属性值的集合
+ */
+ private List values;
+
+ @Schema(description = "管理后台 - 属性值的简单 Response VO")
+ @Data
+ public static class Value {
+
+ @Schema(description = "属性值的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+ private Long id;
+
+ @Schema(description = "属性值的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "红色")
+ private String name;
+
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java
new file mode 100644
index 000000000..0006bd7b3
--- /dev/null
+++ b/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 = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "颜色")
+ @NotBlank(message = "名称不能为空")
+ private String name;
+
+ @Schema(description = "备注", example = "颜色")
+ private String remark;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java
new file mode 100644
index 000000000..b854dd73c
--- /dev/null
+++ b/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 {
+
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java
new file mode 100644
index 000000000..3ff46484f
--- /dev/null
+++ b/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,17 @@
+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;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - 属性项 List Request VO")
+@Data
+@ToString(callSuper = true)
+public class ProductPropertyListReqVO {
+
+ @Schema(description = "属性名称", example = "颜色")
+ private String name;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java
new file mode 100644
index 000000000..97b959d6a
--- /dev/null
+++ b/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 = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @Schema(description = "创建时间")
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java
new file mode 100644
index 000000000..5f541230a
--- /dev/null
+++ b/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 = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java
new file mode 100644
index 000000000..9319f7cf2
--- /dev/null
+++ b/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 = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "主键不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java
new file mode 100644
index 000000000..d46b66b3d
--- /dev/null
+++ b/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 = "属性项的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "属性项的编号不能为空")
+ private Long propertyId;
+
+ @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "红色")
+ @NotEmpty(message = "名称名字不能为空")
+ private String name;
+
+ @Schema(description = "备注", example = "颜色")
+ private String remark;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java
new file mode 100644
index 000000000..d3fe4d0f1
--- /dev/null
+++ b/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 {
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueDetailRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueDetailRespVO.java
new file mode 100644
index 000000000..4d22f0dbf
--- /dev/null
+++ b/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 = "属性的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long propertyId;
+
+ @Schema(description = "属性的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "颜色")
+ private String propertyName;
+
+ @Schema(description = "属性值的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long valueId;
+
+ @Schema(description = "属性值的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "红色")
+ private String valueName;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java
new file mode 100644
index 000000000..ff0c32614
--- /dev/null
+++ b/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 = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java
new file mode 100644
index 000000000..6ef17c32e
--- /dev/null
+++ b/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 = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Long id;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java
new file mode 100644
index 000000000..d0e0d9382
--- /dev/null
+++ b/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 = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "主键不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java
new file mode 100755
index 000000000..9acbacd66
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "管理后台 - 商品 SKU")
+@RestController
+@RequestMapping("/product/sku")
+@Validated
+public class ProductSkuController {
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java
new file mode 100755
index 000000000..8f47e9392
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java
@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+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;
+import java.util.List;
+
+/**
+* 商品 SKU Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class ProductSkuBaseVO {
+
+ @Schema(description = "商品 SKU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉小短袖")
+ @NotEmpty(message = "商品 SKU 名字不能为空")
+ private String name;
+
+ @Schema(description = "销售价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1999")
+ @NotNull(message = "销售价格,单位:分不能为空")
+ private Integer price;
+
+ @Schema(description = "市场价", example = "2999")
+ private Integer marketPrice;
+
+ @Schema(description = "成本价", example = "19")
+ private Integer costPrice;
+
+ @Schema(description = "条形码", example = "15156165456")
+ private String barCode;
+
+ @Schema(description = "图片地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
+ @NotNull(message = "图片地址不能为空")
+ private String picUrl;
+
+ @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
+ @NotNull(message = "库存不能为空")
+ private Integer stock;
+
+ @Schema(description = "预警预存", example = "10")
+ private Integer warnStock;
+
+ @Schema(description = "商品重量,单位:kg 千克", example = "1.2")
+ private Double weight;
+
+ @Schema(description = "商品体积,单位:m^3 平米", example = "2.5")
+ private Double volume;
+
+ @Schema(description = "一级分销的佣金,单位:分", example = "199")
+ private Integer firstBrokeragePrice;
+
+ @Schema(description = "二级分销的佣金,单位:分", example = "19")
+ private Integer secondBrokeragePrice;
+
+ @Schema(description = "属性数组")
+ private List properties;
+
+ @Schema(description = "商品属性")
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class Property {
+
+ @Schema(description = "属性编号", example = "10")
+ private Long propertyId;
+
+ @Schema(description = "属性名字", example = "颜色")
+ private String propertyName;
+
+ @Schema(description = "属性值编号", example = "10")
+ private Long valueId;
+
+ @Schema(description = "属性值名字", example = "红色")
+ private String valueName;
+
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java
new file mode 100755
index 000000000..e750013d5
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java
@@ -0,0 +1,16 @@
+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 {
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java
new file mode 100755
index 000000000..42d72e00e
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SKU Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSkuRespVO extends ProductSkuBaseVO {
+
+ @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.http b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.http
new file mode 100644
index 000000000..4ab7b4f71
--- /dev/null
+++ b/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}}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java
new file mode 100755
index 000000000..10c1a9ca2
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java
@@ -0,0 +1,147 @@
+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.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+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.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+//import cn.iocoder.yudao.module.promotion.api.coupon.CouponTemplateApi;
+//import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO;
+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.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+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.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+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 CouponTemplateApi couponTemplateApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建商品 SPU")
+ @PreAuthorize("@ss.hasPermission('product:spu:create')")
+ public CommonResult createProductSpu(@Valid @RequestBody ProductSpuCreateReqVO createReqVO) {
+ return success(productSpuService.createSpu(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新商品 SPU")
+ @PreAuthorize("@ss.hasPermission('product:spu:update')")
+ public CommonResult updateSpu(@Valid @RequestBody ProductSpuUpdateReqVO updateReqVO) {
+ productSpuService.updateSpu(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-status")
+ @Operation(summary = "更新商品 SPU Status")
+ @PreAuthorize("@ss.hasPermission('product:spu:update')")
+ public CommonResult updateStatus(@Valid @RequestBody ProductSpuUpdateStatusReqVO updateReqVO) {
+ productSpuService.updateSpuStatus(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 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 getSpuDetail(@RequestParam("id") Long id) {
+ // 获得商品 SPU
+ ProductSpuDO spu = productSpuService.getSpu(id);
+ if (spu == null) {
+ throw exception(SPU_NOT_EXISTS);
+ }
+ // 查询商品 SKU
+ List skus = productSkuService.getSkuListBySpuId(spu.getId());
+ // 查询优惠卷
+ // TODO @puhui999:优惠劵的信息,要不交给前端读取?主要是为了避免商品依赖 promotion 模块哈;
+// List couponTemplateList = couponTemplateApi.getCouponTemplateListByIds(
+// spu.getGiveCouponTemplateIds());
+ return success(ProductSpuConvert.INSTANCE.convertForSpuDetailRespVO(spu, skus));
+ }
+
+ @GetMapping("/list-all-simple")
+ @Operation(summary = "获得商品 SPU 精简列表")
+ @PreAuthorize("@ss.hasPermission('product:spu:query')")
+ public CommonResult> getSpuSimpleList() {
+ List list = productSpuService.getSpuListByStatus(ProductSpuStatusEnum.ENABLE.getStatus());
+ // 降序排序后,返回给前端
+ list.sort(Comparator.comparing(ProductSpuDO::getSort).reversed());
+ return success(ProductSpuConvert.INSTANCE.convertList02(list));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得商品 SPU 详情列表")
+ @Parameter(name = "spuIds", description = "spu 编号列表", required = true, example = "[1,2,3]")
+ @PreAuthorize("@ss.hasPermission('product:spu:query')")
+ public CommonResult> getSpuList(@RequestParam("spuIds") Collection spuIds) {
+ return success(ProductSpuConvert.INSTANCE.convertForSpuDetailRespListVO(
+ productSpuService.getSpuList(spuIds), productSkuService.getSkuListBySpuId(spuIds)));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得商品 SPU 分页")
+ @PreAuthorize("@ss.hasPermission('product:spu:query')")
+ public CommonResult> getSpuPage(@Valid ProductSpuPageReqVO pageVO) {
+ return success(ProductSpuConvert.INSTANCE.convertPage(productSpuService.getSpuPage(pageVO)));
+ }
+
+ @GetMapping("/get-count")
+ @Operation(summary = "获得商品 SPU 分页 tab count")
+ @PreAuthorize("@ss.hasPermission('product:spu:query')")
+ public CommonResult