picUrls;
+ /**
+ * 商品视频
+ */
+ private String videoUrl;
+
+ /**
+ * 排序字段
+ */
+ private Integer sort;
+ /**
+ * 商品状态
+ *
+ * 枚举 {@link ProductSpuStatusEnum}
+ */
+ private Integer status;
+
+ // ========== SKU 相关字段 =========
+
+ /**
+ * 规格类型
+ *
+ * 枚举 {@link ProductSpuSpecTypeEnum}
+ */
+ private Integer specType;
+ /**
+ * 最小价格,单位使用:分
+ *
+ * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最小值
+ */
+ private Integer minPrice;
+ /**
+ * 最大价格,单位使用:分
+ *
+ * 基于其对应的 {@link ProductSkuRespDTO#getPrice()} 最大值
+ */
+ private Integer maxPrice;
+ /**
+ * 市场价,单位使用:分
+ *
+ * 基于其对应的 {@link ProductSkuRespDTO#getMarketPrice()} 最大值
+ */
+ private Integer marketPrice;
+ /**
+ * 总库存
+ *
+ * 基于其对应的 {@link ProductSkuRespDTO#getStock()} 求和
+ */
+ private Integer totalStock;
+ /**
+ * 是否展示库存
+ */
+ private Boolean showStock;
+
+ // ========== 统计相关字段 =========
+
+ /**
+ * 商品销量
+ */
+ private Integer salesCount;
+ /**
+ * 虚拟销量
+ */
+ private Integer virtualSalesCount;
+ /**
+ * 商品点击量
+ */
+ private Integer clickCount;
+
+}
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..4adad0afc
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * Product 错误码枚举类
+ *
+ * product 系统,使用 1-008-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 商品分类相关 1008001000 ============
+ ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1008001000, "商品分类不存在");
+ ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1008001001, "父分类不存在");
+ ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, "父分类不能是二级分类");
+ ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, "存在子分类,无法删除");
+ ErrorCode CATEGORY_DISABLED = new ErrorCode(1008001004, "商品分类({})已禁用,无法使用");
+
+ // ========== 商品品牌相关编号 1008002000 ==========
+ ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1008002000, "品牌不存在");
+ ErrorCode BRAND_DISABLED = new ErrorCode(1008002001, "品牌不存在");
+ ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1008002002, "品牌名称已存在");
+
+ // ========== 商品属性项 1008003000 ==========
+ ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1008003000, "属性项不存在");
+ ErrorCode PROPERTY_EXISTS = new ErrorCode(1008003001, "属性项的名称已存在");
+ ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1008003002, "属性项下存在属性值,无法删除");
+
+ // ========== 商品属性值 1008004000 ==========
+ ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "属性值不存在");
+ ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1008004001, "属性值的名称已存在");
+
+ // ========== 商品 SPU 1008005000 ==========
+ ErrorCode SPU_NOT_EXISTS = new ErrorCode(1008005000, "商品 SPU 不存在");
+ ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1008005001, "商品分类不正确,原因:必须使用第三级的商品分类下");
+ ErrorCode SPU_NOT_ENABLE = new ErrorCode(1008005002, "商品 SPU 不处于上架状态");
+
+ // ========== 商品 SKU 1008006000 ==========
+ ErrorCode SKU_NOT_EXISTS = new ErrorCode(1008006000, "商品 SKU 不存在");
+ ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复");
+ ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU,其属性项必须一致");
+ ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU,必须不重复");
+ ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");
+
+}
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/delivery/DeliveryTypeEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/delivery/DeliveryTypeEnum.java
new file mode 100644
index 000000000..da322ff24
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/delivery/DeliveryTypeEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.enums.delivery;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 配送方式枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum DeliveryTypeEnum implements IntArrayValuable {
+
+ // TODO 芋艿:英文单词,需要再想下;
+ EXPRESS(1, "快递发货"),
+ USER(2, "用户自提"),;
+
+ public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DeliveryTypeEnum::getMode).toArray();
+
+ /**
+ * 配送方式
+ */
+ private final Integer mode;
+ /**
+ * 状态名
+ */
+ private final String name;
+
+ @Override
+ public int[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/group/ProductGroupStyleEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/group/ProductGroupStyleEnum.java
new file mode 100644
index 000000000..c5e55e8e4
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/group/ProductGroupStyleEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.enums.group;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 商品分组的样式枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ProductGroupStyleEnum implements IntArrayValuable {
+
+ ONE(1, "每列一个"),
+ TWO(2, "每列两个"),
+ THREE(2, "每列三个"),;
+
+ public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductGroupStyleEnum::getStyle).toArray();
+
+ /**
+ * 列表样式
+ */
+ private final Integer style;
+ /**
+ * 状态名
+ */
+ private final String name;
+
+ @Override
+ public int[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuSpecTypeEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuSpecTypeEnum.java
new file mode 100644
index 000000000..fbc227b53
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuSpecTypeEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.product.enums.spu;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 商品 SPU 规格类型
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum ProductSpuSpecTypeEnum implements IntArrayValuable {
+
+ RECYCLE(1, "统一规格"),
+ DISABLE(2, "多规格");
+
+ public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductSpuSpecTypeEnum::getType).toArray();
+
+ /**
+ * 规格类型
+ */
+ private final Integer type;
+ /**
+ * 规格名称
+ */
+ private final String name;
+
+ @Override
+ public int[] array() {
+ return ARRAYS;
+ }
+
+}
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..2223cf23d
--- /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/pom.xml b/yudao-module-mall/yudao-module-product-biz/pom.xml
new file mode 100644
index 000000000..e89ed105e
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+ cn.iocoder.boot
+ yudao-module-mall
+ ${revision}
+
+ 4.0.0
+ yudao-module-product-biz
+ jar
+
+ ${project.artifactId}
+
+ product 模块,主要实现商品相关功能
+ 例如:品牌、商品分类、spu、sku等功能。
+
+
+
+
+ cn.iocoder.boot
+ yudao-module-product-api
+ ${revision}
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-operatelog
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-web
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-security
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-mybatis
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-test
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-excel
+
+
+
+
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/property/ProductPropertyValueApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApiImpl.java
new file mode 100644
index 000000000..9aab9e560
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApiImpl.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.product.api.property;
+
+import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
+import cn.iocoder.yudao.module.product.convert.propertyvalue.ProductPropertyValueConvert;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 商品属性值 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class ProductPropertyValueApiImpl implements ProductPropertyValueApi {
+
+ @Resource
+ private ProductPropertyValueService productPropertyValueService;
+
+ @Override
+ public List getPropertyValueDetailList(Collection ids) {
+ return ProductPropertyValueConvert.INSTANCE.convertList02(
+ productPropertyValueService.getPropertyValueDetailList(ids));
+ }
+
+}
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..89913c70e
--- /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,49 @@
+package cn.iocoder.yudao.module.product.api.sku;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * TODO LeeYan9: 类注释;
+ * @author LeeYan9
+ * @since 2022-09-06
+ */
+@Service
+@Validated
+public class ProductSkuApiImpl implements ProductSkuApi {
+
+ @Resource
+ private ProductSkuService productSkuService;
+
+ @Override
+ public ProductSkuRespDTO getSku(Long id) {
+ // TODO TODO LeeYan9: 需要实现
+ return null;
+ }
+
+ @Override
+ public List getSkuList(Collection ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyList();
+ }
+ List skus = productSkuService.getSkuList(ids);
+ return ProductSkuConvert.INSTANCE.convertList04(skus);
+ }
+
+ @Override
+ public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
+ productSkuService.updateSkuStock(updateStockReqDTO);
+ }
+
+}
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..4d880e662
--- /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,38 @@
+package cn.iocoder.yudao.module.product.api.spu;
+
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * TODO LeeYan9: 类注释;
+ *
+ * @author LeeYan9
+ * @since 2022-09-06
+ */
+@Service
+@Validated
+public class ProductSpuApiImpl implements ProductSpuApi {
+
+ @Resource
+ private ProductSpuMapper productSpuMapper;
+
+ @Override
+ public List getSpuList(Collection spuIds) {
+ // TODO TODO LeeYan9: AllEmpty?
+ if (CollectionUtils.isAnyEmpty(spuIds)) {
+ return Collections.emptyList();
+ }
+ List productSpuDOList = productSpuMapper.selectBatchIds(spuIds);
+ return ProductSpuConvert.INSTANCE.convertList2(productSpuDOList);
+ }
+}
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..0e6f7f3bc
--- /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,82 @@
+package cn.iocoder.yudao.module.product.controller.admin.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.*;
+import cn.iocoder.yudao.module.product.convert.brand.ProductBrandConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO;
+import cn.iocoder.yudao.module.product.service.brand.ProductBrandService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 商品品牌")
+@RestController
+@RequestMapping("/product/brand")
+@Validated
+public class ProductBrandController {
+
+ @Resource
+ private ProductBrandService brandService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建品牌")
+ @PreAuthorize("@ss.hasPermission('product:brand:create')")
+ public CommonResult 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("/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..e8561fe07
--- /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 = "品牌名称", required = true, example = "芋道")
+ @NotNull(message = "品牌名称不能为空")
+ private String name;
+
+ @Schema(description = "品牌图片", required = true)
+ @NotNull(message = "品牌图片不能为空")
+ private String picUrl;
+
+ @Schema(description = "品牌排序", required = true, example = "1")
+ @NotNull(message = "品牌排序不能为空")
+ private Integer sort;
+
+ @Schema(description = "品牌描述", example = "描述")
+ private String description;
+
+ @Schema(description = "状态", required = true, example = "0")
+ @NotNull(message = "状态不能为空")
+ private Integer status;
+
+}
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..63305810c
--- /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..81d470b1b
--- /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..b68fbc5be
--- /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 = "品牌编号", required = true, example = "1")
+ private Long id;
+
+ @Schema(description = "创建时间", required = true)
+ 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/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..c01c3c4b7
--- /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 = "品牌编号", required = true, 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..5182bdadd
--- /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 = "父分类编号", required = true, example = "1")
+ @NotNull(message = "父分类编号不能为空")
+ private Long parentId;
+
+ @Schema(description = "分类名称", required = true, example = "办公文具")
+ @NotBlank(message = "分类名称不能为空")
+ private String name;
+
+ @Schema(description = "分类图片", required = true)
+ @NotBlank(message = "分类图片不能为空")
+ private String picUrl;
+
+ @Schema(description = "分类排序", required = true, example = "1")
+ private Integer sort;
+
+ @Schema(description = "分类描述", required = true, example = "描述")
+ private String description;
+
+ @Schema(description = "开启状态", required = true, example = "0")
+ @NotNull(message = "开启状态不能为空")
+ private Integer status;
+
+}
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..f9b559776
--- /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,14 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品分类创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductCategoryCreateReqVO extends ProductCategoryBaseVO {
+
+}
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..9c9439d3e
--- /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,13 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 商品分类列表查询 Request VO")
+@Data
+public class ProductCategoryListReqVO {
+
+ @Schema(description = "分类名称", example = "办公文具")
+ private String name;
+
+}
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..484e0e51e
--- /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 = "分类编号", required = true, example = "2")
+ private Long id;
+
+ @Schema(description = "创建时间", required = true)
+ 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..15f663761
--- /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,20 @@
+package cn.iocoder.yudao.module.product.controller.admin.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 商品分类更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductCategoryUpdateReqVO extends ProductCategoryBaseVO {
+
+ @Schema(description = "分类编号", required = true, example = "2")
+ @NotNull(message = "分类编号不能为空")
+ private Long id;
+
+}
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..bd063e0ec
--- /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,99 @@
+package cn.iocoder.yudao.module.product.controller.admin.property;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.*;
+import cn.iocoder.yudao.module.product.convert.property.ProductPropertyConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - 商品属性项")
+@RestController
+@RequestMapping("/product/property")
+@Validated
+public class ProductPropertyController {
+
+ @Resource
+ private ProductPropertyService productPropertyService;
+ @Resource
+ private ProductPropertyValueService productPropertyValueService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建属性项")
+ @PreAuthorize("@ss.hasPermission('product:property:create')")
+ public CommonResult 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)));
+ }
+
+ @GetMapping("/get-value-list")
+ @Operation(summary = "获得属性项列表")
+ @PreAuthorize("@ss.hasPermission('product:property:query')")
+ public CommonResult> getPropertyAndValueList(@Valid 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..92ce6bee0
--- /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.propertyvalue.ProductPropertyValueConvert;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 商品属性值")
+@RestController
+@RequestMapping("/product/property/value")
+@Validated
+public class ProductPropertyValueController {
+
+ @Resource
+ private ProductPropertyValueService productPropertyValueService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建属性值")
+ @PreAuthorize("@ss.hasPermission('product:property:create')")
+ public CommonResult createProperty(@Valid @RequestBody ProductPropertyValueCreateReqVO createReqVO) {
+ return success(productPropertyValueService.createPropertyValue(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新属性值")
+ @PreAuthorize("@ss.hasPermission('product:property:update')")
+ public CommonResult updateProperty(@Valid @RequestBody ProductPropertyValueUpdateReqVO updateReqVO) {
+ productPropertyValueService.updatePropertyValue(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除属性值")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('product:property:delete')")
+ public CommonResult deleteProperty(@RequestParam("id") Long id) {
+ productPropertyValueService.deletePropertyValue(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得属性值")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('product:property:query')")
+ public CommonResult getProperty(@RequestParam("id") Long id) {
+ return success(ProductPropertyValueConvert.INSTANCE.convert(productPropertyValueService.getPropertyValue(id)));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得属性值分页")
+ @PreAuthorize("@ss.hasPermission('product:property:query')")
+ public CommonResult> 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..4be1bad18
--- /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 = "属性项的编号", required = true, example = "1024")
+ private Long id;
+
+ @Schema(description = "属性项的名称", required = true, example = "颜色")
+ private String name;
+
+ /**
+ * 属性值的集合
+ */
+ private List values;
+
+ @Schema(description = "管理后台 - 属性值的简单 Response VO")
+ @Data
+ public static class Value {
+
+ @Schema(description = "属性值的编号", required = true, example = "2048")
+ private Long id;
+
+ @Schema(description = "属性值的名称", required = true, example = "红色")
+ private String name;
+
+ }
+
+}
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..1f05af4b2
--- /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 = "名称", required = true, 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..242caff84
--- /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,15 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 属性项 List Request VO")
+@Data
+@ToString(callSuper = true)
+public class ProductPropertyListReqVO {
+
+ @Schema(description = "名称", example = "颜色")
+ private String name;
+
+}
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..d729880f0
--- /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 = "状态", required = true, 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..b33e615fc
--- /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 = "编号", required = true, example = "1024")
+ private Long id;
+
+ @Schema(description = "创建时间", required = true)
+ 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..33ba8f2c1
--- /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 = "主键", required = true, 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..da3bf5068
--- /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 = "属性项的编号", required = true, example = "1024")
+ @NotNull(message = "属性项的编号不能为空")
+ private Long propertyId;
+
+ @Schema(description = "名称", required = true, example = "红色")
+ @NotEmpty(message = "名称名字不能为空")
+ private String name;
+
+ @Schema(description = "备注", example = "颜色")
+ private String remark;
+
+}
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..56801cc7d
--- /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 = "属性的编号", required = true, example = "1")
+ private Long propertyId;
+
+ @Schema(description = "属性的名称", required = true, example = "颜色")
+ private String propertyName;
+
+ @Schema(description = "属性值的编号", required = true, example = "1024")
+ private Long valueId;
+
+ @Schema(description = "属性值的名称", required = true, example = "红色")
+ private String valueName;
+
+}
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..a6088deb9
--- /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 = "状态", required = true, 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..dac075b86
--- /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 = "编号", required = true, 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..474a980b8
--- /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 = "主键", required = true, 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..038642db1
--- /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,57 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - 商品 sku")
+@RestController
+@RequestMapping("/product/sku")
+@Validated
+public class ProductSkuController {
+
+ @Resource
+ private ProductSkuService productSkuService;
+ @Resource
+ private ProductSpuService productSpuService;
+
+ @GetMapping("/get-option-list")
+ @Operation(summary = "获得商品 SKU 选项的列表")
+// @PreAuthorize("@ss.hasPermission('product:sku:query')")
+ public CommonResult> getSkuOptionList() {
+ // 获得 SKU 列表
+ List skus = productSkuService.getSkuList();
+ if (CollUtil.isEmpty(skus)) {
+ return success(Collections.emptyList());
+ }
+
+ // 获得对应的 SPU 映射
+ Map spuMap = productSpuService.getSpuMap(convertSet(skus, ProductSkuDO::getSpuId));
+ // 转换为返回结果
+ List skuVOs = ProductSkuConvert.INSTANCE.convertList05(skus);
+ skuVOs.forEach(sku -> MapUtils.findAndThen(spuMap, sku.getSpuId(),
+ spu -> sku.setSpuId(spu.getId()).setSpuName(spu.getName())));
+ return success(skuVOs);
+ }
+
+}
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..f28220046
--- /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,75 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+* 商品 SKU Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class ProductSkuBaseVO {
+
+ @Schema(description = "商品 SKU 名字", required = true, example = "芋道")
+ @NotEmpty(message = "商品 SKU 名字不能为空")
+ private String name;
+
+ @Schema(description = "销售价格,单位:分", required = true, example = "1024")
+ @NotNull(message = "销售价格,单位:分不能为空")
+ private Integer price;
+
+ @Schema(description = "市场价", example = "1024")
+ private Integer marketPrice;
+
+ @Schema(description = "成本价", example = "1024")
+ private Integer costPrice;
+
+ @Schema(description = "条形码", example = "haha")
+ private String barCode;
+
+ @Schema(description = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
+ @NotNull(message = "图片地址不能为空")
+ private String picUrl;
+
+ @Schema(description = "SKU 状态", required = true, example = "1")
+ @NotNull(message = "SKU 状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+ @Schema(description = "库存", required = true, example = "1")
+ @NotNull(message = "库存不能为空")
+ private Integer stock;
+
+ @Schema(description = "预警预存", example = "1")
+ private Integer warnStock;
+
+ @Schema(description = "商品重量", example = "1") // 单位:kg 千克
+ private Double weight;
+
+ @Schema(description = "商品体积", example = "1024") // 单位:m^3 平米
+ private Double volume;
+
+ @Schema(description = "商品属性")
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Property {
+
+ @Schema(description = "属性编号", required = true, example = "1")
+ @NotNull(message = "属性编号不能为空")
+ private Long propertyId;
+
+ @Schema(description = "属性值编号", required = true, example = "1024")
+ @NotNull(message = "属性值编号不能为空")
+ private Long valueId;
+
+ }
+
+}
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..496475f99
--- /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,21 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SKU 创建/更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSkuCreateOrUpdateReqVO extends ProductSkuBaseVO {
+
+ /**
+ * 属性数组
+ */
+ private List properties;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuOptionRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuOptionRespVO.java
new file mode 100644
index 000000000..861454795
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuOptionRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 商品 SKU 选项 Response VO") // 用于前端 SELECT 选项
+@Data
+public class ProductSkuOptionRespVO {
+
+ @Schema(description = "主键", required = true, example = "1024")
+ private Long id;
+
+ @Schema(description = "商品 SKU 名字", example = "红色")
+ private String name;
+
+ @Schema(description = "销售价格", required = true, example = "100")
+ private String price;
+
+ @Schema(description = "库存", required = true, example = "100")
+ private Integer stock;
+
+ // ========== 商品 SPU 信息 ==========
+
+ @Schema(description = "商品 SPU 编号", required = true, example = "1")
+ private Long spuId;
+
+ @Schema(description = "商品 SPU 名字", required = true, example = "iPhone 11")
+ private String spuName;
+
+}
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..95e394ada
--- /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,28 @@
+package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SKU Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSkuRespVO extends ProductSkuBaseVO {
+
+ @Schema(description = "主键", required = true, example = "1024")
+ private Long id;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+ /**
+ * 属性数组
+ */
+ private List properties;
+
+}
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..85fa93f8e
--- /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,101 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
+
+@Tag(name = "管理后台 - 商品 SPU")
+@RestController
+@RequestMapping("/product/spu")
+@Validated
+public class ProductSpuController {
+
+ @Resource
+ private ProductSpuService productSpuService;
+ @Resource
+ private ProductSkuService productSkuService;
+ @Resource
+ private ProductPropertyValueService productPropertyValueService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建商品 SPU")
+ @PreAuthorize("@ss.hasPermission('product:spu:create')")
+ public CommonResult 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);
+ }
+
+ @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.getSkuListBySpuIdAndStatus(spu.getId(), null);
+ // 查询商品属性
+ List propertyValues = productPropertyValueService
+ .getPropertyValueDetailList(ProductSkuConvert.INSTANCE.convertPropertyValueIds(skus));
+ // 拼接
+ return success(ProductSpuConvert.INSTANCE.convert03(spu, skus, propertyValues));
+ }
+
+ @GetMapping("/get-simple-list")
+ @Operation(summary = "获得商品 SPU 精简列表")
+ @PreAuthorize("@ss.hasPermission('product:spu:query')")
+ public CommonResult> getSpuSimpleList() {
+ List list = productSpuService.getSpuList();
+ return success(ProductSpuConvert.INSTANCE.convertList02(list));
+ }
+
+ @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)));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java
new file mode 100755
index 000000000..3f0c94a74
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+* 商品 SPU Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class ProductSpuBaseVO {
+
+ @Schema(description = "商品名称", required = true, example = "芋道")
+ @NotEmpty(message = "商品名称不能为空")
+ private String name;
+
+ @Schema(description = "商品编码", example = "yudaoyuanma")
+ private String code;
+
+ @Schema(description = "促销语", example = "好吃!")
+ private String sellPoint;
+
+ @Schema(description = "商品详情", required = true, example = "我是商品描述")
+ @NotNull(message = "商品详情不能为空")
+ private String description;
+
+ @Schema(description = "商品分类编号", required = true, example = "1")
+ @NotNull(message = "商品分类编号不能为空")
+ private Long categoryId;
+
+ @Schema(description = "商品品牌编号", example = "1")
+ private Long brandId;
+
+ @Schema(description = "商品图片的数组", required = true)
+ @NotNull(message = "商品图片的数组不能为空")
+ private List picUrls;
+
+ @Schema(description = "商品视频", required = true)
+ private String videoUrl;
+
+ @Schema(description = "排序字段", required = true, example = "1")
+ private Integer sort;
+
+ @Schema(description = "商品状态", required = true, example = "1")
+ @NotNull(message = "商品状态不能为空")
+ @InEnum(ProductSpuStatusEnum.class)
+ private Integer status;
+
+ // ========== SKU 相关字段 =========
+
+ @Schema(description = "规格类型", required = true, example = "1")
+ @NotNull(message = "规格类型不能为空")
+ @InEnum(ProductSpuSpecTypeEnum.class)
+ private Integer specType;
+
+ @Schema(description = "是否展示库存", required = true, example = "true")
+ @NotNull(message = "是否展示库存不能为空")
+ private Boolean showStock;
+
+ @Schema(description = "市场价", example = "1024")
+ private Integer marketPrice;
+
+ // ========== 统计相关字段 =========
+
+ @Schema(description = "虚拟销量", required = true, example = "1024")
+ @NotNull(message = "虚拟销量不能为空")
+ private Integer virtualSalesCount;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuCreateReqVO.java
new file mode 100755
index 000000000..c75ed4d2e
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuCreateReqVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SPU 创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuCreateReqVO extends ProductSpuBaseVO {
+
+ /**
+ * SKU 数组
+ */
+ @Valid
+ private List skus;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java
new file mode 100644
index 000000000..58f9565c8
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SPU 详细 Response VO") // 包括关联的 SKU 等信息
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuDetailRespVO extends ProductSpuRespVO {
+
+ // ========== SKU 相关字段 =========
+
+ /**
+ * SKU 数组
+ */
+ private List skus;
+
+ @Schema(description = "管理后台 - 商品 SKU 详细 Response VO")
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ @ToString(callSuper = true)
+ public static class Sku extends ProductSkuBaseVO {
+
+ /**
+ * 属性数组
+ */
+ private List properties;
+
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java
new file mode 100755
index 000000000..0bfefb8d4
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品 SPU 分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuPageReqVO extends PageParam {
+
+ @Schema(description = "商品名称", example = "yutou")
+ private String name;
+
+ @Schema(description = "商品编码", example = "yudaoyuanma")
+ private String code;
+
+ @Schema(description = "分类编号", example = "1")
+ private Long categoryId;
+
+ @Schema(description = "商品品牌编号", example = "1")
+ private Long brandId;
+
+ @Schema(description = "上下架状态", example = "1")
+ private Integer status;
+
+ @Schema(description = "销量最小值", example = "1")
+ private Integer salesCountMin;
+
+ @Schema(description = "销量最大值", example = "1024")
+ private Integer salesCountMax;
+
+ @Schema(description = "市场价最小值", example = "1")
+ private Integer marketPriceMin;
+
+ @Schema(description = "市场价最大值", example = "1024")
+ private Integer marketPriceMax;
+
+ @Schema(description = "是否库存告警", example = "true")
+ private Boolean alarmStock;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java
new file mode 100755
index 000000000..5f088b74b
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 商品 SPU Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuRespVO extends ProductSpuBaseVO {
+
+ @Schema(description = "主键", required = true, example = "1")
+ private Long id;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+ // ========== SKU 相关字段 =========
+
+ @Schema(description = "库存", required = true, example = "true")
+ private Integer totalStock;
+
+ @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
+ private Integer minPrice;
+
+ @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
+ private Integer maxPrice;
+
+ @Schema(description = "商品销量", example = "1024")
+ private Integer salesCount;
+
+ // ========== 统计相关字段 =========
+
+ @Schema(description = "点击量", example = "1024")
+ private Integer clickCount;
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSimpleRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSimpleRespVO.java
new file mode 100755
index 000000000..9cc7bf169
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSimpleRespVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 商品 SPU 精简 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuSimpleRespVO extends ProductSpuBaseVO {
+
+ @Schema(description = "主键", required = true, example = "1")
+ private Long id;
+
+ @Schema(description = "商品名称", required = true, example = "芋道")
+ private String name;
+
+ @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
+ private Integer minPrice;
+
+ @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
+ private Integer maxPrice;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuUpdateReqVO.java
new file mode 100755
index 000000000..6ea84fb9e
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuUpdateReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商品 SPU 更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductSpuUpdateReqVO extends ProductSpuBaseVO {
+
+ @Schema(description = "商品编号", required = true, example = "1")
+ @NotNull(message = "商品编号不能为空")
+ private Long id;
+
+ /**
+ * SKU 数组
+ */
+ @Valid
+ private List skus;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/AppCategoryController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/AppCategoryController.java
new file mode 100644
index 000000000..e484498b8
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/AppCategoryController.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.controller.app.category;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.product.controller.app.category.vo.AppCategoryRespVO;
+import cn.iocoder.yudao.module.product.convert.category.ProductCategoryConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
+import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 APP - 商品分类")
+@RestController
+@RequestMapping("/product/category")
+@Validated
+public class AppCategoryController {
+
+ @Resource
+ private ProductCategoryService categoryService;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得商品分类列表")
+ public CommonResult> getProductCategoryList() {
+ List list = categoryService.getEnableCategoryList();
+ list.sort(Comparator.comparing(ProductCategoryDO::getSort));
+ return success(ProductCategoryConvert.INSTANCE.convertList03(list));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/vo/AppCategoryRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/vo/AppCategoryRespVO.java
new file mode 100644
index 000000000..1dca05a3a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/vo/AppCategoryRespVO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.product.controller.app.category.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+@Schema(description = "用户 APP - 商品分类 Response VO")
+public class AppCategoryRespVO {
+
+ @Schema(description = "分类编号", required = true, example = "2")
+ private Long id;
+
+ @Schema(description = "父分类编号", required = true, example = "1")
+ @NotNull(message = "父分类编号不能为空")
+ private Long parentId;
+
+ @Schema(description = "分类名称", required = true, example = "办公文具")
+ @NotBlank(message = "分类名称不能为空")
+ private String name;
+
+ @Schema(description = "分类图片", required = true)
+ @NotBlank(message = "分类图片不能为空")
+ private String picUrl;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/package-info.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/package-info.java
new file mode 100644
index 000000000..379e85180
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位符,无时间作用,避免 package 缩进
+ */
+package cn.iocoder.yudao.module.product.controller.app.property;
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/property/package-info.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/property/package-info.java
new file mode 100644
index 000000000..6538bea3c
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/property/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位符,无时间作用,避免 package 缩进
+ */
+package cn.iocoder.yudao.module.product.controller.app.property.vo.property;
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/value/AppProductPropertyValueDetailRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/value/AppProductPropertyValueDetailRespVO.java
new file mode 100644
index 000000000..ac687e547
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/property/vo/value/AppProductPropertyValueDetailRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.app.property.vo.value;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 商品属性值的明细 Response VO")
+@Data
+public class AppProductPropertyValueDetailRespVO {
+
+ @Schema(description = "属性的编号", required = true, example = "1")
+ private Long propertyId;
+
+ @Schema(description = "属性的名称", required = true, example = "颜色")
+ private String propertyName;
+
+ @Schema(description = "属性值的编号", required = true, example = "1024")
+ private Long valueId;
+
+ @Schema(description = "属性值的名称", required = true, example = "红色")
+ private String valueName;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http
new file mode 100644
index 000000000..04df7bfec
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.http
@@ -0,0 +1,8 @@
+### 获得订单交易的分页 TODO
+GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10
+Authorization: Bearer {{appToken}}
+tenant-id: {{appTenentId}}
+
+### 获得商品 SPU 明细
+GET {{appApi}}/product/spu/get-detail?id=4
+tenant-id: {{appTenentId}}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
new file mode 100644
index 000000000..d9ab87c72
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.product.controller.app.spu;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
+import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
+import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
+import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_ENABLE;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
+
+@Tag(name = "用户 APP - 商品 SPU")
+@RestController
+@RequestMapping("/product/spu")
+@Validated
+public class AppProductSpuController {
+
+ @Resource
+ private ProductSpuService productSpuService;
+ @Resource
+ private ProductSkuService productSkuService;
+ @Resource
+ private ProductPropertyValueService productPropertyValueService;
+
+ @GetMapping("/page")
+ @Operation(summary = "获得商品 SPU 分页")
+ public CommonResult> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) {
+ PageResult pageResult = productSpuService.getSpuPage(pageVO, ProductSpuStatusEnum.ENABLE.getStatus());
+ return success(ProductSpuConvert.INSTANCE.convertPage02(pageResult));
+ }
+
+ @GetMapping("/get-detail")
+ @Operation(summary = "获得商品 SPU 明细")
+ @Parameter(name = "id", description = "编号", required = true)
+ public CommonResult getSpuDetail(@RequestParam("id") Long id) {
+ // 获得商品 SPU
+ ProductSpuDO spu = productSpuService.getSpu(id);
+ if (spu == null) {
+ throw exception(SPU_NOT_EXISTS);
+ }
+ if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
+ throw exception(SPU_NOT_ENABLE);
+ }
+
+ // 查询商品 SKU
+ List skus = productSkuService.getSkuListBySpuIdAndStatus(spu.getId(),
+ CommonStatusEnum.ENABLE.getStatus());
+ // 查询商品属性
+ List propertyValues = productPropertyValueService
+ .getPropertyValueDetailList(ProductSkuConvert.INSTANCE.convertPropertyValueIds(skus));
+ // 拼接
+ return success(ProductSpuConvert.INSTANCE.convert(spu, skus, propertyValues));
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuDetailRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuDetailRespVO.java
new file mode 100644
index 000000000..cf5471cba
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuDetailRespVO.java
@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "用户 App - 商品 SPU 明细 Response VO")
+@Data
+public class AppProductSpuDetailRespVO {
+
+ @Schema(description = "商品 SPU 编号", required = true, example = "1")
+ private Long id;
+
+ // ========== 基本信息 =========
+
+ @Schema(description = "商品名称", required = true, example = "芋道")
+ private String name;
+
+ @Schema(description = "促销语", example = "好吃!")
+ private String sellPoint;
+
+ @Schema(description = "商品详情", required = true, example = "我是商品描述")
+ private String description;
+
+ @Schema(description = "商品分类编号", required = true, example = "1")
+ private Long categoryId;
+
+ @Schema(description = "商品图片的数组", required = true)
+ private List picUrls;
+
+ @Schema(description = "商品视频", required = true)
+ private String videoUrl;
+
+ // ========== SKU 相关字段 =========
+
+ @Schema(description = "规格类型", required = true, example = "1")
+ private Integer specType;
+
+ @Schema(description = "是否展示库存", required = true, example = "true")
+ private Boolean showStock;
+
+ @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
+ private Integer minPrice;
+
+ @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
+ private Integer maxPrice;
+
+ /**
+ * SKU 数组
+ */
+ private List skus;
+
+ // ========== 统计相关字段 =========
+
+ @Schema(description = "商品销量", required = true, example = "1024")
+ private Integer salesCount;
+
+ @Schema(description = "用户 App - 商品 SPU 明细的 SKU 信息")
+ @Data
+ public static class Sku {
+
+ @Schema(description = "商品 SKU 编号", example = "1")
+ private Long id;
+
+ /**
+ * 商品属性数组
+ */
+ private List properties;
+
+ @Schema(description = "销售价格,单位:分", required = true, example = "1024")
+ private Integer price;
+
+ @Schema(description = "市场价", example = "1024")
+ private Integer marketPrice;
+
+ @Schema(description = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
+ private String picUrl;
+
+ @Schema(description = "库存", required = true, example = "1")
+ private Integer stock;
+
+ @Schema(description = "商品重量", example = "1") // 单位:kg 千克
+ private Double weight;
+
+ @Schema(description = "商品体积", example = "1024") // 单位:m^3 平米
+ private Double volume;
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java
new file mode 100644
index 000000000..d826900a6
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageItemRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "用户 App - 商品 SPU 分页项 Response VO")
+@Data
+public class AppProductSpuPageItemRespVO {
+
+ @Schema(description = "商品 SPU 编号", required = true, example = "1")
+ private Long id;
+
+ @Schema(description = "商品名称", required = true, example = "芋道")
+ @NotEmpty(message = "商品名称不能为空")
+ private String name;
+
+ @Schema(description = "分类编号", required = true)
+ @NotNull(message = "分类编号不能为空")
+ private Long categoryId;
+
+ @Schema(description = "商品图片的数组", required = true)
+ private List picUrls;
+
+ @Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
+ private Integer minPrice;
+
+ @Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
+ private Integer maxPrice;
+
+ // ========== 统计相关字段 =========
+
+ @Schema(description = "商品销量", example = "1024")
+ private Integer salesCount;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java
new file mode 100644
index 000000000..dcbd1e190
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageReqVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.AssertTrue;
+
+@Schema(description = "用户 App - 商品 SPU 分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppProductSpuPageReqVO extends PageParam {
+
+ public static final String SORT_FIELD_PRICE = "price";
+ public static final String SORT_FIELD_SALES_COUNT = "salesCount";
+
+ @Schema(description = "分类编号", example = "1")
+ private Long categoryId;
+
+ @Schema(description = "关键字", example = "好看")
+ private String keyword;
+
+ @Schema(description = "排序字段", example = "price") // 参见 AppSpuPageReqVO.SORT_FIELD_XXX 常量
+ private String sortField;
+
+ @Schema(description = "排序方式", example = "true")
+ private Boolean sortAsc;
+
+ @AssertTrue(message = "排序字段不合法")
+ @JsonIgnore
+ public boolean isSortFieldValid() {
+ if (StrUtil.isEmpty(sortField)) {
+ return true;
+ }
+ return StrUtil.equalsAny(sortField, SORT_FIELD_PRICE, SORT_FIELD_SALES_COUNT);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/brand/ProductBrandConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/brand/ProductBrandConvert.java
new file mode 100644
index 000000000..a318e9128
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/brand/ProductBrandConvert.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.product.convert.brand;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandUpdateReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 品牌 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductBrandConvert {
+
+ ProductBrandConvert INSTANCE = Mappers.getMapper(ProductBrandConvert.class);
+
+ ProductBrandDO convert(ProductBrandCreateReqVO bean);
+
+ ProductBrandDO convert(ProductBrandUpdateReqVO bean);
+
+ ProductBrandRespVO convert(ProductBrandDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/ProductCategoryConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/ProductCategoryConvert.java
new file mode 100644
index 000000000..ae01ca9d5
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/ProductCategoryConvert.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.product.convert.category;
+
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;
+import cn.iocoder.yudao.module.product.controller.app.category.vo.AppCategoryRespVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 商品分类 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductCategoryConvert {
+
+ ProductCategoryConvert INSTANCE = Mappers.getMapper(ProductCategoryConvert.class);
+
+ ProductCategoryDO convert(ProductCategoryCreateReqVO bean);
+
+ ProductCategoryDO convert(ProductCategoryUpdateReqVO bean);
+
+ ProductCategoryRespVO convert(ProductCategoryDO bean);
+
+ List convertList(List list);
+
+ List convertList03(List list);
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/property/ProductPropertyConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/property/ProductPropertyConvert.java
new file mode 100644
index 000000000..211bcc293
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/property/ProductPropertyConvert.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.product.convert.property;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyAndValueRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyUpdateReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 属性项 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductPropertyConvert {
+
+ ProductPropertyConvert INSTANCE = Mappers.getMapper(ProductPropertyConvert.class);
+
+ ProductPropertyDO convert(ProductPropertyCreateReqVO bean);
+
+ ProductPropertyDO convert(ProductPropertyUpdateReqVO bean);
+
+ ProductPropertyRespVO convert(ProductPropertyDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+ default List convertList(List keys, List values) {
+ Map> valueMap = CollectionUtils.convertMultiMap(values, ProductPropertyValueDO::getPropertyId);
+ return CollectionUtils.convertList(keys, key -> {
+ ProductPropertyAndValueRespVO respVO = convert02(key);
+ respVO.setValues(convertList02(valueMap.get(key.getId())));
+ return respVO;
+ });
+ }
+ ProductPropertyAndValueRespVO convert02(ProductPropertyDO bean);
+ List convertList02(List list);
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/propertyvalue/ProductPropertyValueConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/propertyvalue/ProductPropertyValueConvert.java
new file mode 100644
index 000000000..d6167c174
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/propertyvalue/ProductPropertyValueConvert.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.product.convert.propertyvalue;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 属性值 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductPropertyValueConvert {
+
+ ProductPropertyValueConvert INSTANCE = Mappers.getMapper(ProductPropertyValueConvert.class);
+
+ ProductPropertyValueDO convert(ProductPropertyValueCreateReqVO bean);
+
+ ProductPropertyValueDO convert(ProductPropertyValueUpdateReqVO bean);
+
+ ProductPropertyValueRespVO convert(ProductPropertyValueDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+ default List convertList(List values, List keys) {
+ Map keyMap = convertMap(keys, ProductPropertyDO::getId);
+ return CollectionUtils.convertList(values, value -> {
+ ProductPropertyValueDetailRespBO valueDetail = new ProductPropertyValueDetailRespBO()
+ .setValueId(value.getId()).setValueName(value.getName());
+ // 设置属性项
+ MapUtils.findAndThen(keyMap, value.getPropertyId(),
+ key -> valueDetail.setPropertyId(key.getId()).setPropertyName(key.getName()));
+ return valueDetail;
+ });
+ }
+
+ List convertList02(List list);
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
new file mode 100755
index 000000000..f397dfc48
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.product.convert.sku;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuDetailRespVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 商品 SKU Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductSkuConvert {
+
+ ProductSkuConvert INSTANCE = Mappers.getMapper(ProductSkuConvert.class);
+
+ ProductSkuDO convert(ProductSkuCreateOrUpdateReqVO bean);
+
+ ProductSkuRespVO convert(ProductSkuDO bean);
+
+ List convertList(List list);
+
+ List convertList06(List list);
+
+ default List convertList06(List list, Long spuId, String spuName) {
+ List result = convertList06(list);
+ result.forEach(item -> item.setSpuId(spuId).setSpuName(spuName));
+ return result;
+ }
+
+ ProductSkuRespDTO convert02(ProductSkuDO bean);
+
+ List convertList03(List list);
+
+ List convertList04(List list);
+
+ List convertList05(List skus);
+
+ /**
+ * 获得 SPU 的库存变化 Map
+ *
+ * @param items SKU 库存变化
+ * @param skus SKU 列表
+ * @return SPU 的库存变化 Map
+ */
+ default Map convertSpuStockMap(List items,
+ List skus) {
+ Map skuIdAndSpuIdMap = convertMap(skus, ProductSkuDO::getId, ProductSkuDO::getSpuId); // SKU 与 SKU 编号的 Map 关系
+ Map spuIdAndStockMap = new HashMap<>(); // SPU 的库存变化 Map 关系
+ items.forEach(item -> {
+ Long spuId = skuIdAndSpuIdMap.get(item.getId());
+ if (spuId == null) {
+ return;
+ }
+ Integer stock = spuIdAndStockMap.getOrDefault(spuId, 0) + item.getIncrCount();
+ spuIdAndStockMap.put(spuId, stock);
+ });
+ return spuIdAndStockMap;
+ }
+
+ default Collection convertPropertyValueIds(List list) {
+ if (CollUtil.isEmpty(list)) {
+ return new HashSet<>();
+ }
+ return list.stream().filter(item -> item.getProperties() != null)
+ .flatMap(p -> p.getProperties().stream()) // 遍历多个 Property 属性
+ .map(ProductSkuDO.Property::getValueId) // 将每个 Property 转换成对应的 propertyId,最后形成集合
+ .collect(Collectors.toSet());
+ }
+
+ default String buildPropertyKey(ProductSkuDO bean) {
+ if (CollUtil.isEmpty(bean.getProperties())) {
+ return StrUtil.EMPTY;
+ }
+ List properties = new ArrayList<>(bean.getProperties());
+ properties.sort(Comparator.comparing(ProductSkuDO.Property::getValueId));
+ return properties.stream().map(m -> String.valueOf(m.getValueId())).collect(Collectors.joining());
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
new file mode 100755
index 000000000..fcf6d6436
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
@@ -0,0 +1,108 @@
+package cn.iocoder.yudao.module.product.convert.spu;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 商品 SPU Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface ProductSpuConvert {
+
+ ProductSpuConvert INSTANCE = Mappers.getMapper(ProductSpuConvert.class);
+
+ ProductSpuDO convert(ProductSpuCreateReqVO bean);
+
+ ProductSpuDO convert(ProductSpuUpdateReqVO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+ ProductSpuPageReqVO convert(AppProductSpuPageReqVO bean);
+
+ List convertList2(List list);
+
+ List convertList02(List list);
+
+ default AppProductSpuDetailRespVO convert(ProductSpuDO spu, List skus,
+ List propertyValues) {
+ AppProductSpuDetailRespVO spuVO = convert02(spu)
+ .setSalesCount(spu.getSalesCount() + defaultIfNull(spu.getVirtualSalesCount(), 0));
+ spuVO.setSkus(convertList03(skus));
+ // 处理商品属性
+ Map propertyValueMap = convertMap(propertyValues, ProductPropertyValueDetailRespBO::getValueId);
+ for (int i = 0; i < skus.size(); i++) {
+ List properties = skus.get(i).getProperties();
+ if (CollUtil.isEmpty(properties)) {
+ continue;
+ }
+ AppProductSpuDetailRespVO.Sku sku = spuVO.getSkus().get(i);
+ sku.setProperties(new ArrayList<>(properties.size()));
+ // 遍历每个 properties,设置到 AppSpuDetailRespVO.Sku 中
+ properties.forEach(property -> {
+ ProductPropertyValueDetailRespBO propertyValue = propertyValueMap.get(property.getValueId());
+ if (propertyValue == null) {
+ return;
+ }
+ sku.getProperties().add(convert03(propertyValue));
+ });
+ }
+ return spuVO;
+ }
+ AppProductSpuDetailRespVO convert02(ProductSpuDO spu);
+ List convertList03(List skus);
+ AppProductPropertyValueDetailRespVO convert03(ProductPropertyValueDetailRespBO propertyValue);
+
+ PageResult convertPage02(PageResult page);
+
+ default ProductSpuDetailRespVO convert03(ProductSpuDO spu, List skus,
+ List propertyValues) {
+ ProductSpuDetailRespVO spuVO = convert03(spu);
+ spuVO.setSkus(convertList04(skus));
+ // 处理商品属性
+ Map propertyValueMap = convertMap(propertyValues, ProductPropertyValueDetailRespBO::getValueId);
+ for (int i = 0; i < skus.size(); i++) {
+ List properties = skus.get(i).getProperties();
+ if (CollUtil.isEmpty(properties)) {
+ continue;
+ }
+ ProductSpuDetailRespVO.Sku sku = spuVO.getSkus().get(i);
+ sku.setProperties(new ArrayList<>(properties.size()));
+ // 遍历每个 properties,设置到 AppSpuDetailRespVO.Sku 中
+ properties.forEach(property -> {
+ ProductPropertyValueDetailRespBO propertyValue = propertyValueMap.get(property.getValueId());
+ if (propertyValue == null) {
+ return;
+ }
+ sku.getProperties().add(convert04(propertyValue));
+ });
+ }
+ return spuVO;
+ }
+ ProductSpuDetailRespVO convert03(ProductSpuDO spu);
+ List convertList04(List skus);
+ ProductPropertyValueDetailRespVO convert04(ProductPropertyValueDetailRespBO propertyValue);
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java
new file mode 100644
index 000000000..9775f36a5
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/brand/ProductBrandDO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.brand;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品品牌 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_brand")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductBrandDO extends BaseDO {
+
+ /**
+ * 品牌编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 品牌名称
+ */
+ private String name;
+ /**
+ * 品牌图片
+ */
+ private String picUrl;
+ /**
+ * 品牌排序
+ */
+ private Integer sort;
+ /**
+ * 品牌描述
+ */
+ private String description;
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+ // TODO 芋艿:firstLetter 首字母
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/ProductCategoryDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/ProductCategoryDO.java
new file mode 100644
index 000000000..93ec925a9
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/category/ProductCategoryDO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.category;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品分类 DO
+ *
+ * 商品分类一共两类:
+ * 1)一级分类:{@link #parentId} 等于 0
+ * 2)二级 + 三级分类:{@link #parentId} 不等于 0
+ *
+ * @author 芋道源码
+ */
+@TableName("product_category")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductCategoryDO extends BaseDO {
+
+ /**
+ * 父分类编号 - 根分类
+ */
+ public static final Long PARENT_ID_NULL = 0L;
+
+ /**
+ * 分类编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 父分类编号
+ */
+ private Long parentId;
+ /**
+ * 分类名称
+ */
+ private String name;
+ /**
+ * 分类图片
+ *
+ * 一级分类:推荐 200 x 100 分辨率
+ * 二级 + 三级分类:推荐 100 x 100 分辨率
+ */
+ private String picUrl;
+ /**
+ * 分类排序
+ */
+ private Integer sort;
+ /**
+ * 分类描述
+ */
+ private String description;
+ /**
+ * 开启状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/comment/ProductCommentDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/comment/ProductCommentDO.java
new file mode 100644
index 000000000..c14808f22
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/comment/ProductCommentDO.java
@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.comment;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.enums.comment.ProductCommentAuditStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 商品评论 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_comment")
+@KeySequence("product_comment_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductCommentDO extends BaseDO {
+
+ /**
+ * 评论编号,主键自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * 商品 SPU 编号
+ *
+ * 关联 {@link ProductSpuDO#getId()}
+ */
+ private Long spuId;
+ /**
+ * 交易订单编号
+ *
+ * 关联 TradeOrderDO 的 id 编号
+ */
+ private Long orderId;
+ /**
+ * 交易订单项编号
+ *
+ * 关联 TradeOrderItemDO 的 id 编号
+ */
+ private Long orderItemId;
+ /**
+ * 审核状态
+ *
+ * 枚举 {@link ProductCommentAuditStatusEnum}
+ */
+ private Integer auditStatus;
+
+ /**
+ * 用户编号
+ *
+ * 关联 MemberUserDO 的 id 编号
+ */
+ private Long userId;
+ /**
+ * 用户 IP
+ */
+ private String userIp;
+ /**
+ * 是否匿名
+ */
+ private Boolean anonymous;
+ /**
+ * 评论内容
+ */
+ private String content;
+ /**
+ * 评论图片地址数组
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List picUrls;
+ /**
+ * 描述相符星级
+ *
+ * 1-5 星
+ */
+ private Integer descriptionScore;
+ /**
+ * 商品评论星级
+ *
+ * 1-5 星
+ */
+ private Integer productScore;
+ /**
+ * 服务评论星级
+ *
+ * 1-5 星
+ */
+ private Integer serviceScore;
+ /**
+ * 物流评论星级
+ *
+ * 1-5 星
+ */
+ private Integer expressComment;
+
+ /**
+ * 商家是否回复
+ */
+ private Boolean replied;
+ /**
+ * 商家回复内容
+ */
+ private String replyContent;
+ /**
+ * 商家回复时间
+ */
+ private LocalDateTime replyTime;
+
+ /**
+ * 有用的计数
+ *
+ * 其他用户看到评论时,可点击「有用」按钮
+ */
+ private Integer usefulCount;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/delivery/DeliveryTemplateDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/delivery/DeliveryTemplateDO.java
new file mode 100644
index 000000000..70445dc01
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/delivery/DeliveryTemplateDO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.delivery;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 配送模板 SPU DO
+ *
+ * @author 芋道源码
+ */
+@TableName("delivery_template")
+@KeySequence("delivery_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeliveryTemplateDO extends BaseDO {
+
+ /**
+ * 编号,自增
+ */
+ @TableId
+ private Long id;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDO.java
new file mode 100644
index 000000000..54d4b31ac
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.favorite;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品收藏 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_favorite")
+@KeySequence("product_favorite_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductFavoriteDO extends BaseDO {
+
+ /**
+ * 编号,主键自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * 用户编号
+ *
+ * 关联 MemberUserDO 的 id 编号
+ */
+ private Long userId;
+ /**
+ * 商品 SPU 编号
+ *
+ * 关联 {@link ProductSpuDO#getId()}
+ */
+ private Long spuId;
+
+ // TODO 芋艿:type 1 收藏;2 点赞
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupBindDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupBindDO.java
new file mode 100644
index 000000000..2e3b63e59
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupBindDO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.group;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品分组的绑定 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_group_bind")
+@KeySequence("product_group_bind_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductGroupBindDO extends BaseDO {
+
+ /**
+ * 编号,自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * 商品分组编号
+ *
+ * 关联 {@link ProductGroupDO#getId()}
+ */
+ private Long groupId;
+ /**
+ * 商品 SPU 编号
+ *
+ * 关联 {@link ProductSpuDO#getId()}
+ */
+ private Long spuId;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupDO.java
new file mode 100644
index 000000000..605e8c38a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/group/ProductGroupDO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.group;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.enums.group.ProductGroupStyleEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品分组 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_group")
+@KeySequence("product_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductGroupDO extends BaseDO {
+
+ /**
+ * 商品分组编号,自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * 分组名称
+ */
+ private String name;
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 商品数量
+ */
+ private Integer count;
+ /**
+ * 排序
+ */
+ private Integer sort;
+ /**
+ * 风格,用于 APP 首页展示商品的样式
+ *
+ * 枚举 {@link ProductGroupStyleEnum}
+ */
+ private Integer style;
+ /**
+ * 是否默认
+ *
+ * true - 系统默认,不允许删除
+ * false - 自定义,允许删除
+ */
+ private Boolean defaulted;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyDO.java
new file mode 100644
index 000000000..2976674c1
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyDO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.property;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品属性项 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_property")
+@KeySequence("product_property_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductPropertyDO extends BaseDO {
+
+ /**
+ * 主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 名称
+ */
+ private String name;
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyValueDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyValueDO.java
new file mode 100644
index 000000000..d73fe06b2
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/property/ProductPropertyValueDO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.property;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+
+/**
+ * 商品属性值 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_property_value")
+@KeySequence("product_property_value_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductPropertyValueDO extends BaseDO {
+
+ /**
+ * 主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 属性项的编号
+ *
+ * 关联 {@link ProductPropertyDO#getId()}
+ */
+ private Long propertyId;
+ /**
+ * 名称
+ */
+ private String name;
+ /**
+ * 备注
+ *
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/search/ProductHotSearchDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/search/ProductHotSearchDO.java
new file mode 100644
index 000000000..3d5cf9101
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/search/ProductHotSearchDO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.search;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品热搜关键字 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("product_hot_search")
+@KeySequence("product_hot_search_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductHotSearchDO extends BaseDO {
+
+ /**
+ * 编号,主键自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * 关键字
+ */
+ private String name;
+ /**
+ * 内容
+ */
+ private String content;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/shop/ShopDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/shop/ShopDO.java
new file mode 100644
index 000000000..1c702da91
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/shop/ShopDO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.shop;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+// TODO 芋艿:待设计
+/**
+ * 店铺 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("shop")
+@KeySequence("shop_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ShopDO extends BaseDO {
+
+ private Long id;
+
+}
diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java
new file mode 100755
index 000000000..3836f20e5
--- /dev/null
+++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/sku/ProductSkuDO.java
@@ -0,0 +1,137 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.sku;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * 商品 SKU DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "product_sku",autoResultMap = true)
+@KeySequence("product_sku_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductSkuDO extends BaseDO {
+
+ /**
+ * 商品 SKU 编号,自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * SPU 编号
+ *
+ * 关联 {@link ProductSpuDO#getId()}
+ */
+ private Long spuId;
+ /**
+ * SPU 名字
+ *
+ * 冗余 {@link ProductSkuDO#getSpuName()}
+ */
+ private String spuName;
+ /**
+ * 属性数组,JSON 格式
+ */
+ @TableField(typeHandler = PropertyTypeHandler.class)
+ private List properties;
+ /**
+ * 销售价格,单位:分
+ */
+ private Integer price;
+ /**
+ * 市场价,单位:分
+ */
+ private Integer marketPrice;
+ /**
+ * 成本价,单位:分
+ */
+ private Integer costPrice;
+ /**
+ * SKU 的条形码
+ */
+ private String barCode;
+ /**
+ * 图片地址
+ */
+ private String picUrl;
+ /**
+ * SKU 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 库存
+ */
+ private Integer stock;
+ /**
+ * 预警预存
+ */
+ private Integer warnStock;
+ /**
+ * 商品重量,单位:kg 千克
+ */
+ private Double weight;
+ /**
+ * 商品体积,单位:m^3 平米
+ */
+ private Double volume;
+
+ /**
+ * 商品属性
+ */
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class Property {
+
+ /**
+ * 属性编号
+ *
+ * 关联 {@link ProductPropertyDO#getId()}
+ */
+ private Long propertyId;
+ /**
+ * 属性值编号
+ *
+ * 关联 {@link ProductPropertyValueDO#getId()}
+ */
+ private Long valueId;
+
+ }
+
+ // TODO @芋艿:可以找一些新的思路
+ public static class PropertyTypeHandler extends AbstractJsonTypeHandler