diff --git a/pom.xml b/pom.xml index 65000e402..ae701f807 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ yudao-module-pay yudao-module-report yudao-module-mp + yudao-module-mall ${project.artifactId} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java index 68fe2f107..8e0661749 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java @@ -59,6 +59,11 @@ public class DictFrameworkUtils { log.info("[init][初始化 DictFrameworkUtils 成功]"); } + @SneakyThrows + public static String getDictDataLabel(String dictType, Integer value) { + return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, String.valueOf(value))).getLabel(); + } + @SneakyThrows public static String getDictDataLabel(String dictType, String value) { return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel(); diff --git a/yudao-module-mall/pom.xml b/yudao-module-mall/pom.xml new file mode 100644 index 000000000..8029a9a85 --- /dev/null +++ b/yudao-module-mall/pom.xml @@ -0,0 +1,31 @@ + + + + yudao + cn.iocoder.cloud + ${revision} + + 4.0.0 + + yudao-module-mall + pom + + ${project.artifactId} + + + 商城大模块,由 product 商品、promotion 营销、trade 交易、statistics 统计等组成 + + + + + yudao-module-product-api + yudao-module-product-biz + + + + + + + diff --git a/yudao-module-mall/yudao-module-product-api/pom.xml b/yudao-module-mall/yudao-module-product-api/pom.xml new file mode 100644 index 000000000..3404a06d0 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + cn.iocoder.cloud + yudao-module-mall + ${revision} + + + yudao-module-product-api + jar + + ${project.artifactId} + + product 模块 API,暴露给其它模块调用 + + + + + cn.iocoder.cloud + yudao-common + + + + + org.springdoc + springdoc-openapi-ui + provided + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApi.java new file mode 100644 index 000000000..38feb96d6 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApi.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.product.api.category; + +import java.util.Collection; + +/** + * 商品分类 API 接口 + * + * @author owen + */ +public interface ProductCategoryApi { + + /** + * 校验商品分类是否有效。如下情况,视为无效: + * 1. 商品分类编号不存在 + * 2. 商品分类被禁用 + * + * @param ids 商品分类编号数组 + */ + void validateCategoryList(Collection ids); +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApi.java new file mode 100644 index 000000000..b0ba1f7fe --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApi.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.product.api.comment; + +import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; + +/** + * 产品评论 API 接口 + * + * @author HUIHUI + */ +public interface ProductCommentApi { + + /** + * 创建评论 + * + * @param createReqDTO 评论参数 + * @return 返回评论创建后的 id + */ + Long createComment(ProductCommentCreateReqDTO createReqDTO); + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/comment/dto/ProductCommentCreateReqDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/comment/dto/ProductCommentCreateReqDTO.java new file mode 100644 index 000000000..aff7fbd27 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/comment/dto/ProductCommentCreateReqDTO.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.product.api.comment.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 评论创建请求 DTO + * + * @author HUIHUI + */ +@Data +public class ProductCommentCreateReqDTO { + + /** + * 商品 SKU 编号 + */ + private Long skuId; + /** + * 订单编号 + */ + private Long orderId; + /** + * 交易订单项编号 + */ + private Long orderItemId; + + /** + * 评分星级 1-5 分 + */ + private Integer scores; + /** + * 描述星级 1-5 分 + */ + private Integer descriptionScores; + /** + * 服务星级 1-5 分 + */ + private Integer benefitScores; + /** + * 评论内容 + */ + private String content; + /** + * 评论图片地址数组,以逗号分隔最多上传 9 张 + */ + private List picUrls; + + /** + * 是否匿名 + */ + private Boolean anonymous; + /** + * 评价人 + */ + private Long userId; + + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java new file mode 100644 index 000000000..b19092853 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.product.api; \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApi.java new file mode 100644 index 000000000..83269f91d --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApi.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.product.api.property; + +import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; + +import java.util.Collection; +import java.util.List; + +/** + * 商品属性值 API 接口 + * + * @author 芋道源码 + */ +public interface ProductPropertyValueApi { + + /** + * 根据编号数组,获得属性值列表 + * + * @param ids 编号数组 + * @return 属性值明细列表 + */ + List getPropertyValueDetailList(Collection ids); + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/dto/ProductPropertyValueDetailRespDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/dto/ProductPropertyValueDetailRespDTO.java new file mode 100644 index 000000000..2a1ab71a9 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/dto/ProductPropertyValueDetailRespDTO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.product.api.property.dto; + +import lombok.Data; + +/** + * 商品属性项的明细 Response DTO + * + * @author 芋道源码 + */ +@Data +public class ProductPropertyValueDetailRespDTO { + + /** + * 属性的编号 + */ + private Long propertyId; + + /** + * 属性的名称 + */ + private String propertyName; + + /** + * 属性值的编号 + */ + private Long valueId; + + /** + * 属性值的名称 + */ + private String valueName; + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java new file mode 100644 index 000000000..3581fdb91 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.product.api.sku; + +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; + +import java.util.Collection; +import java.util.List; + +/** + * 商品 SKU API 接口 + * + * @author LeeYan9 + * @since 2022-08-26 + */ +public interface ProductSkuApi { + + /** + * 查询 SKU 信息 + * + * @param id SKU 编号 + * @return SKU 信息 + */ + ProductSkuRespDTO getSku(Long id); + + /** + * 批量查询 SKU 数组 + * + * @param ids SKU 编号列表 + * @return SKU 数组 + */ + List getSkuList(Collection ids); + + /** + * 批量查询 SKU 数组 + * + * @param spuIds SPU 编号列表 + * @return SKU 数组 + */ + List getSkuListBySpuId(Collection spuIds); + + /** + * 更新 SKU 库存(增加 or 减少) + * + * @param updateStockReqDTO 更新请求 + */ + void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO); + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java new file mode 100644 index 000000000..338c4dd85 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuRespDTO.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.product.api.sku.dto; + +import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO; +import lombok.Data; + +import java.util.List; + +/** + * 商品 SKU 信息 Response DTO + * + * @author LeeYan9 + * @since 2022-08-26 + */ +@Data +public class ProductSkuRespDTO { + + /** + * 商品 SKU 编号,自增 + */ + private Long id; + /** + * SPU 编号 + */ + private Long spuId; + + /** + * 属性数组 + */ + private List properties; + /** + * 销售价格,单位:分 + */ + private Integer price; + /** + * 市场价,单位:分 + */ + private Integer marketPrice; + /** + * 成本价,单位:分 + */ + private Integer costPrice; + /** + * SKU 的条形码 + */ + private String barCode; + /** + * 图片地址 + */ + private String picUrl; + /** + * 库存 + */ + private Integer stock; + /** + * 商品重量,单位:kg 千克 + */ + private Double weight; + /** + * 商品体积,单位:m^3 平米 + */ + private Double volume; + /** + * 一级分销的佣金,单位:分 + */ + private Integer firstBrokeragePrice; + /** + * 二级分销的佣金,单位:分 + */ + private Integer secondBrokeragePrice; + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java new file mode 100644 index 000000000..345c17cbd --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.product.api.sku.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 商品 SKU 更新库存 Request DTO + * + * @author LeeYan9 + * @since 2022-08-26 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductSkuUpdateStockReqDTO { + + /** + * 商品 SKU + */ + @NotNull(message = "商品 SKU 不能为空") + private List items; + + @Data + public static class Item { + + /** + * 商品 SKU 编号 + */ + @NotNull(message = "商品 SKU 编号不能为空") + private Long id; + + /** + * 库存变化数量 + * + * 正数:增加库存 + * 负数:扣减库存 + */ + @NotNull(message = "库存变化数量不能为空") + private Integer incrCount; + + } + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java new file mode 100644 index 000000000..233d129fa --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.product.api.spu; + +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; + +import java.util.Collection; +import java.util.List; + +/** + * 商品 SPU API 接口 + * + * @author LeeYan9 + * @since 2022-08-26 + */ +public interface ProductSpuApi { + + /** + * 批量查询 SPU 数组 + * + * @param ids SPU 编号列表 + * @return SPU 数组 + */ + List getSpuList(Collection ids); + + /** + * 批量查询 SPU 数组,并且校验是否 SPU 是否有效。 + * + * 如下情况,视为无效: + * 1. 商品编号不存在 + * 2. 商品被禁用 + * + * @param ids SPU 编号列表 + * @return SPU 数组 + */ + List validateSpuList(Collection ids); + + /** + * 获得 SPU + * + * @return SPU + */ + ProductSpuRespDTO getSpu(Long id); + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java new file mode 100644 index 000000000..829714ce2 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java @@ -0,0 +1,152 @@ +package cn.iocoder.yudao.module.product.api.spu.dto; + +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +import lombok.Data; + +import java.util.List; + +/** + * 商品 SPU 信息 Response DTO + * + * @author LeeYan9 + * @since 2022-08-26 + */ +@Data +public class ProductSpuRespDTO { + + /** + * 商品 SPU 编号,自增 + */ + private Long id; + + // ========== 基本信息 ========= + + /** + * 商品名称 + */ + private String name; + /** + * 关键字 + */ + private String keyword; + /** + * 单位 + * + * 对应 product_unit 数据字典 + */ + private Integer unit; + /** + * 商品简介 + */ + private String introduction; + /** + * 商品详情 + */ + private String description; + // TODO @芋艿:是不是要删除 + /** + * 商品条码(一维码) + */ + private String barCode; + + /** + * 商品分类编号 + */ + private Long categoryId; + /** + * 商品品牌编号 + */ + private Long brandId; + /** + * 商品封面图 + */ + private String picUrl; + /** + * 商品轮播图 + */ + private List sliderPicUrls; + /** + * 商品视频 + */ + private String videoUrl; + + /** + * 排序字段 + */ + private Integer sort; + /** + * 商品状态 + *

+ * 枚举 {@link ProductSpuStatusEnum} + */ + private Integer status; + + // ========== SKU 相关字段 ========= + + /** + * 规格类型 + * + * false - 单规格 + * true - 多规格 + */ + private Boolean specType; + /** + * 商品价格,单位使用:分 + */ + private Integer price; + /** + * 市场价,单位使用:分 + */ + private Integer marketPrice; + /** + * 成本价,单位使用:分 + */ + private Integer costPrice; + /** + * 库存 + */ + private Integer stock; + + // ========== 物流相关字段 ========= + + /** + * 物流配置模板编号 + * + * 对应 TradeDeliveryExpressTemplateDO 的 id 编号 + */ + private Long deliveryTemplateId; + + // ========== 营销相关字段 ========= + + /** + * 赠送积分 + */ + private Integer giveIntegral; + + // ========== 统计相关字段 ========= + + /** + * 商品销量 + */ + private Integer salesCount; + /** + * 虚拟销量 + */ + private Integer virtualSalesCount; + /** + * 商品点击量 + */ + private Integer clickCount; + + + // ========== 分销相关字段 ========= + + /** + * 分销类型 + * + * false - 默认 + * true - 自行设置 + */ + private Boolean subCommissionType; + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/DictTypeConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/DictTypeConstants.java new file mode 100644 index 000000000..85725a18e --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/DictTypeConstants.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.product.enums; + +/** + * product 字典类型的枚举类 + * + * @author HUIHUI + */ +public interface DictTypeConstants { + + String PRODUCT_UNIT = "product_unit"; // 商品单位 + String PRODUCT_SPU_STATUS = "product_spu_status"; // 商品 SPU 状态 + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java new file mode 100644 index 000000000..1d0ea189f --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.product.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +/** + * Product 错误码枚举类 + * + * product 系统,使用 1-008-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 商品分类相关 1-008-001-000 ============ + ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_008_001_000, "商品分类不存在"); + ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1_008_001_001, "父分类不存在"); + ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_008_001_002, "父分类不能是二级分类"); + ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_008_001_003, "存在子分类,无法删除"); + ErrorCode CATEGORY_DISABLED = new ErrorCode(1_008_001_004, "商品分类({})已禁用,无法使用"); + ErrorCode CATEGORY_HAVE_BIND_SPU = new ErrorCode(1_008_001_005, "类别下存在商品,无法删除"); + + // ========== 商品品牌相关编号 1-008-002-000 ========== + ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1_008_002_000, "品牌不存在"); + ErrorCode BRAND_DISABLED = new ErrorCode(1_008_002_001, "品牌已禁用"); + ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1_008_002_002, "品牌名称已存在"); + + // ========== 商品属性项 1-008-003-000 ========== + ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1_008_003_000, "属性项不存在"); + ErrorCode PROPERTY_EXISTS = new ErrorCode(1_008_003_001, "属性项的名称已存在"); + ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1_008_003_002, "属性项下存在属性值,无法删除"); + + // ========== 商品属性值 1-008-004-000 ========== + ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1_008_004_000, "属性值不存在"); + ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1_008_004_001, "属性值的名称已存在"); + + // ========== 商品 SPU 1-008-005-000 ========== + ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在"); + ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下"); + ErrorCode SPU_SAVE_FAIL_COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_005_002, "商品 SPU 保存失败,原因:优惠卷不存在"); + ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_003, "商品 SPU【{}】不处于上架状态"); + ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_004, "商品 SPU 不处于回收站状态"); + + // ========== 商品 SKU 1-008-006-000 ========== + ErrorCode SKU_NOT_EXISTS = new ErrorCode(1_008_006_000, "商品 SKU 不存在"); + ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1_008_006_001, "商品 SKU 的属性组合存在重复"); + ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1_008_006_002, "一个 SPU 下的每个 SKU,其属性项必须一致"); + ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1_008_006_003, "一个 SPU 下的每个 SKU,必须不重复"); + ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1_008_006_004, "商品 SKU 库存不足"); + + // ========== 商品 评价 1-008-007-000 ========== + ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1_008_007_000, "商品评价不存在"); + ErrorCode COMMENT_ORDER_EXISTS = new ErrorCode(1_008_007_001, "订单的商品评价已存在"); + + // ========== 商品 收藏 1-008-008-000 ========== + ErrorCode FAVORITE_EXISTS = new ErrorCode(1_008_008_000, "该商品已经被收藏"); + ErrorCode FAVORITE_NOT_EXISTS = new ErrorCode(1_008_008_001, "商品收藏不存在"); + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ProductConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ProductConstants.java new file mode 100644 index 000000000..f3570c589 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ProductConstants.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.product.enums; + +/** + * Product 常量 + * + * @author HUIHUI + */ +public interface ProductConstants { + + /** + * 警戒库存 TODO 警戒库存暂时为 10,后期需要使用常量或者数据库配置替换 + */ + int ALERT_STOCK = 10; + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentAuditStatusEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentAuditStatusEnum.java new file mode 100644 index 000000000..276839daf --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentAuditStatusEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.product.enums.comment; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 商品评论的审批状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum ProductCommentAuditStatusEnum implements IntArrayValuable { + + NONE(1, "待审核"), + APPROVE(2, "审批通过"), + REJECT(2, "审批不通过"),; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductCommentAuditStatusEnum::getStatus).toArray(); + + /** + * 审批状态 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentScoresEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentScoresEnum.java new file mode 100644 index 000000000..a114e1ab8 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/comment/ProductCommentScoresEnum.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.product.enums.comment; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 商品评论的星级枚举 + * + * @author wangzhs + */ +@Getter +@AllArgsConstructor +public enum ProductCommentScoresEnum implements IntArrayValuable { + + ONE(1, "1星"), + TWO(2, "2星"), + THREE(3, "3星"), + FOUR(4, "4星"), + FIVE(5, "5星"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductCommentScoresEnum::getScores).toArray(); + + /** + * 星级 + */ + private final Integer scores; + + /** + * 星级名 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/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/ProductSpuStatusEnum.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuStatusEnum.java new file mode 100644 index 000000000..4ba6124e0 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/spu/ProductSpuStatusEnum.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.product.enums.spu; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 商品 SPU 状态 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum ProductSpuStatusEnum implements IntArrayValuable { + + RECYCLE(-1, "回收站"), + DISABLE(0, "下架"), + ENABLE(1, "上架"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductSpuStatusEnum::getStatus).toArray(); + + /** + * 状态 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + + /** + * 判断是否处于【上架】状态 + * + * @param status 状态 + * @return 是否处于【上架】状态 + */ + public static boolean isEnable(Integer status) { + return ENABLE.getStatus().equals(status); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/Dockerfile b/yudao-module-mall/yudao-module-product-biz/Dockerfile new file mode 100644 index 000000000..c375d0d56 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/Dockerfile @@ -0,0 +1,19 @@ +## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性 +## 感谢复旦核博士的建议!灰子哥,牛皮! +FROM eclipse-temurin:8-jre + +## 创建目录,并使用它作为工作目录 +RUN mkdir -p /yudao-module-product-biz +WORKDIR /yudao-module-product-biz +## 将后端项目的 Jar 文件,复制到镜像中 +COPY ./target/yudao-module-product-biz.jar app.jar + +## 设置 TZ 时区 +## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" + +## 暴露后端项目的 48080 端口 +EXPOSE 48100 + +## 启动后端项目 +CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar diff --git a/yudao-module-mall/yudao-module-product-biz/pom.xml b/yudao-module-mall/yudao-module-product-biz/pom.xml new file mode 100644 index 000000000..eb720dbeb --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/pom.xml @@ -0,0 +1,107 @@ + + + + cn.iocoder.cloud + yudao-module-mall + ${revision} + + 4.0.0 + yudao-module-product-biz + jar + + ${project.artifactId} + + product 模块,主要实现商品相关功能 + 例如:品牌、商品分类、spu、sku等功能。 + + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + cn.iocoder.cloud + yudao-spring-boot-starter-env + + + + + cn.iocoder.cloud + yudao-module-product-api + ${revision} + + + cn.iocoder.cloud + yudao-module-member-api + ${revision} + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-operatelog + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-dict + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-web + + + cn.iocoder.cloud + yudao-spring-boot-starter-security + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-mybatis + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-test + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-excel + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-monitor + + + + diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/ProductServerApplication.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/ProductServerApplication.java new file mode 100644 index 000000000..651481ba0 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/ProductServerApplication.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.product; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + * + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * + * @author 芋道源码 + */ +@SpringBootApplication +public class ProductServerApplication { + + public static void main(String[] args) { + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + + SpringApplication.run(ProductServerApplication.class, args); + + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java new file mode 100644 index 000000000..18f5c1d6c --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/category/ProductCategoryApiImpl.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.product.api.category; + +import cn.iocoder.yudao.module.product.service.category.ProductCategoryService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; + +/** + * 商品分类 API 接口实现类 + * + * @author owen + */ +@Service +@Validated +public class ProductCategoryApiImpl implements ProductCategoryApi { + + @Resource + private ProductCategoryService productCategoryService; + + @Override + public void validateCategoryList(Collection ids) { + productCategoryService.validateCategoryList(ids); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApiImpl.java new file mode 100644 index 000000000..919c22c90 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/comment/ProductCommentApiImpl.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.product.api.comment; + +import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; +import cn.iocoder.yudao.module.product.service.comment.ProductCommentService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; + +/** + * 商品评论 API 实现类 + * + * @author HUIHUI + */ +@Service +@Validated +public class ProductCommentApiImpl implements ProductCommentApi { + + @Resource + private ProductCommentService productCommentService; + + @Override + public Long createComment(ProductCommentCreateReqDTO createReqDTO) { + return productCommentService.createComment(createReqDTO); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java new file mode 100644 index 000000000..162453c3c --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.product.api; diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/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..d3eaaf42d --- /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.property.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..bd0258efa --- /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,59 @@ +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; + +/** + * 商品 SKU API 实现类 + * + * @author LeeYan9 + * @since 2022-09-06 + */ +@Service +@Validated +public class ProductSkuApiImpl implements ProductSkuApi { + + @Resource + private ProductSkuService productSkuService; + + @Override + public ProductSkuRespDTO getSku(Long id) { + ProductSkuDO sku = productSkuService.getSku(id); + return ProductSkuConvert.INSTANCE.convert02(sku); + } + + @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 List getSkuListBySpuId(Collection spuIds) { + if (CollUtil.isEmpty(spuIds)) { + return Collections.emptyList(); + } + List skus = productSkuService.getSkuListBySpuId(spuIds); + 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..559075898 --- /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,46 @@ +package cn.iocoder.yudao.module.product.api.spu; + +import cn.hutool.core.collection.CollectionUtil; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; +import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * 商品 SPU API 接口实现类 + * + * @author LeeYan9 + * @since 2022-09-06 + */ +@Service +@Validated +public class ProductSpuApiImpl implements ProductSpuApi { + + @Resource + private ProductSpuService spuService; + + @Override + public List getSpuList(Collection ids) { + if (CollectionUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return ProductSpuConvert.INSTANCE.convertList2(spuService.getSpuList(ids)); + } + + @Override + public List validateSpuList(Collection ids) { + return ProductSpuConvert.INSTANCE.convertList2(spuService.validateSpuList(ids)); + } + + @Override + public ProductSpuRespDTO getSpu(Long id) { + return ProductSpuConvert.INSTANCE.convert02(spuService.getSpu(id)); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/ProductBrandController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/ProductBrandController.java new file mode 100644 index 000000000..a7c954124 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/ProductBrandController.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.product.controller.admin.brand; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.*; +import cn.iocoder.yudao.module.product.convert.brand.ProductBrandConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO; +import cn.iocoder.yudao.module.product.service.brand.ProductBrandService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.Comparator; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 商品品牌") +@RestController +@RequestMapping("/product/brand") +@Validated +public class ProductBrandController { + + @Resource + private ProductBrandService brandService; + + @PostMapping("/create") + @Operation(summary = "创建品牌") + @PreAuthorize("@ss.hasPermission('product:brand:create')") + public CommonResult createBrand(@Valid @RequestBody ProductBrandCreateReqVO createReqVO) { + return success(brandService.createBrand(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新品牌") + @PreAuthorize("@ss.hasPermission('product:brand:update')") + public CommonResult updateBrand(@Valid @RequestBody ProductBrandUpdateReqVO updateReqVO) { + brandService.updateBrand(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除品牌") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:brand:delete')") + public CommonResult deleteBrand(@RequestParam("id") Long id) { + brandService.deleteBrand(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得品牌") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:brand:query')") + public CommonResult getBrand(@RequestParam("id") Long id) { + ProductBrandDO brand = brandService.getBrand(id); + return success(ProductBrandConvert.INSTANCE.convert(brand)); + } + + @GetMapping("/list-all-simple") + @Operation(summary = "获取品牌精简信息列表", description = "主要用于前端的下拉选项") + public CommonResult> getSimpleBrandList() { + // 获取品牌列表,只要开启状态的 + List list = brandService.getBrandListByStatus(CommonStatusEnum.ENABLE.getStatus()); + // 排序后,返回给前端 + return success(ProductBrandConvert.INSTANCE.convertList1(list)); + } + + @GetMapping("/page") + @Operation(summary = "获得品牌分页") + @PreAuthorize("@ss.hasPermission('product:brand:query')") + public CommonResult> getBrandPage(@Valid ProductBrandPageReqVO pageVO) { + PageResult pageResult = brandService.getBrandPage(pageVO); + return success(ProductBrandConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/list") + @Operation(summary = "获得品牌列表") + @PreAuthorize("@ss.hasPermission('product:brand:query')") + public CommonResult> getBrandList(@Valid ProductBrandListReqVO listVO) { + List list = brandService.getBrandList(listVO); + list.sort(Comparator.comparing(ProductBrandDO::getSort)); + return success(ProductBrandConvert.INSTANCE.convertList(list)); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandBaseVO.java new file mode 100644 index 000000000..a148d52de --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandBaseVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.product.controller.admin.brand.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** +* 商品品牌 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class ProductBrandBaseVO { + + @Schema(description = "品牌名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "苹果") + @NotNull(message = "品牌名称不能为空") + private String name; + + @Schema(description = "品牌图片", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "品牌图片不能为空") + private String picUrl; + + @Schema(description = "品牌排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "品牌排序不能为空") + private Integer sort; + + @Schema(description = "品牌描述", example = "描述") + private String description; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandCreateReqVO.java new file mode 100644 index 000000000..dc85a476b --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.product.controller.admin.brand.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品品牌创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductBrandCreateReqVO extends ProductBrandBaseVO { + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandListReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandListReqVO.java new file mode 100644 index 000000000..ed93ff090 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandListReqVO.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.product.controller.admin.brand.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品品牌分页 Request VO") +@Data +public class ProductBrandListReqVO { + + @Schema(description = "品牌名称", example = "苹果") + private String name; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandPageReqVO.java new file mode 100644 index 000000000..3a6efc93f --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandPageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.product.controller.admin.brand.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 商品品牌分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductBrandPageReqVO extends PageParam { + + @Schema(description = "品牌名称", example = "苹果") + private String name; + + @Schema(description = "状态", example = "0") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandRespVO.java new file mode 100644 index 000000000..486fe764b --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.product.controller.admin.brand.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 品牌 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductBrandRespVO extends ProductBrandBaseVO { + + @Schema(description = "品牌编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandSimpleRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandSimpleRespVO.java new file mode 100644 index 000000000..6379a5fb1 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandSimpleRespVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.product.controller.admin.brand.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Schema(description = "管理后台 - 品牌精简信息 Response VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductBrandSimpleRespVO { + + @Schema(description = "品牌编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "品牌名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "苹果") + private String name; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandUpdateReqVO.java new file mode 100644 index 000000000..a39a6830d --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/brand/vo/ProductBrandUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.product.controller.admin.brand.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 商品品牌更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductBrandUpdateReqVO extends ProductBrandBaseVO { + + @Schema(description = "品牌编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "品牌编号不能为空") + private Long id; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/ProductCategoryController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/ProductCategoryController.java new file mode 100644 index 000000000..dc3a57a38 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/ProductCategoryController.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.product.controller.admin.category; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO; +import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryRespVO; +import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO; +import cn.iocoder.yudao.module.product.convert.category.ProductCategoryConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; +import cn.iocoder.yudao.module.product.service.category.ProductCategoryService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.Comparator; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 商品分类") +@RestController +@RequestMapping("/product/category") +@Validated +public class ProductCategoryController { + + @Resource + private ProductCategoryService categoryService; + + @PostMapping("/create") + @Operation(summary = "创建商品分类") + @PreAuthorize("@ss.hasPermission('product:category:create')") + public CommonResult createCategory(@Valid @RequestBody ProductCategoryCreateReqVO createReqVO) { + return success(categoryService.createCategory(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品分类") + @PreAuthorize("@ss.hasPermission('product:category:update')") + public CommonResult updateCategory(@Valid @RequestBody ProductCategoryUpdateReqVO updateReqVO) { + categoryService.updateCategory(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品分类") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('product:category:delete')") + public CommonResult deleteCategory(@RequestParam("id") Long id) { + categoryService.deleteCategory(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商品分类") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:category:query')") + public CommonResult getCategory(@RequestParam("id") Long id) { + ProductCategoryDO category = categoryService.getCategory(id); + return success(ProductCategoryConvert.INSTANCE.convert(category)); + } + + @GetMapping("/list") + @Operation(summary = "获得商品分类列表") + @PreAuthorize("@ss.hasPermission('product:category:query')") + public CommonResult> getCategoryList(@Valid ProductCategoryListReqVO treeListReqVO) { + List list = categoryService.getEnableCategoryList(treeListReqVO); + list.sort(Comparator.comparing(ProductCategoryDO::getSort)); + return success(ProductCategoryConvert.INSTANCE.convertList(list)); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryBaseVO.java new file mode 100644 index 000000000..5a8363167 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryBaseVO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.product.controller.admin.category.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** +* 商品分类 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class ProductCategoryBaseVO { + + @Schema(description = "父分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "父分类编号不能为空") + private Long parentId; + + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "办公文具") + @NotBlank(message = "分类名称不能为空") + private String name; + + @Schema(description = "移动端分类图", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "移动端分类图不能为空") + private String picUrl; + + @Schema(description = "PC 端分类图") + private String bigPicUrl; + + @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer sort; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "开启状态不能为空") + private Integer status; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryCreateReqVO.java new file mode 100644 index 000000000..cd02ddbd0 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryCreateReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.product.controller.admin.category.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotBlank; + +@Schema(description = "管理后台 - 商品分类创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductCategoryCreateReqVO extends ProductCategoryBaseVO { + + @Schema(description = "分类描述", example = "描述") + private String description; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java new file mode 100644 index 000000000..16f5df857 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.product.controller.admin.category.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品分类列表查询 Request VO") +@Data +public class ProductCategoryListReqVO { + + @Schema(description = "分类名称", example = "办公文具") + private String name; + + @Schema(description = "开启状态", example = "0") + private Integer status; + + @Schema(description = "父分类编号", example = "1") + private Long parentId; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryRespVO.java new file mode 100644 index 000000000..8f46ff60f --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.product.controller.admin.category.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 商品分类 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductCategoryRespVO extends ProductCategoryBaseVO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java new file mode 100644 index 000000000..ffcdc3f5c --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.product.controller.admin.category.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 商品分类更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductCategoryUpdateReqVO extends ProductCategoryBaseVO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "分类编号不能为空") + private Long id; + + @Schema(description = "分类描述", example = "描述") + private String description; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.http b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.http new file mode 100644 index 000000000..e69de29bb diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java new file mode 100644 index 000000000..70e95cd9a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/ProductCommentController.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.product.controller.admin.comment; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.*; +import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; +import cn.iocoder.yudao.module.product.service.comment.ProductCommentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 商品评价") +@RestController +@RequestMapping("/product/comment") +@Validated +public class ProductCommentController { + + @Resource + private ProductCommentService productCommentService; + + @GetMapping("/page") + @Operation(summary = "获得商品评价分页") + @PreAuthorize("@ss.hasPermission('product:comment:query')") + public CommonResult> getCommentPage(@Valid ProductCommentPageReqVO pageVO) { + PageResult pageResult = productCommentService.getCommentPage(pageVO); + return success(ProductCommentConvert.INSTANCE.convertPage(pageResult)); + } + + @PutMapping("/update-visible") + @Operation(summary = "显示 / 隐藏评论") + @PreAuthorize("@ss.hasPermission('product:comment:update')") + public CommonResult updateCommentVisible(@Valid @RequestBody ProductCommentUpdateVisibleReqVO updateReqVO) { + productCommentService.updateCommentVisible(updateReqVO); + return success(true); + } + + @PutMapping("/reply") + @Operation(summary = "商家回复") + @PreAuthorize("@ss.hasPermission('product:comment:update')") + public CommonResult commentReply(@Valid @RequestBody ProductCommentReplyReqVO replyVO) { + productCommentService.replyComment(replyVO, getLoginUserId()); + return success(true); + } + + @PostMapping("/create") + @Operation(summary = "添加自评") + @PreAuthorize("@ss.hasPermission('product:comment:update')") + public CommonResult createComment(@Valid @RequestBody ProductCommentCreateReqVO createReqVO) { + productCommentService.createComment(createReqVO); + return success(true); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentBaseVO.java new file mode 100644 index 000000000..f88f5ed68 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentBaseVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.product.controller.admin.comment.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.List; + +@Data +public class ProductCommentBaseVO { + + @Schema(description = "评价人", requiredMode = Schema.RequiredMode.REQUIRED, example = "16868") + private Long userId; + + @Schema(description = "评价订单项", requiredMode = Schema.RequiredMode.REQUIRED, example = "19292") + private Long orderItemId; + + @Schema(description = "评价人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小姑凉") + @NotNull(message = "评价人名称不能为空") + private String userNickname; + + @Schema(description = "评价人头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png") + @NotNull(message = "评价人头像不能为空") + private String userAvatar; + + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "商品 SKU 编号不能为空") + private Long skuId; + + @Schema(description = "描述星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + @NotNull(message = "描述星级不能为空") + private Integer descriptionScores; + + @Schema(description = "服务星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + @NotNull(message = "服务星级分不能为空") + private Integer benefitScores; + + @Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "穿起来非常丝滑凉快") + @NotNull(message = "评论内容不能为空") + private String content; + + @Schema(description = "评论图片地址数组,以逗号分隔最多上传 9 张", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/xx.png]") + @Size(max = 9, message = "评论图片地址数组长度不能超过 9 张") + private List picUrls; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentCreateReqVO.java new file mode 100644 index 000000000..f976b756d --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.product.controller.admin.comment.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品评价创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductCommentCreateReqVO extends ProductCommentBaseVO { + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentPageReqVO.java new file mode 100644 index 000000000..3791f572e --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentPageReqVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.product.controller.admin.comment.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 商品评价分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductCommentPageReqVO extends PageParam { + + @Schema(description = "评价人名称", example = "王二狗") + private String userNickname; + + @Schema(description = "交易订单编号", example = "24428") + private Long orderId; + + @Schema(description = "商品SPU编号", example = "29502") + private Long spuId; + + @Schema(description = "商品SPU名称", example = "感冒药") + private String spuName; + + @Schema(description = "评分星级 1-5 分", example = "5") + @InEnum(ProductCommentScoresEnum.class) + private Integer scores; + + @Schema(description = "商家是否回复", example = "true") + private Boolean replyStatus; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentReplyReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentReplyReqVO.java new file mode 100644 index 000000000..97ad39d7a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentReplyReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.product.controller.admin.comment.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 商品评价的商家回复 Request VO") +@Data +@ToString(callSuper = true) +public class ProductCommentReplyReqVO { + + @Schema(description = "评价编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15721") + @NotNull(message = "评价编号不能为空") + private Long id; + + @Schema(description = "商家回复内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "谢谢亲") + @NotEmpty(message = "商家回复内容不能为空") + private String replyContent; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentRespVO.java new file mode 100644 index 000000000..d03359f7f --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentRespVO.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.product.controller.admin.comment.vo; + +import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 商品评价 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductCommentRespVO extends ProductCommentBaseVO { + + @Schema(description = "订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24965") + private Long id; + + @Schema(description = "是否匿名", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean anonymous; + + @Schema(description = "交易订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24428") + private Long orderId; + + @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean visible; + + @Schema(description = "商家是否回复", requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean replyStatus; + + @Schema(description = "回复管理员编号", example = "9527") + private Long replyUserId; + + @Schema(description = "商家回复内容", example = "感谢好评哦亲(づ ̄3 ̄)づ╭❤~") + private String replyContent; + + @Schema(description = "商家回复时间", example = "2023-08-08 12:20:55") + private LocalDateTime replyTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "评分星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + private Integer scores; + + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉丝滑透气小短袖") + @NotNull(message = "商品 SPU 编号不能为空") + private Long spuId; + + @Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @NotNull(message = "商品 SPU 名称不能为空") + private String spuName; + + @Schema(description = "商品 SKU 图片地址", example = "https://www.iocoder.cn/yudao.jpg") + private String skuPicUrl; + + @Schema(description = "商品 SKU 规格值数组") + private List skuProperties; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentUpdateVisibleReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentUpdateVisibleReqVO.java new file mode 100644 index 000000000..c88fbaf34 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/comment/vo/ProductCommentUpdateVisibleReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.product.controller.admin.comment.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 商品评价可见修改 Request VO") +@Data +@ToString(callSuper = true) +public class ProductCommentUpdateVisibleReqVO { + + @Schema(description = "评价编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15721") + @NotNull(message = "评价编号不能为空") + private Long id; + + @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否可见不能为空") + private Boolean visible; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java new file mode 100644 index 000000000..831319ed5 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java @@ -0,0 +1,100 @@ +package cn.iocoder.yudao.module.product.controller.admin.property; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.*; +import cn.iocoder.yudao.module.product.convert.property.ProductPropertyConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO; +import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "管理后台 - 商品属性项") +@RestController +@RequestMapping("/product/property") +@Validated +public class ProductPropertyController { + + @Resource + private ProductPropertyService productPropertyService; + @Resource + private ProductPropertyValueService productPropertyValueService; + + @PostMapping("/create") + @Operation(summary = "创建属性项") + @PreAuthorize("@ss.hasPermission('product:property:create')") + public CommonResult createProperty(@Valid @RequestBody ProductPropertyCreateReqVO createReqVO) { + return success(productPropertyService.createProperty(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新属性项") + @PreAuthorize("@ss.hasPermission('product:property:update')") + public CommonResult updateProperty(@Valid @RequestBody ProductPropertyUpdateReqVO updateReqVO) { + productPropertyService.updateProperty(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除属性项") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('product:property:delete')") + public CommonResult deleteProperty(@RequestParam("id") Long id) { + productPropertyService.deleteProperty(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得属性项") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:property:query')") + public CommonResult getProperty(@RequestParam("id") Long id) { + return success(ProductPropertyConvert.INSTANCE.convert(productPropertyService.getProperty(id))); + } + + @GetMapping("/list") + @Operation(summary = "获得属性项列表") + @PreAuthorize("@ss.hasPermission('product:property:query')") + public CommonResult> getPropertyList(@Valid ProductPropertyListReqVO listReqVO) { + return success(ProductPropertyConvert.INSTANCE.convertList(productPropertyService.getPropertyList(listReqVO))); + } + + @GetMapping("/page") + @Operation(summary = "获得属性项分页") + @PreAuthorize("@ss.hasPermission('product:property:query')") + public CommonResult> getPropertyPage(@Valid ProductPropertyPageReqVO pageVO) { + return success(ProductPropertyConvert.INSTANCE.convertPage(productPropertyService.getPropertyPage(pageVO))); + } + + @PostMapping("/get-value-list") + @Operation(summary = "获得属性项列表") + @PreAuthorize("@ss.hasPermission('product:property:query')") + public CommonResult> getPropertyAndValueList( + @Valid @RequestBody ProductPropertyListReqVO listReqVO) { + // 查询属性项 + List keys = productPropertyService.getPropertyList(listReqVO); + if (CollUtil.isEmpty(keys)) { + return success(Collections.emptyList()); + } + // 查询属性值 + List values = productPropertyValueService.getPropertyValueListByPropertyId( + convertSet(keys, ProductPropertyDO::getId)); + return success(ProductPropertyConvert.INSTANCE.convertList(keys, values)); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java new file mode 100644 index 000000000..54ce881d1 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.product.controller.admin.property; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO; +import cn.iocoder.yudao.module.product.convert.property.ProductPropertyValueConvert; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 商品属性值") +@RestController +@RequestMapping("/product/property/value") +@Validated +public class ProductPropertyValueController { + + @Resource + private ProductPropertyValueService productPropertyValueService; + + @PostMapping("/create") + @Operation(summary = "创建属性值") + @PreAuthorize("@ss.hasPermission('product:property:create')") + public CommonResult createPropertyValue(@Valid @RequestBody ProductPropertyValueCreateReqVO createReqVO) { + return success(productPropertyValueService.createPropertyValue(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新属性值") + @PreAuthorize("@ss.hasPermission('product:property:update')") + public CommonResult updatePropertyValue(@Valid @RequestBody ProductPropertyValueUpdateReqVO updateReqVO) { + productPropertyValueService.updatePropertyValue(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除属性值") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:property:delete')") + public CommonResult deletePropertyValue(@RequestParam("id") Long id) { + productPropertyValueService.deletePropertyValue(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得属性值") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:property:query')") + public CommonResult getPropertyValue(@RequestParam("id") Long id) { + return success(ProductPropertyValueConvert.INSTANCE.convert(productPropertyValueService.getPropertyValue(id))); + } + + @GetMapping("/page") + @Operation(summary = "获得属性值分页") + @PreAuthorize("@ss.hasPermission('product:property:query')") + public CommonResult> getPropertyValuePage(@Valid ProductPropertyValuePageReqVO pageVO) { + return success(ProductPropertyValueConvert.INSTANCE.convertPage(productPropertyValueService.getPropertyValuePage(pageVO))); + } +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java new file mode 100644 index 000000000..6ef051451 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.property; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 商品属性项 + 属性值 Response VO") +@Data +public class ProductPropertyAndValueRespVO { + + @Schema(description = "属性项的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "属性项的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "颜色") + private String name; + + /** + * 属性值的集合 + */ + private List values; + + @Schema(description = "管理后台 - 属性值的简单 Response VO") + @Data + public static class Value { + + @Schema(description = "属性值的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long id; + + @Schema(description = "属性值的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "红色") + private String name; + + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java new file mode 100644 index 000000000..0006bd7b3 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.property; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 商品属性项 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class ProductPropertyBaseVO { + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "颜色") + @NotBlank(message = "名称不能为空") + private String name; + + @Schema(description = "备注", example = "颜色") + private String remark; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java new file mode 100644 index 000000000..b854dd73c --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.property; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 属性项创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPropertyCreateReqVO extends ProductPropertyBaseVO { + + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java new file mode 100644 index 000000000..3ff46484f --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.property; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import java.util.List; + +@Schema(description = "管理后台 - 属性项 List Request VO") +@Data +@ToString(callSuper = true) +public class ProductPropertyListReqVO { + + @Schema(description = "属性名称", example = "颜色") + private String name; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java new file mode 100644 index 000000000..97b959d6a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.property; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 属性项 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPropertyPageReqVO extends PageParam { + + @Schema(description = "名称", example = "颜色") + private String name; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java new file mode 100644 index 000000000..5f541230a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.property; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 属性项 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPropertyRespVO extends ProductPropertyBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java new file mode 100644 index 000000000..9319f7cf2 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.property; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 属性项更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPropertyUpdateReqVO extends ProductPropertyBaseVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "主键不能为空") + private Long id; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java new file mode 100644 index 000000000..d46b66b3d --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.value; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** +* 属性值 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class ProductPropertyValueBaseVO { + + @Schema(description = "属性项的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "属性项的编号不能为空") + private Long propertyId; + + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "红色") + @NotEmpty(message = "名称名字不能为空") + private String name; + + @Schema(description = "备注", example = "颜色") + private String remark; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java new file mode 100644 index 000000000..d3fe4d0f1 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.value; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品属性值创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPropertyValueCreateReqVO extends ProductPropertyValueBaseVO { + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueDetailRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueDetailRespVO.java new file mode 100644 index 000000000..4d22f0dbf --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueDetailRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.value; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品属性值的明细 Response VO") +@Data +public class ProductPropertyValueDetailRespVO { + + @Schema(description = "属性的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long propertyId; + + @Schema(description = "属性的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "颜色") + private String propertyName; + + @Schema(description = "属性值的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long valueId; + + @Schema(description = "属性值的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "红色") + private String valueName; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java new file mode 100644 index 000000000..ff0c32614 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.value; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品属性值分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPropertyValuePageReqVO extends PageParam { + + @Schema(description = "属性项的编号", example = "1024") + private String propertyId; + + @Schema(description = "名称", example = "红色") + private String name; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java new file mode 100644 index 000000000..6ef17c32e --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.value; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 商品属性值 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPropertyValueRespVO extends ProductPropertyValueBaseVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Long id; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java new file mode 100644 index 000000000..d0e0d9382 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.product.controller.admin.property.vo.value; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 商品属性值更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductPropertyValueUpdateReqVO extends ProductPropertyValueBaseVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "主键不能为空") + private Long id; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java new file mode 100755 index 000000000..9acbacd66 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/ProductSkuController.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.product.controller.admin.sku; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "管理后台 - 商品 SKU") +@RestController +@RequestMapping("/product/sku") +@Validated +public class ProductSkuController { + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java new file mode 100755 index 000000000..8f47e9392 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java @@ -0,0 +1,82 @@ +package cn.iocoder.yudao.module.product.controller.admin.sku.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** +* 商品 SKU Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class ProductSkuBaseVO { + + @Schema(description = "商品 SKU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉小短袖") + @NotEmpty(message = "商品 SKU 名字不能为空") + private String name; + + @Schema(description = "销售价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1999") + @NotNull(message = "销售价格,单位:分不能为空") + private Integer price; + + @Schema(description = "市场价", example = "2999") + private Integer marketPrice; + + @Schema(description = "成本价", example = "19") + private Integer costPrice; + + @Schema(description = "条形码", example = "15156165456") + private String barCode; + + @Schema(description = "图片地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png") + @NotNull(message = "图片地址不能为空") + private String picUrl; + + @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "库存不能为空") + private Integer stock; + + @Schema(description = "预警预存", example = "10") + private Integer warnStock; + + @Schema(description = "商品重量,单位:kg 千克", example = "1.2") + private Double weight; + + @Schema(description = "商品体积,单位:m^3 平米", example = "2.5") + private Double volume; + + @Schema(description = "一级分销的佣金,单位:分", example = "199") + private Integer firstBrokeragePrice; + + @Schema(description = "二级分销的佣金,单位:分", example = "19") + private Integer secondBrokeragePrice; + + @Schema(description = "属性数组") + private List properties; + + @Schema(description = "商品属性") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Property { + + @Schema(description = "属性编号", example = "10") + private Long propertyId; + + @Schema(description = "属性名字", example = "颜色") + private String propertyName; + + @Schema(description = "属性值编号", example = "10") + private Long valueId; + + @Schema(description = "属性值名字", example = "红色") + private String valueName; + + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java new file mode 100755 index 000000000..e750013d5 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.product.controller.admin.sku.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +@Schema(description = "管理后台 - 商品 SKU 创建/更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductSkuCreateOrUpdateReqVO extends ProductSkuBaseVO { + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java new file mode 100755 index 000000000..42d72e00e --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuRespVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.product.controller.admin.sku.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import javax.validation.constraints.NotNull; +import java.util.List; + +@Schema(description = "管理后台 - 商品 SKU Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductSkuRespVO extends ProductSkuBaseVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.http b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.http new file mode 100644 index 000000000..4ab7b4f71 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.http @@ -0,0 +1,4 @@ +### 获得商品 SPU 明细 +GET {{baseUrl}}/product/spu/get-detail?id=4 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java new file mode 100755 index 000000000..10c1a9ca2 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java @@ -0,0 +1,147 @@ +package cn.iocoder.yudao.module.product.controller.admin.spu; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*; +import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +//import cn.iocoder.yudao.module.promotion.api.coupon.CouponTemplateApi; +//import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS; + +@Tag(name = "管理后台 - 商品 SPU") +@RestController +@RequestMapping("/product/spu") +@Validated +public class ProductSpuController { + + @Resource + private ProductSpuService productSpuService; + @Resource + private ProductSkuService productSkuService; +// +// @Resource +// private CouponTemplateApi couponTemplateApi; + + @PostMapping("/create") + @Operation(summary = "创建商品 SPU") + @PreAuthorize("@ss.hasPermission('product:spu:create')") + public CommonResult createProductSpu(@Valid @RequestBody ProductSpuCreateReqVO createReqVO) { + return success(productSpuService.createSpu(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品 SPU") + @PreAuthorize("@ss.hasPermission('product:spu:update')") + public CommonResult updateSpu(@Valid @RequestBody ProductSpuUpdateReqVO updateReqVO) { + productSpuService.updateSpu(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新商品 SPU Status") + @PreAuthorize("@ss.hasPermission('product:spu:update')") + public CommonResult updateStatus(@Valid @RequestBody ProductSpuUpdateStatusReqVO updateReqVO) { + productSpuService.updateSpuStatus(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商品 SPU") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:spu:delete')") + public CommonResult deleteSpu(@RequestParam("id") Long id) { + productSpuService.deleteSpu(id); + return success(true); + } + + @GetMapping("/get-detail") + @Operation(summary = "获得商品 SPU 明细") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('product:spu:query')") + public CommonResult getSpuDetail(@RequestParam("id") Long id) { + // 获得商品 SPU + ProductSpuDO spu = productSpuService.getSpu(id); + if (spu == null) { + throw exception(SPU_NOT_EXISTS); + } + // 查询商品 SKU + List skus = productSkuService.getSkuListBySpuId(spu.getId()); + // 查询优惠卷 + // TODO @puhui999:优惠劵的信息,要不交给前端读取?主要是为了避免商品依赖 promotion 模块哈; +// List couponTemplateList = couponTemplateApi.getCouponTemplateListByIds( +// spu.getGiveCouponTemplateIds()); + return success(ProductSpuConvert.INSTANCE.convertForSpuDetailRespVO(spu, skus)); + } + + @GetMapping("/list-all-simple") + @Operation(summary = "获得商品 SPU 精简列表") + @PreAuthorize("@ss.hasPermission('product:spu:query')") + public CommonResult> getSpuSimpleList() { + List list = productSpuService.getSpuListByStatus(ProductSpuStatusEnum.ENABLE.getStatus()); + // 降序排序后,返回给前端 + list.sort(Comparator.comparing(ProductSpuDO::getSort).reversed()); + return success(ProductSpuConvert.INSTANCE.convertList02(list)); + } + + @GetMapping("/list") + @Operation(summary = "获得商品 SPU 详情列表") + @Parameter(name = "spuIds", description = "spu 编号列表", required = true, example = "[1,2,3]") + @PreAuthorize("@ss.hasPermission('product:spu:query')") + public CommonResult> getSpuList(@RequestParam("spuIds") Collection spuIds) { + return success(ProductSpuConvert.INSTANCE.convertForSpuDetailRespListVO( + productSpuService.getSpuList(spuIds), productSkuService.getSkuListBySpuId(spuIds))); + } + + @GetMapping("/page") + @Operation(summary = "获得商品 SPU 分页") + @PreAuthorize("@ss.hasPermission('product:spu:query')") + public CommonResult> getSpuPage(@Valid ProductSpuPageReqVO pageVO) { + return success(ProductSpuConvert.INSTANCE.convertPage(productSpuService.getSpuPage(pageVO))); + } + + @GetMapping("/get-count") + @Operation(summary = "获得商品 SPU 分页 tab count") + @PreAuthorize("@ss.hasPermission('product:spu:query')") + public CommonResult> getSpuCount() { + return success(productSpuService.getTabsCount()); + } + + @GetMapping("/export") + @Operation(summary = "导出商品") + @PreAuthorize("@ss.hasPermission('product:spu:export')") + @OperateLog(type = EXPORT) + public void exportUserList(@Validated ProductSpuExportReqVO reqVO, + HttpServletResponse response) throws IOException { + List spuList = productSpuService.getSpuList(reqVO); + // 导出 Excel + List datas = ProductSpuConvert.INSTANCE.convertList03(spuList); + ExcelUtils.write(response, "商品列表.xls", "数据", ProductSpuExcelVO.class, datas); + } + +} 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..7f2c22c95 --- /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,126 @@ +package cn.iocoder.yudao.module.product.controller.admin.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; + +/** +* 商品 SPU Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @author HUIHUI + */ +@Data +public class ProductSpuBaseVO { + + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉小短袖") + @NotEmpty(message = "商品名称不能为空") + private String name; + + @Schema(description = "关键字", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉丝滑不出汗") + @NotEmpty(message = "商品关键字不能为空") + private String keyword; + + @Schema(description = "商品简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉小短袖简介") + @NotEmpty(message = "商品简介不能为空") + private String introduction; + + @Schema(description = "商品详情", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉小短袖详情") + @NotEmpty(message = "商品详情不能为空") + private String description; + + @Schema(description = "商品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "商品分类不能为空") + private Long categoryId; + + @Schema(description = "商品品牌编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "商品品牌不能为空") + private Long brandId; + + @Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png") + @NotEmpty(message = "商品封面图不能为空") + private String picUrl; + + @Schema(description = "商品轮播图", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/xx.png, https://www.iocoder.cn/xxx.png]") + private List sliderPicUrls; + + @Schema(description = "商品视频", example = "https://www.iocoder.cn/xx.mp4") + private String videoUrl; + + @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "商品单位不能为空") + private Integer unit; + + @Schema(description = "排序字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "商品排序字段不能为空") + private Integer sort; + + // ========== SKU 相关字段 ========= + + @Schema(description = "规格类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "商品规格类型不能为空") + private Boolean specType; + + // ========== 物流相关字段 ========= + + @Schema(description = "物流配置模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") + @NotNull(message = "物流配置模板编号不能为空") + private Long deliveryTemplateId; + + // ========== 营销相关字段 ========= + + @Schema(description = "是否热卖推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "商品推荐不能为空") + private Boolean recommendHot; + + @Schema(description = "是否优惠推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "商品推荐不能为空") + private Boolean recommendBenefit; + + @Schema(description = "是否精品推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "商品推荐不能为空") + private Boolean recommendBest; + + @Schema(description = "是否新品推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "商品推荐不能为空") + private Boolean recommendNew; + + @Schema(description = "是否优品推荐", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "商品推荐不能为空") + private Boolean recommendGood; + + @Schema(description = "赠送积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") + @NotNull(message = "商品赠送积分不能为空") + private Integer giveIntegral; + + @Schema(description = "赠送的优惠劵数组包含优惠券编号和名称") + private List giveCouponTemplates; + + @Schema(description = "分销类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "商品分销类型不能为空") + private Boolean subCommissionType; + + @Schema(description = "活动展示顺序", example = "[1, 3, 2, 4, 5]") + private List activityOrders; + + // ========== 统计相关字段 ========= + + @Schema(description = "虚拟销量", example = "66") + private Integer virtualSalesCount; + + @Schema(description = "管理后台 - 商品 SPU 赠送的优惠卷") + @Data + public static class GiveCouponTemplate { + + @Schema(description = "模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送") + private String name; + + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/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..baf6adfab --- /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 相关字段 ========= + + @Schema(description = "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..1be96632d --- /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,34 @@ +package cn.iocoder.yudao.module.product.controller.admin.spu.vo; + +import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO; +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") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductSpuDetailRespVO extends ProductSpuBaseVO { + + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1212") + private Long id; + + @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + private Integer salesCount; + + @Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20000") + private Integer browseCount; + + @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + // ========== SKU 相关字段 ========= + + @Schema(description = "SKU 数组") + 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/ProductSpuExcelVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuExcelVO.java new file mode 100644 index 000000000..38cbd8472 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuExcelVO.java @@ -0,0 +1,112 @@ +package cn.iocoder.yudao.module.product.controller.admin.spu.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.product.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + + +import java.time.LocalDateTime; + +/** + * 商品 Spu Excel 导出 VO TODO 暂定 + * + * @author HUIHUI + */ +@Data +public class ProductSpuExcelVO { + + @ExcelProperty("商品编号") + private Long id; + + @ExcelProperty("商品名称") + private String name; + + @ExcelProperty("关键字") + private String keyword; + + @ExcelProperty("商品简介") + private String introduction; + + @ExcelProperty("商品详情") + private String description; + + @ExcelProperty("条形码") + private String barCode; + + @ExcelProperty("商品分类编号") + private Long categoryId; + + @ExcelProperty("商品品牌编号") + private Long brandId; + + @ExcelProperty("商品封面图") + private String picUrl; + + @ExcelProperty("商品视频") + private String videoUrl; + + @ExcelProperty(value = "商品单位", converter = DictConvert.class) + @DictFormat(DictTypeConstants.PRODUCT_UNIT) + private Integer unit; + + @ExcelProperty("排序字段") + private Integer sort; + + @ExcelProperty(value = "商品状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.PRODUCT_SPU_STATUS) + private Integer status; + + @ExcelProperty("规格类型") + private Boolean specType; + + @ExcelProperty("商品价格") + private Integer price; + + @ExcelProperty("市场价") + private Integer marketPrice; + + @ExcelProperty("成本价") + private Integer costPrice; + + @ExcelProperty("库存") + private Integer stock; + + @ExcelProperty("物流配置模板编号") + private Long deliveryTemplateId; + + @ExcelProperty("是否热卖推荐") + private Boolean recommendHot; + + @ExcelProperty("是否优惠推荐") + private Boolean recommendBenefit; + + @ExcelProperty("是否精品推荐") + private Boolean recommendBest; + + @ExcelProperty("是否新品推荐") + private Boolean recommendNew; + + @ExcelProperty("是否优品推荐") + private Boolean recommendGood; + + @ExcelProperty("赠送积分") + private Integer giveIntegral; + + @ExcelProperty("分销类型") + private Boolean subCommissionType; + + @ExcelProperty("商品销量") + private Integer salesCount; + + @ExcelProperty("虚拟销量") + private Integer virtualSalesCount; + + @ExcelProperty("商品点击量") + private Integer browseCount; + + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuExportReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuExportReqVO.java new file mode 100644 index 000000000..3b3dccd7e --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuExportReqVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.product.controller.admin.spu.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +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 = "管理后台 - 商品 SPU 导出 Request VO,参数和 ProductSpuPageReqVO 是一致的") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductSpuExportReqVO { + + @Schema(description = "商品名称", example = "清凉小短袖") + private String name; + + @Schema(description = "前端请求的tab类型", example = "1") + private Integer tabType; + + @Schema(description = "商品分类编号", example = "100") + private Long categoryId; + + @Schema(description = "创建时间", example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/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..81cff4210 --- /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,58 @@ +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; +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 = "管理后台 - 商品 SPU 分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductSpuPageReqVO extends PageParam { + + /** + * 出售中商品 + */ + public static final Integer FOR_SALE = 0; + + /** + * 仓库中商品 + */ + public static final Integer IN_WAREHOUSE = 1; + + /** + * 已售空商品 + */ + public static final Integer SOLD_OUT = 2; + + /** + * 警戒库存 + */ + public static final Integer ALERT_STOCK = 3; + + /** + * 商品回收站 + */ + public static final Integer RECYCLE_BIN = 4; + + @Schema(description = "商品名称", example = "清凉小短袖") + private String name; + + @Schema(description = "前端请求的tab类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer tabType; + + @Schema(description = "商品分类编号", example = "1") + private Long categoryId; + + @Schema(description = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/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..0148cb2a1 --- /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,43 @@ +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 = "spuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") + private Long id; + + @Schema(description = "商品价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "1999") + private Integer price; + + @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") + private Integer salesCount; + + @Schema(description = "市场价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "199") + private Integer marketPrice; + + @Schema(description = "成本价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "19") + private Integer costPrice; + + @Schema(description = "商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + private Integer stock; + + @Schema(description = "商品创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-05-24 00:00:00") + private LocalDateTime createTime; + + @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") + private Integer browseCount; + +} 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..7d9d0d05d --- /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,41 @@ +package cn.iocoder.yudao.module.product.controller.admin.spu.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品 SPU 精简 Response VO") +@Data +@ToString(callSuper = true) +public class ProductSpuSimpleRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "213") + private Long id; + + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉小短袖") + private String name; + + @Schema(description = "商品价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1999") + private Integer price; + + @Schema(description = "商品市场价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "199") + private Integer marketPrice; + + @Schema(description = "商品成本价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "19") + private Integer costPrice; + + @Schema(description = "商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") + private Integer stock; + + // ========== 统计相关字段 ========= + + @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Integer salesCount; + + @Schema(description = "商品虚拟销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20000") + private Integer virtualSalesCount; + + @Schema(description = "商品浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000") + private Integer browseCount; + +} 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..bb69eb4cf --- /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,41 @@ +package cn.iocoder.yudao.module.product.controller.admin.spu.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +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 = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "商品编号不能为空") + private Long id; + + @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1999") + private Integer salesCount; + + @Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1999") + private Integer browseCount; + + @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @InEnum(ProductSpuStatusEnum.class) + private Integer status; + + // ========== SKU 相关字段 ========= + + @Schema(description = "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/ProductSpuUpdateStatusReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuUpdateStatusReqVO.java new file mode 100644 index 000000000..e36e68466 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuUpdateStatusReqVO.java @@ -0,0 +1,23 @@ +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.ProductSpuStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 商品 SPU Status 更新 Request VO") +@Data +public class ProductSpuUpdateStatusReqVO{ + + @Schema(description = "商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "商品编号不能为空") + private Long id; + + @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "商品状态不能为空") + @InEnum(ProductSpuStatusEnum.class) + private Integer status; + +} 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..02e5f171a --- /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 = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long id; + + @Schema(description = "父分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "父分类编号不能为空") + private Long parentId; + + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "办公文具") + @NotBlank(message = "分类名称不能为空") + private String name; + + @Schema(description = "分类图片", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "分类图片不能为空") + private String picUrl; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppCommentController.http b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppCommentController.http new file mode 100644 index 000000000..e69de29bb diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppProductCommentController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppProductCommentController.java new file mode 100644 index 000000000..5f997fdec --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/AppProductCommentController.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.product.controller.app.comment; + +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.app.comment.vo.AppCommentPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO; +import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; +import cn.iocoder.yudao.module.product.service.comment.ProductCommentService; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.context.annotation.Lazy; +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 java.util.Set; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "用户 APP - 商品评价") +@RestController +@RequestMapping("/product/comment") +@Validated +public class AppProductCommentController { + + @Resource + private ProductCommentService productCommentService; + + @Resource + @Lazy + private ProductSkuService productSkuService; + + @GetMapping("/list") + @Operation(summary = "获得最近的 n 条商品评价") + @Parameters({ + @Parameter(name = "spuId", description = "商品 SPU 编号", required = true, example = "1024"), + @Parameter(name = "count", description = "数量", required = true, example = "10") + }) + public CommonResult> getCommentList( + @RequestParam("spuId") Long spuId, + @RequestParam(value = "count", defaultValue = "10") Integer count) { + return success(productCommentService.getCommentList(spuId, count)); + } + + @GetMapping("/page") + @Operation(summary = "获得商品评价分页") + public CommonResult> getCommentPage(@Valid AppCommentPageReqVO pageVO) { + // 查询评论分页 + PageResult commentPageResult = productCommentService.getCommentPage(pageVO, Boolean.TRUE); + if (CollUtil.isEmpty(commentPageResult.getList())) { + return success(PageResult.empty(commentPageResult.getTotal())); + } + + // 拼接返回 + Set skuIds = convertSet(commentPageResult.getList(), ProductCommentDO::getSkuId); + PageResult commentVOPageResult = ProductCommentConvert.INSTANCE.convertPage02( + commentPageResult, productSkuService.getSkuList(skuIds)); + return success(commentVOPageResult); + } + + // TODO 芋艿:需要搞下 + @GetMapping("/statistics") + @Operation(summary = "获得商品的评价统计") + public CommonResult getCommentStatistics(@Valid @RequestParam("spuId") Long spuId) { + return success(productCommentService.getCommentStatistics(spuId, Boolean.TRUE)); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentPageReqVO.java new file mode 100644 index 000000000..a81dcc3eb --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentPageReqVO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.product.controller.app.comment.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 javax.validation.constraints.NotNull; + +@Schema(description = "用户 App - 商品评价分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AppCommentPageReqVO extends PageParam { + + /** + * 好评 + */ + public static final Integer GOOD_COMMENT = 1; + /** + * 中评 + */ + public static final Integer MEDIOCRE_COMMENT = 2; + /** + * 差评 + */ + public static final Integer NEGATIVE_COMMENT = 3; + + @Schema(description = "商品SPU编号", example = "29502") + @NotNull(message = "商品SPU编号不能为空") + private Long spuId; + + @Schema(description = "app 评论页 tab 类型 (0 全部、1 好评、2 中评、3 差评)", example = "0") + @NotNull(message = "商品SPU编号不能为空") + private Integer type; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentStatisticsRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentStatisticsRespVO.java new file mode 100644 index 000000000..e863ab02c --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppCommentStatisticsRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.product.controller.app.comment.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +@Schema(description = "APP - 商品评价页评论分类数统计 Response VO") +@Data +@ToString(callSuper = true) +public class AppCommentStatisticsRespVO { + + @Schema(description = "好评数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15721") + private Long goodCount; + + @Schema(description = "中评数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15721") + private Long mediocreCount; + + @Schema(description = "差评数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15721") + private Long negativeCount; + + @Schema(description = "总平均分", requiredMode = Schema.RequiredMode.REQUIRED, example = "3.55") + private Double scores; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppProductCommentRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppProductCommentRespVO.java new file mode 100644 index 000000000..2ea3af496 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/comment/vo/AppProductCommentRespVO.java @@ -0,0 +1,98 @@ +package cn.iocoder.yudao.module.product.controller.app.comment.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 lombok.ToString; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "用户 App - 商品评价详情 Response VO") +@Data +@ToString(callSuper = true) +public class AppProductCommentRespVO { + + @Schema(description = "评价人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15721") + private Long userId; + + @Schema(description = "评价人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + private String userNickname; + + @Schema(description = "评价人头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png") + private String userAvatar; + + @Schema(description = "订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24965") + private Long id; + + @Schema(description = "是否匿名", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean anonymous; + + @Schema(description = "交易订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24428") + private Long orderId; + + @Schema(description = "交易订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8233") + private Long orderItemId; + + @Schema(description = "商家是否回复", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean replyStatus; + + @Schema(description = "回复管理员编号", example = "22212") + private Long replyUserId; + + @Schema(description = "商家回复内容", example = "亲,你的好评就是我的动力(*^▽^*)") + private String replyContent; + + @Schema(description = "商家回复时间") + private LocalDateTime replyTime; + + @Schema(description = "追加评价内容", example = "穿了很久都很丝滑诶") + private String additionalContent; + + @Schema(description = "追评评价图片地址数组,以逗号分隔最多上传 9 张", example = "[https://www.iocoder.cn/xx.png, https://www.iocoder.cn/xxx.png]") + private List additionalPicUrls; + + @Schema(description = "追加评价时间") + private LocalDateTime additionalTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "91192") + @NotNull(message = "商品 SPU 编号不能为空") + private Long spuId; + + @Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉丝滑小短袖") + @NotNull(message = "商品 SPU 名称不能为空") + private String spuName; + + @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "81192") + @NotNull(message = "商品 SKU 编号不能为空") + private Long skuId; + + @Schema(description = "商品 SKU 属性", requiredMode = Schema.RequiredMode.REQUIRED) + private List skuProperties; + + @Schema(description = "评分星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + @NotNull(message = "评分星级 1-5 分不能为空") + private Integer scores; + + @Schema(description = "描述星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + @NotNull(message = "描述星级 1-5 分不能为空") + private Integer descriptionScores; + + @Schema(description = "服务星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + @NotNull(message = "服务星级 1-5 分不能为空") + private Integer benefitScores; + + @Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "哇,真的很丝滑凉快诶,好评") + @NotNull(message = "评论内容不能为空") + private String content; + + @Schema(description = "评论图片地址数组,以逗号分隔最多上传 9 张", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/xx.png]") + @Size(max = 9, message = "评论图片地址数组长度不能超过 9 张") + private List picUrls; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/AppFavoriteController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/AppFavoriteController.java new file mode 100644 index 000000000..7e54c8ed9 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/AppFavoriteController.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.product.controller.app.favorite; + +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.framework.security.core.annotations.PreAuthenticated; +import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoriteBatchReqVO; +import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; +import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoriteReqVO; +import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoriteRespVO; +import cn.iocoder.yudao.module.product.convert.favorite.ProductFavoriteConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import cn.iocoder.yudao.module.product.service.favorite.ProductFavoriteService; +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.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "用户 APP - 商品收藏") +@RestController +@RequestMapping("/product/favorite") +public class AppFavoriteController { + + @Resource + private ProductFavoriteService productFavoriteService; + @Resource + private ProductSpuService productSpuService; + + @PostMapping(value = "/create") + @Operation(summary = "添加商品收藏") + @PreAuthenticated + public CommonResult createFavorite(@RequestBody @Valid AppFavoriteReqVO reqVO) { + return success(productFavoriteService.createFavorite(getLoginUserId(), reqVO.getSpuId())); + } + + @PostMapping(value = "/create-list") + @Operation(summary = "添加多个商品收藏") + @PreAuthenticated + public CommonResult createFavoriteList(@RequestBody @Valid AppFavoriteBatchReqVO reqVO) { + // todo @jason:待实现;如果有已经收藏的,不用报错,忽略即可; + return success(true); + } + + @DeleteMapping(value = "/delete") + @Operation(summary = "取消单个商品收藏") + @PreAuthenticated + public CommonResult deleteFavorite(@RequestBody @Valid AppFavoriteReqVO reqVO) { + productFavoriteService.deleteFavorite(getLoginUserId(), reqVO.getSpuId()); + return success(Boolean.TRUE); + } + + @DeleteMapping(value = "/delete-list") + @Operation(summary = "取消多个商品收藏") + @PreAuthenticated + public CommonResult deleteFavoriteList(@RequestBody @Valid AppFavoriteBatchReqVO reqVO) { + // todo @jason:待实现 +// productFavoriteService.deleteFavorite(getLoginUserId(), reqVO.getSpuId()); + return success(Boolean.TRUE); + } + + @GetMapping(value = "/page") + @Operation(summary = "获得商品收藏分页") + @PreAuthenticated + public CommonResult> getFavoritePage(AppFavoritePageReqVO reqVO) { + PageResult favoritePage = productFavoriteService.getFavoritePage(getLoginUserId(), reqVO); + if (CollUtil.isEmpty(favoritePage.getList())) { + return success(PageResult.empty()); + } + + // 得到商品 spu 信息 + List favorites = favoritePage.getList(); + List spuIds = convertList(favorites, ProductFavoriteDO::getSpuId); + List spus = productSpuService.getSpuList(spuIds); + + // 转换 VO 结果 + PageResult pageResult = new PageResult<>(favoritePage.getTotal()); + pageResult.setList(ProductFavoriteConvert.INSTANCE.convertList(favorites, spus)); + return success(pageResult); + } + + @GetMapping(value = "/exits") + @Operation(summary = "检查是否收藏过商品") + @PreAuthenticated + public CommonResult isFavoriteExists(AppFavoriteReqVO reqVO) { + ProductFavoriteDO favorite = productFavoriteService.getFavorite(getLoginUserId(), reqVO.getSpuId()); + return success(favorite != null); + } + + @GetMapping(value = "/get-count") + @Operation(summary = "获得商品收藏数量") + @PreAuthenticated + public CommonResult getFavoriteCount() { + return success(productFavoriteService.getFavoriteCount(getLoginUserId())); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteBatchReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteBatchReqVO.java new file mode 100644 index 000000000..fbb1afb9e --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteBatchReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.product.controller.app.favorite.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.util.List; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +@Schema(description = "用户 APP - 商品收藏的批量 Request VO") // 用于收藏、取消收藏、获取收藏 +@Data +public class AppFavoriteBatchReqVO { + + @Schema(description = "商品 SPU 编号数组", requiredMode = REQUIRED, example = "29502") + @NotEmpty(message = "商品 SPU 编号数组不能为空") + private List spuIds; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoritePageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoritePageReqVO.java new file mode 100644 index 000000000..2aacc3e54 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoritePageReqVO.java @@ -0,0 +1,10 @@ +package cn.iocoder.yudao.module.product.controller.app.favorite.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 App - 商品收藏分页查询 Request VO") +@Data +public class AppFavoritePageReqVO extends PageParam { +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteReqVO.java new file mode 100644 index 000000000..4f95a6bf0 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.product.controller.app.favorite.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +@Schema(description = "用户 APP - 商品收藏的单个 Request VO") // 用于收藏、取消收藏、获取收藏 +@Data +public class AppFavoriteReqVO { + + @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502") + @NotNull(message = "商品 SPU 编号不能为空") + private Long spuId; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteRespVO.java new file mode 100644 index 000000000..db2ea90a4 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteRespVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.product.controller.app.favorite.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +@Schema(description = "用户 App - 商品收藏 Response VO") +@Data +public class AppFavoriteRespVO { + + @Schema(description = "编号", requiredMode = REQUIRED, example = "1") + private Long id; + + @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502") + private Long spuId; + + // ========== 商品相关字段 ========== + + @Schema(description = "商品 SPU 名称", example = "赵六") + private String spuName; + + @Schema(description = "商品封面图", example = "https://domain/pic.png") + private String picUrl; + + @Schema(description = "商品单价", example = "100") + private Integer price; + +} 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..5cac09143 --- /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 = "属性的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long propertyId; + + @Schema(description = "属性的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "颜色") + private String propertyName; + + @Schema(description = "属性值的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long valueId; + + @Schema(description = "属性值的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "红色") + private String valueName; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/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..c391b5873 --- /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,18 @@ +### 获得订单交易的分页(默认) +GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10 +Authorization: Bearer {{appToken}} +tenant-id: {{appTenentId}} + +### 获得订单交易的分页(价格) +GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10&sortField=price&sortAsc=true +Authorization: Bearer {{appToken}} +tenant-id: {{appTenentId}} + +### 获得订单交易的分页(销售) +GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10&sortField=salesCount&sortAsc=true +Authorization: Bearer {{appToken}} +tenant-id: {{appTenentId}} + +### 获得商品 SPU 明细 +GET {{appApi}}/product/spu/get-detail?id=102 +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..322188a26 --- /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,144 @@ +package cn.iocoder.yudao.module.product.controller.app.spu; + +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.member.api.level.MemberLevelApi; +import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +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.AppProductSpuPageRespVO; +import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +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.Collections; +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.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +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 MemberLevelApi memberLevelApi; + @Resource + private MemberUserApi memberUserApi; + + @GetMapping("/list") + @Operation(summary = "获得商品 SPU 列表") + @Parameters({ + @Parameter(name = "recommendType", description = "推荐类型", required = true), // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX 常量 + @Parameter(name = "count", description = "数量", required = true) + }) + public CommonResult> getSpuList( + @RequestParam("recommendType") String recommendType, + @RequestParam(value = "count", defaultValue = "10") Integer count) { + List list = productSpuService.getSpuList(recommendType, count); + if (CollUtil.isEmpty(list)) { + return success(Collections.emptyList()); + } + + // 拼接返回 + List voList = ProductSpuConvert.INSTANCE.convertListForGetSpuList(list); + // 处理 vip 价格 + MemberLevelRespDTO memberLevel = getMemberLevel(); + voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel))); + return success(voList); + } + + @GetMapping("/page") + @Operation(summary = "获得商品 SPU 分页") + public CommonResult> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) { + PageResult pageResult = productSpuService.getSpuPage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 拼接返回 + PageResult voPageResult = ProductSpuConvert.INSTANCE.convertPageForGetSpuPage(pageResult); + // 处理 vip 价格 + MemberLevelRespDTO memberLevel = getMemberLevel(); + voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel))); + return success(voPageResult); + } + + @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); + } + + // 拼接返回 + List skus = productSkuService.getSkuListBySpuId(spu.getId()); + AppProductSpuDetailRespVO detailVO = ProductSpuConvert.INSTANCE.convertForGetSpuDetail(spu, skus); + // 处理 vip 价格 + MemberLevelRespDTO memberLevel = getMemberLevel(); + detailVO.setVipPrice(calculateVipPrice(detailVO.getPrice(), memberLevel)); + return success(detailVO); + } + + private MemberLevelRespDTO getMemberLevel() { + Long userId = getLoginUserId(); + if (userId == null) { + return null; + } + MemberUserRespDTO user = memberUserApi.getUser(userId).getCheckedData(); + if (user.getLevelId() == null || user.getLevelId() <= 0) { + return null; + } + return memberLevelApi.getMemberLevel(user.getLevelId()).getCheckedData(); + } + + /** + * 计算会员 VIP 优惠价格 + * + * @param price 原价 + * @param memberLevel 会员等级 + * @return 优惠价格 + */ + public Integer calculateVipPrice(Integer price, MemberLevelRespDTO memberLevel) { + if (memberLevel == null || memberLevel.getDiscountPercent() == null) { + return 0; + } + Integer newPrice = price * memberLevel.getDiscountPercent() / 100; + return price - newPrice; + } + + // TODO 芋艿:商品的浏览记录; +} 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..c890a7961 --- /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,109 @@ +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 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + // ========== 基本信息 ========= + + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "商品简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个快乐简介") + private String introduction; + + @Schema(description = "商品详情", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是商品描述") + private String description; + + @Schema(description = "商品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long categoryId; + + @Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED) + private String picUrl; + + @Schema(description = "商品轮播图", requiredMode = Schema.RequiredMode.REQUIRED) + private List sliderPicUrls; + + @Schema(description = "商品视频", requiredMode = Schema.RequiredMode.REQUIRED) + private String videoUrl; + + @Schema(description = "单位名", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") + private String unitName; + + // ========== 营销相关字段 ========= + + @Schema(description = "活动排序数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private List activityOrders; + + // ========== SKU 相关字段 ========= + + @Schema(description = "规格类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean specType; + + @Schema(description = "商品价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer price; + + @Schema(description = "市场价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer marketPrice; + + @Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级,计算出折扣后价格 + private Integer vipPrice; + + @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666") + private Integer stock; + + /** + * SKU 数组 + */ + private List skus; + + // ========== 统计相关字段 ========= + + @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, 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 = "销售价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer price; + + @Schema(description = "市场价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer marketPrice; + + @Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级,计算出折扣后价格 + private Integer vipPrice; + + @Schema(description = "图片地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png") + private String picUrl; + + @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, 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/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..30a545b37 --- /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,52 @@ +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"; + + public static final String RECOMMEND_TYPE_HOT = "hot"; + public static final String RECOMMEND_TYPE_BENEFIT = "benefit"; + public static final String RECOMMEND_TYPE_BEST = "best"; + public static final String RECOMMEND_TYPE_NEW = "new"; + public static final String RECOMMEND_TYPE_GOOD = "good"; + + @Schema(description = "分类编号", example = "1") + private Long categoryId; + + @Schema(description = "关键字", example = "好看") + private String keyword; + + @Schema(description = "排序字段", example = "price") // 参见 AppProductSpuPageReqVO.SORT_FIELD_XXX 常量 + private String sortField; + + @Schema(description = "排序方式", example = "true") + private Boolean sortAsc; + + @Schema(description = "推荐类型", example = "hot") // 参见 AppProductSpuPageReqVO.RECOMMEND_TYPE_XXX 常量 + private String recommendType; + + @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/controller/app/spu/vo/AppProductSpuPageRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageRespVO.java new file mode 100644 index 000000000..c4a66afd2 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageRespVO.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.product.controller.app.spu.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "用户 App - 商品 SPU Response VO") +@Data +public class AppProductSpuPageRespVO { + + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED) + private Long categoryId; + + @Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED) + private String picUrl; + + @Schema(description = "商品轮播图", requiredMode = Schema.RequiredMode.REQUIRED) + private List sliderPicUrls; + + @Schema(description = "单位名", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") + private String unitName; + + // ========== SKU 相关字段 ========= + + @Schema(description = "规格类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean specType; + + @Schema(description = "商品价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer price; + + @Schema(description = "市场价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer marketPrice; + + @Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级,计算出折扣后价格 + private Integer vipPrice; + + @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666") + private Integer stock; + + // ========== 营销相关字段 ========= + + @Schema(description = "活动排序数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private List activityOrders; + + // ========== 统计相关字段 ========= + + @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Integer salesCount; + +} 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..e6c72fb49 --- /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,36 @@ +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.ProductBrandSimpleRespVO; +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 convertList1(List list); + + 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/comment/ProductCommentConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/comment/ProductCommentConvert.java new file mode 100644 index 000000000..944eb2bc2 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/comment/ProductCommentConvert.java @@ -0,0 +1,132 @@ +package cn.iocoder.yudao.module.product.convert.comment; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentCreateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentRespVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO; +import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO; +import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * 商品评价 Convert + * + * @author wangzhs + */ +@Mapper +public interface ProductCommentConvert { + + ProductCommentConvert INSTANCE = Mappers.getMapper(ProductCommentConvert.class); + + ProductCommentRespVO convert(ProductCommentDO bean); + + @Mapping(target = "scores", expression = "java(calculateOverallScore(goodCount, mediocreCount, negativeCount))") + AppCommentStatisticsRespVO convert(Long goodCount, Long mediocreCount, Long negativeCount); + + @Named("calculateOverallScore") + default double calculateOverallScore(long goodCount, long mediocreCount, long negativeCount) { + return (goodCount * 5 + mediocreCount * 3 + negativeCount) / (double) (goodCount + mediocreCount + negativeCount); + } + + List convertList(List list); + + PageResult convertPage(PageResult page); + + PageResult convertPage01(PageResult pageResult); + + default PageResult convertPage02(PageResult pageResult, + List skuList) { + Map skuMap = CollectionUtils.convertMap(skuList, ProductSkuDO::getId); + PageResult page = convertPage01(pageResult); + page.getList().forEach(item -> { + // 判断用户是否选择匿名 + if (ObjectUtil.equal(item.getAnonymous(), true)) { + item.setUserNickname(ProductCommentDO.NICKNAME_ANONYMOUS); + } + // 设置 SKU 规格值 + findAndThen(skuMap, item.getSkuId(), + sku -> item.setSkuProperties(convertList01(sku.getProperties()))); + }); + return page; + } + + List convertList01(List properties); + + /** + * 计算综合评分 + * + * @param descriptionScores 描述星级 + * @param benefitScores 服务星级 + * @return 综合评分 + */ + @Named("convertScores") + default Integer convertScores(Integer descriptionScores, Integer benefitScores) { + // 计算评价最终综合评分 最终星数 = (商品评星 + 服务评星) / 2 + BigDecimal sumScore = new BigDecimal(descriptionScores + benefitScores); + BigDecimal divide = sumScore.divide(BigDecimal.valueOf(2L), 0, RoundingMode.DOWN); + return divide.intValue(); + } + + ProductCommentDO convert(ProductCommentCreateReqDTO createReqDTO); + + @Mapping(target = "scores", + expression = "java(convertScores(createReqDTO.getDescriptionScores(), createReqDTO.getBenefitScores()))") + default ProductCommentDO convert(ProductCommentCreateReqDTO createReqDTO, ProductSpuDO spuDO, ProductSkuDO skuDO, MemberUserRespDTO user) { + ProductCommentDO commentDO = convert(createReqDTO); + if (user != null) { + commentDO.setUserId(user.getId()); + commentDO.setUserNickname(user.getNickname()); + commentDO.setUserAvatar(user.getAvatar()); + } + if (spuDO != null) { + commentDO.setSpuId(spuDO.getId()); + commentDO.setSpuName(spuDO.getName()); + } + if (skuDO != null) { + commentDO.setSkuPicUrl(skuDO.getPicUrl()); + commentDO.setSkuProperties(skuDO.getProperties()); + } + return commentDO; + } + + @Mapping(target = "visible", constant = "true") + @Mapping(target = "replyStatus", constant = "false") + @Mapping(target = "userId", constant = "0L") + @Mapping(target = "orderId", constant = "0L") + @Mapping(target = "orderItemId", constant = "0L") + @Mapping(target = "anonymous", expression = "java(Boolean.FALSE)") + @Mapping(target = "scores", + expression = "java(convertScores(createReq.getDescriptionScores(), createReq.getBenefitScores()))") + ProductCommentDO convert(ProductCommentCreateReqVO createReq); + + List convertList02(List list); + + default ProductCommentDO convert(ProductCommentCreateReqVO createReq, ProductSpuDO spu, ProductSkuDO sku) { + ProductCommentDO commentDO = convert(createReq); + if (spu != null) { + commentDO.setSpuId(spu.getId()).setSpuName(spu.getName()); + } + if (sku != null) { + commentDO.setSkuPicUrl(sku.getPicUrl()).setSkuProperties(sku.getProperties()); + } + return commentDO; + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java new file mode 100644 index 000000000..b15afacb2 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.product.convert.favorite; + +import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoriteRespVO; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +@Mapper +public interface ProductFavoriteConvert { + + ProductFavoriteConvert INSTANCE = Mappers.getMapper(ProductFavoriteConvert.class); + + ProductFavoriteDO convert(Long userId, Long spuId); + + @Mapping(target = "id", source = "favorite.id") + @Mapping(target = "spuName", source = "spu.name") + AppFavoriteRespVO convert(ProductSpuDO spu, ProductFavoriteDO favorite); + + default List convertList(List favorites, List spus) { + List resultList = new ArrayList<>(favorites.size()); + Map spuMap = convertMap(spus, ProductSpuDO::getId); + for (ProductFavoriteDO favorite : favorites) { + ProductSpuDO spuDO = spuMap.get(favorite.getSpuId()); + resultList.add(convert(spuDO, favorite)); + } + return resultList; + } + +} 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..368da9416 --- /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,57 @@ +package cn.iocoder.yudao.module.product.convert.property; + +import cn.hutool.core.collection.CollUtil; +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.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +/** + * 属性项 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); + // 如果属性值为空value不为null,返回空列表 + if (CollUtil.isEmpty(values)) { + respVO.setValues(Collections.emptyList()); + }else { + 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/property/ProductPropertyValueConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/property/ProductPropertyValueConvert.java new file mode 100644 index 000000000..5429f35fe --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/property/ProductPropertyValueConvert.java @@ -0,0 +1,55 @@ +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.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..5065a9c40 --- /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,77 @@ +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.ProductSkuRespVO; +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) { + List result = convertList06(list); + result.forEach(item -> item.setSpuId(spuId)); + return result; + } + + ProductSkuRespDTO convert02(ProductSkuDO bean); + + List convertList04(List list); + + /** + * 获得 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 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..8f7194b43 --- /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,116 @@ +package cn.iocoder.yudao.module.product.convert.spu; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*; +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.AppProductSpuPageRespVO; +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.enums.DictTypeConstants; +//import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponTemplateRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +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.convertMultiMap; + +/** + * 商品 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); + + @Mapping(target = "price", expression = "java(spu.getPrice() / 100)") + @Mapping(target = "marketPrice", expression = "java(spu.getMarketPrice() / 100)") + @Mapping(target = "costPrice", expression = "java(spu.getCostPrice() / 100)") + ProductSpuExcelVO convert(ProductSpuDO spu); + + default List convertList03(List list) { + List spuExcelVOs = new ArrayList<>(); + list.forEach(spu -> { + ProductSpuExcelVO spuExcelVO = convert(spu); + spuExcelVOs.add(spuExcelVO); + }); + return spuExcelVOs; + } + + ProductSpuDetailRespVO convert03(ProductSpuDO spu); + + ProductSpuRespDTO convert02(ProductSpuDO bean); + + // ========== 用户 App 相关 ========== + + PageResult convertPageForGetSpuPage(PageResult page); + + default List convertListForGetSpuList(List list) { + // 处理虚拟销量 + list.forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount())); + // 处理 VO 字段 + List voList = convertListForGetSpuList0(list); + for (int i = 0; i < list.size(); i++) { + ProductSpuDO spu = list.get(i); + AppProductSpuPageRespVO spuVO = voList.get(i); + spuVO.setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit())); + } + return voList; + } + + @Named("convertListForGetSpuList0") + List convertListForGetSpuList0(List list); + + default AppProductSpuDetailRespVO convertForGetSpuDetail(ProductSpuDO spu, List skus) { + // 处理 SPU + AppProductSpuDetailRespVO spuVO = convertForGetSpuDetail(spu) + .setSalesCount(spu.getSalesCount() + defaultIfNull(spu.getVirtualSalesCount(), 0)) + .setUnitName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.PRODUCT_UNIT, spu.getUnit())); + // 处理 SKU + spuVO.setSkus(convertListForGetSpuDetail(skus)); + return spuVO; + } + + AppProductSpuDetailRespVO convertForGetSpuDetail(ProductSpuDO spu); + + List convertListForGetSpuDetail(List skus); + + default ProductSpuDetailRespVO convertForSpuDetailRespVO(ProductSpuDO spu, List skus) { + ProductSpuDetailRespVO respVO = convert03(spu); + respVO.setSkus(ProductSkuConvert.INSTANCE.convertList(skus)); + return respVO; + } + + default List convertForSpuDetailRespListVO(List spus, List skus) { + Map> skuMultiMap = convertMultiMap(skus, ProductSkuDO::getSpuId); + return CollectionUtils.convertList(spus, spu -> convert03(spu) + .setSkus(ProductSkuConvert.INSTANCE.convertList(skuMultiMap.get(spu.getId())))); + } + +} 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..bf69e0028 --- /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,68 @@ +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 + * + * @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; + /** + * 限定分类层级 + */ + public static final int CATEGORY_LEVEL = 2; + + /** + * 分类编号 + */ + @TableId + private Long id; + /** + * 父分类编号 + */ + private Long parentId; + /** + * 分类名称 + */ + private String name; + /** + * 移动端分类图 + * + * 建议 180*180 分辨率 + */ + private String picUrl; + /** + * PC 端分类图 + * + * 建议 468*340 分辨率 + */ + private String bigPicUrl; + /** + * 分类排序 + */ + private Integer sort; + /** + * 开启状态 + * + * 枚举 {@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..40b04caf0 --- /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,159 @@ +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.sku.ProductSkuDO; +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.JacksonTypeHandler; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 商品评论 DO + * + * @author 芋道源码 + */ +@TableName(value = "product_comment", autoResultMap = true) +@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 { + + /** + * 默认匿名昵称 + */ + public static final String NICKNAME_ANONYMOUS = "匿名用户"; + + /** + * 评论编号,主键自增 + */ + @TableId + private Long id; + + /** + * 评价人的用户编号 + * + * 关联 MemberUserDO 的 id 编号 + */ + private Long userId; + /** + * 评价人名称 + */ + private String userNickname; + /** + * 评价人头像 + */ + private String userAvatar; + /** + * 是否匿名 + */ + private Boolean anonymous; + + /** + * 交易订单编号 + * + * 关联 TradeOrderDO 的 id 编号 + */ + private Long orderId; + /** + * 交易订单项编号 + * + * 关联 TradeOrderItemDO 的 id 编号 + */ + private Long orderItemId; + + /** + * 商品 SPU 编号 + * + * 关联 {@link ProductSpuDO#getId()} + */ + private Long spuId; + /** + * 商品 SPU 名称 + * + * 关联 {@link ProductSpuDO#getName()} + */ + private String spuName; + /** + * 商品 SKU 编号 + * + * 关联 {@link ProductSkuDO#getId()} + */ + private Long skuId; + /** + * 商品 SKU 图片地址 + * + * 关联 {@link ProductSkuDO#getPicUrl()} + */ + private String skuPicUrl; + /** + * 属性数组,JSON 格式 + * + * 关联 {@link ProductSkuDO#getProperties()} + */ + @TableField(typeHandler = ProductSkuDO.PropertyTypeHandler.class) + private List skuProperties; + + /** + * 是否可见 + * + * true:显示 + * false:隐藏 + */ + private Boolean visible; + /** + * 评分星级 + * + * 1-5 分 + */ + private Integer scores; + /** + * 描述星级 + * + * 1-5 星 + */ + private Integer descriptionScores; + /** + * 服务星级 + * + * 1-5 星 + */ + private Integer benefitScores; + /** + * 评论内容 + */ + private String content; + /** + * 评论图片地址数组 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List picUrls; + + /** + * 商家是否回复 + */ + private Boolean replyStatus; + /** + * 回复管理员编号 + * 关联 AdminUserDO 的 id 编号 + */ + private Long replyUserId; + /** + * 商家回复内容 + */ + private String replyContent; + /** + * 商家回复时间 + */ + private LocalDateTime replyTime; + +} 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..6e00eaa6c --- /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,43 @@ +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; + +} 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..8cc646bd5 --- /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,51 @@ +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 { + + /** + * SPU 单规格时,默认属性 id + */ + public static final Long ID_DEFAULT = 0L; + /** + * SPU 单规格时,默认属性名字 + */ + public static final String NAME_DEFAULT = "默认"; + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 名称 + */ + private String name; + /** + * 状态 + */ + private Integer status; + /** + * 备注 + */ + 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..cefa2d958 --- /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,55 @@ +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 { + + /** + * SPU 单规格时,默认属性值 id + */ + public static final Long ID_DEFAULT = 0L; + /** + * SPU 单规格时,默认属性值名字 + */ + public static final String NAME_DEFAULT = "默认"; + + /** + * 主键 + */ + @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/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..dacb02ec8 --- /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,156 @@ +package cn.iocoder.yudao.module.product.dal.dataobject.sku; + +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; + /** + * 属性数组,JSON 格式 + */ + @TableField(typeHandler = PropertyTypeHandler.class) + private List properties; + /** + * 商品价格,单位:分 + */ + private Integer price; + /** + * 市场价,单位:分 + */ + private Integer marketPrice; + /** + * 成本价,单位:分 + */ + private Integer costPrice; + /** + * 商品条码 + */ + private String barCode; + /** + * 图片地址 + */ + private String picUrl; + /** + * 库存 + */ + private Integer stock; + /** + * 商品重量,单位:kg 千克 + */ + private Double weight; + /** + * 商品体积,单位:m^3 平米 + */ + private Double volume; + + /** + * 一级分销的佣金,单位:分 + */ + private Integer firstBrokeragePrice; + /** + * 二级分销的佣金,单位:分 + */ + private Integer secondBrokeragePrice; + + // ========== 营销相关字段 ========= + + // ========== 统计相关字段 ========= + /** + * 商品销量 + */ + private Integer salesCount; + + /** + * 商品属性 + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Property { + + /** + * 属性编号 + * 关联 {@link ProductPropertyDO#getId()} + */ + private Long propertyId; + /** + * 属性名字 + * 冗余 {@link ProductPropertyDO#getName()} + * + * 注意:每次属性名字发生变化时,需要更新该冗余 + */ + private String propertyName; + + /** + * 属性值编号 + * 关联 {@link ProductPropertyValueDO#getId()} + */ + private Long valueId; + /** + * 属性值名字 + * 冗余 {@link ProductPropertyValueDO#getName()} + * + * 注意:每次属性值名字发生变化时,需要更新该冗余 + */ + private String valueName; + + } + + // TODO @芋艿:可以找一些新的思路 + public static class PropertyTypeHandler extends AbstractJsonTypeHandler { + + @Override + protected Object parse(String json) { + return JsonUtils.parseArray(json, Property.class); + } + + @Override + protected String toJson(Object obj) { + return JsonUtils.toJsonString(obj); + } + + } + + // TODO 芋艿:integral from y + // TODO 芋艿:pinkPrice from y + // TODO 芋艿:seckillPrice from y + // TODO 芋艿:pinkStock from y + // TODO 芋艿:seckillStock from y + +} + diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java new file mode 100755 index 000000000..9ce55a096 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java @@ -0,0 +1,213 @@ +package cn.iocoder.yudao.module.product.dal.dataobject.spu; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO; +import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.*; + +import java.util.List; + +/** + * 商品 SPU DO + * + * @author 芋道源码 + */ +@TableName(value = "product_spu", autoResultMap = true) +@KeySequence("product_spu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductSpuDO extends BaseDO { + + /** + * 商品 SPU 编号,自增 + */ + @TableId + private Long id; + + // ========== 基本信息 ========= + + /** + * 商品名称 + */ + private String name; + /** + * 关键字 + */ + private String keyword; + /** + * 商品简介 + */ + private String introduction; + /** + * 商品详情 + */ + private String description; + // TODO @芋艿:是不是要删除 + /** + * 商品条码(一维码) + */ + private String barCode; + + /** + * 商品分类编号 + * + * 关联 {@link ProductCategoryDO#getId()} + */ + private Long categoryId; + /** + * 商品品牌编号 + * + * 关联 {@link ProductBrandDO#getId()} + */ + private Long brandId; + /** + * 商品封面图 + */ + private String picUrl; + /** + * 商品轮播图 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List sliderPicUrls; + /** + * 商品视频 + */ + private String videoUrl; + + /** + * 单位 + * + * 对应 product_unit 数据字典 + */ + private Integer unit; + /** + * 排序字段 + */ + private Integer sort; + /** + * 商品状态 + * + * 枚举 {@link ProductSpuStatusEnum} + */ + private Integer status; + + // ========== SKU 相关字段 ========= + + /** + * 规格类型 + * + * false - 单规格 + * true - 多规格 + */ + private Boolean specType; + /** + * 商品价格,单位使用:分 + * + * 基于其对应的 {@link ProductSkuDO#getPrice()} sku单价最低的商品的 + */ + private Integer price; + /** + * 市场价,单位使用:分 + * + * 基于其对应的 {@link ProductSkuDO#getMarketPrice()} sku单价最低的商品的 + */ + private Integer marketPrice; + /** + * 成本价,单位使用:分 + * + * 基于其对应的 {@link ProductSkuDO#getCostPrice()} sku单价最低的商品的 + */ + private Integer costPrice; + /** + * 库存 + * + * 基于其对应的 {@link ProductSkuDO#getStock()} 求和 + */ + private Integer stock; + + // ========== 物流相关字段 ========= + + /** + * 物流配置模板编号 + * + * 对应 TradeDeliveryExpressTemplateDO 的 id 编号 + */ + private Long deliveryTemplateId; + + // ========== 营销相关字段 ========= + /** + * 是否热卖推荐 + */ + private Boolean recommendHot; + /** + * 是否优惠推荐 + */ + private Boolean recommendBenefit; + /** + * 是否精品推荐 + */ + private Boolean recommendBest; + /** + * 是否新品推荐 + */ + private Boolean recommendNew; + /** + * 是否优品推荐 + */ + private Boolean recommendGood; + + /** + * 赠送积分 + */ + private Integer giveIntegral; + /** + * 赠送的优惠劵编号的数组 + * + * 对应 CouponTemplateDO 的 id 属性 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List giveCouponTemplateIds; + + // TODO @puhui999:字段估计要改成 brokerageType + /** + * 分销类型 + * + * false - 默认 + * true - 自行设置 + */ + private Boolean subCommissionType; + + /** + * 活动展示顺序 + * + * 对应 PromotionTypeEnum 枚举 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List activityOrders; // TODO @芋艿: 活动顺序字段长度需要增加 + + // ========== 统计相关字段 ========= + + /** + * 商品销量 + */ + private Integer salesCount; + /** + * 虚拟销量 + */ + private Integer virtualSalesCount; + /** + * 浏览量 + */ + private Integer browseCount; +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/brand/ProductBrandMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/brand/ProductBrandMapper.java new file mode 100644 index 000000000..b2c4bd5fb --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/brand/ProductBrandMapper.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.product.dal.mysql.brand; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandListReqVO; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandPageReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ProductBrandMapper extends BaseMapperX { + + default PageResult selectPage(ProductBrandPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ProductBrandDO::getName, reqVO.getName()) + .eqIfPresent(ProductBrandDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ProductBrandDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ProductBrandDO::getId)); + } + + + default List selectList(ProductBrandListReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(ProductBrandDO::getName, reqVO.getName())); + } + + default ProductBrandDO selectByName(String name) { + return selectOne(ProductBrandDO::getName, name); + } + + default List selectListByStatus(Integer status) { + return selectList(ProductBrandDO::getStatus, status); + } +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/ProductCategoryMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/ProductCategoryMapper.java new file mode 100644 index 000000000..fbb88f592 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/category/ProductCategoryMapper.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.product.dal.mysql.category; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 商品分类 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ProductCategoryMapper extends BaseMapperX { + + default List selectList(ProductCategoryListReqVO listReqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(ProductCategoryDO::getName, listReqVO.getName()) + .eqIfPresent(ProductCategoryDO::getParentId, listReqVO.getParentId()) + .eqIfPresent(ProductCategoryDO::getStatus, listReqVO.getStatus()) + .orderByDesc(ProductCategoryDO::getId)); + } + + default Long selectCountByParentId(Long parentId) { + return selectCount(ProductCategoryDO::getParentId, parentId); + } + + default List selectListByStatus(Integer status) { + return selectList(ProductCategoryDO::getStatus, status); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/comment/ProductCommentMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/comment/ProductCommentMapper.java new file mode 100644 index 000000000..387a3736b --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/comment/ProductCommentMapper.java @@ -0,0 +1,79 @@ +package cn.iocoder.yudao.module.product.dal.mysql.comment; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ProductCommentMapper extends BaseMapperX { + + default PageResult selectPage(ProductCommentPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ProductCommentDO::getUserNickname, reqVO.getUserNickname()) + .eqIfPresent(ProductCommentDO::getOrderId, reqVO.getOrderId()) + .eqIfPresent(ProductCommentDO::getSpuId, reqVO.getSpuId()) + .eqIfPresent(ProductCommentDO::getScores, reqVO.getScores()) + .eqIfPresent(ProductCommentDO::getReplyStatus, reqVO.getReplyStatus()) + .betweenIfPresent(ProductCommentDO::getCreateTime, reqVO.getCreateTime()) + .likeIfPresent(ProductCommentDO::getSpuName, reqVO.getSpuName()) + .orderByDesc(ProductCommentDO::getId)); + } + + static void appendTabQuery(LambdaQueryWrapperX queryWrapper, Integer type) { + LambdaQueryWrapperX queryWrapperX = new LambdaQueryWrapperX<>(); + // 构建好评查询语句:好评计算 总评 >= 4 + if (ObjectUtil.equal(type, AppCommentPageReqVO.GOOD_COMMENT)) { + queryWrapperX.ge(ProductCommentDO::getScores, 4); + } + // 构建中评查询语句:中评计算 总评 >= 3 且 总评 < 4 + if (ObjectUtil.equal(type, AppCommentPageReqVO.MEDIOCRE_COMMENT)) { + queryWrapperX.ge(ProductCommentDO::getScores, 3); + queryWrapperX.lt(ProductCommentDO::getScores, 4); + } + // 构建差评查询语句:差评计算 总评 < 3 + if (ObjectUtil.equal(type, AppCommentPageReqVO.NEGATIVE_COMMENT)) { + queryWrapperX.lt(ProductCommentDO::getScores, 3); + } + } + + default PageResult selectPage(AppCommentPageReqVO reqVO, Boolean visible) { + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .eqIfPresent(ProductCommentDO::getSpuId, reqVO.getSpuId()) + .eqIfPresent(ProductCommentDO::getVisible, visible); + // 构建评价查询语句 + appendTabQuery(queryWrapper, reqVO.getType()); + // 按评价时间排序最新的显示在前面 + queryWrapper.orderByDesc(ProductCommentDO::getCreateTime); + return selectPage(reqVO, queryWrapper); + } + + default ProductCommentDO selectByUserIdAndOrderItemId(Long userId, Long orderItemId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ProductCommentDO::getUserId, userId) + .eq(ProductCommentDO::getOrderItemId, orderItemId)); + } + + default Long selectCountBySpuId(Long spuId, Boolean visible, Integer type) { + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .eqIfPresent(ProductCommentDO::getSpuId, spuId) + .eqIfPresent(ProductCommentDO::getVisible, visible); + // 构建评价查询语句 + appendTabQuery(queryWrapper, type); + return selectCount(queryWrapper); + } + + default PageResult selectCommentList(Long spuId, Integer count) { + // 构建分页查询条件 + return selectPage(new PageParam().setPageSize(count), new LambdaQueryWrapperX() + .eqIfPresent(ProductCommentDO::getSpuId, spuId) + .orderByDesc(ProductCommentDO::getCreateTime) + ); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java new file mode 100644 index 000000000..54d9d2dd6 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.product.dal.mysql.favorite; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ProductFavoriteMapper extends BaseMapperX { + + default ProductFavoriteDO selectByUserIdAndSpuId(Long userId, Long spuId) { + return selectOne(ProductFavoriteDO::getUserId, userId, + ProductFavoriteDO::getSpuId, spuId); + } + + default PageResult selectPageByUserAndType(Long userId, AppFavoritePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapper() + .eq(ProductFavoriteDO::getUserId, userId) + .orderByDesc(ProductFavoriteDO::getId)); + } + + default Long selectCountByUserId(Long userId) { + return selectCount(ProductFavoriteDO::getUserId, userId); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyMapper.java new file mode 100644 index 000000000..26f8d5239 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.product.dal.mysql.property; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyListReqVO; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyPageReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ProductPropertyMapper extends BaseMapperX { + + default PageResult selectPage(ProductPropertyPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ProductPropertyDO::getName, reqVO.getName()) + .betweenIfPresent(ProductPropertyDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ProductPropertyDO::getId)); + } + + default ProductPropertyDO selectByName(String name) { + return selectOne(ProductPropertyDO::getName, name); + } + + default List selectList(ProductPropertyListReqVO listReqVO) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(ProductPropertyDO::getName, listReqVO.getName())); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyValueMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyValueMapper.java new file mode 100644 index 000000000..402df51e7 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/property/ProductPropertyValueMapper.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.product.dal.mysql.property; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface ProductPropertyValueMapper extends BaseMapperX { + + default List selectListByPropertyId(Collection propertyIds) { + return selectList(new LambdaQueryWrapperX() + .inIfPresent(ProductPropertyValueDO::getPropertyId, propertyIds)); + } + + default ProductPropertyValueDO selectByName(Long propertyId, String name) { + return selectOne(new LambdaQueryWrapperX() + .eq(ProductPropertyValueDO::getPropertyId, propertyId) + .eq(ProductPropertyValueDO::getName, name)); + } + + default void deleteByPropertyId(Long propertyId) { + delete(new LambdaQueryWrapperX() + .eq(ProductPropertyValueDO::getPropertyId, propertyId)); + } + + default PageResult selectPage(ProductPropertyValuePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ProductPropertyValueDO::getPropertyId, reqVO.getPropertyId()) + .likeIfPresent(ProductPropertyValueDO::getName, reqVO.getName()) + .orderByDesc(ProductPropertyValueDO::getId)); + } + + default Integer selectCountByPropertyId(Long propertyId) { + return selectCount(ProductPropertyValueDO::getPropertyId, propertyId).intValue(); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java new file mode 100755 index 000000000..6da00caf4 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.product.dal.mysql.sku; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface ProductSkuMapper extends BaseMapperX { + + default List selectListBySpuId(Long spuId) { + return selectList(ProductSkuDO::getSpuId, spuId); + } + + default List selectListBySpuId(Collection spuIds) { + return selectList(ProductSkuDO::getSpuId, spuIds); + } + + default void deleteBySpuId(Long spuId) { + delete(new LambdaQueryWrapperX().eq(ProductSkuDO::getSpuId, spuId)); + } + + /** + * 更新 SKU 库存(增加) + * + * @param id 编号 + * @param incrCount 增加库存(正数) + */ + default void updateStockIncr(Long id, Integer incrCount) { + Assert.isTrue(incrCount > 0); + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" stock = stock + " + incrCount) + .eq(ProductSkuDO::getId, id); + update(null, lambdaUpdateWrapper); + } + + /** + * 更新 SKU 库存(减少) + * + * @param id 编号 + * @param incrCount 减少库存(负数) + * @return 更新条数 + */ + default int updateStockDecr(Long id, Integer incrCount) { + Assert.isTrue(incrCount < 0); + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() + .setSql(" stock = stock + " + incrCount) // 负数,所以使用 + 号 + .eq(ProductSkuDO::getId, id) + .ge(ProductSkuDO::getStock, -incrCount); // cas 逻辑 + return update(null, updateWrapper); + } + + default List selectListByAlarmStock() { + return selectList(new QueryWrapper().apply("stock <= warn_stock")); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java new file mode 100755 index 000000000..0448381fa --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java @@ -0,0 +1,169 @@ +package cn.iocoder.yudao.module.product.dal.mysql.spu; + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuExportReqVO; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import cn.iocoder.yudao.module.product.enums.ProductConstants; +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Mapper +public interface ProductSpuMapper extends BaseMapperX { + + /** + * 获取商品 SPU 分页列表数据 + * + * @param reqVO 分页请求参数 + * @return 商品 SPU 分页列表数据 + */ + default PageResult selectPage(ProductSpuPageReqVO reqVO) { + Integer tabType = reqVO.getTabType(); + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .likeIfPresent(ProductSpuDO::getName, reqVO.getName()) + .eqIfPresent(ProductSpuDO::getCategoryId, reqVO.getCategoryId()) + .betweenIfPresent(ProductSpuDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ProductSpuDO::getSort); + appendTabQuery(tabType, queryWrapper); + return selectPage(reqVO, queryWrapper); + } + + /** + * 查询触发警戒库存的 SPU 数量 + * + * @return 触发警戒库存的 SPU 数量 + */ + default Long selectCount() { + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX<>(); + // 库存小于等于警戒库存 + queryWrapper.le(ProductSpuDO::getStock, ProductConstants.ALERT_STOCK) + // 如果库存触发警戒库存且状态为回收站的话则不计入触发警戒库存的个数 + .notIn(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus()); + return selectCount(queryWrapper); + } + + /** + * 获得商品 SPU 分页,提供给用户 App 使用 + */ + default PageResult selectPage(AppProductSpuPageReqVO pageReqVO, Set categoryIds) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + // 关键字匹配,目前只匹配商品名 + .likeIfPresent(ProductSpuDO::getName, pageReqVO.getKeyword()) + // 分类 + .inIfPresent(ProductSpuDO::getCategoryId, categoryIds); + // 上架状态 且有库存 + query.eq(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus()).gt(ProductSpuDO::getStock, 0); + // 推荐类型的过滤条件 + if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_HOT)) { + query.eq(ProductSpuDO::getRecommendHot, true); + } else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_BENEFIT)) { + query.eq(ProductSpuDO::getRecommendBenefit, true); + } else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_BEST)) { + query.eq(ProductSpuDO::getRecommendBest, true); + } else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_NEW)) { + query.eq(ProductSpuDO::getRecommendNew, true); + } else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_GOOD)) { + query.eq(ProductSpuDO::getRecommendGood, true); + } + + // 排序逻辑 + if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_SALES_COUNT)) { + query.last(String.format(" ORDER BY (sales_count + virtual_sales_count) %s, sort DESC, id DESC", + pageReqVO.getSortAsc() ? "ASC" : "DESC")); + } else if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_PRICE)) { + query.orderBy(true, pageReqVO.getSortAsc(), ProductSpuDO::getPrice) + .orderByDesc(ProductSpuDO::getSort).orderByDesc(ProductSpuDO::getId); + } else { + query.orderByDesc(ProductSpuDO::getSort).orderByDesc(ProductSpuDO::getId); + } + return selectPage(pageReqVO, query); + } + + default List selectListByRecommendType(String recommendType, Integer count) { + QueryWrapperX query = new QueryWrapperX<>(); + // 上架状态 且有库存 + query.eq("status", ProductSpuStatusEnum.ENABLE.getStatus()).gt("stock", 0); + // 推荐类型的过滤条件 + if (ObjUtil.equal(recommendType, AppProductSpuPageReqVO.RECOMMEND_TYPE_HOT)) { + query.eq("recommend_hot", true); + } else if (ObjUtil.equal(recommendType, AppProductSpuPageReqVO.RECOMMEND_TYPE_GOOD)) { + query.eq("recommend_good", true); + } + // 设置最大长度 + query.limitN(count); + return selectList(query); + } + + /** + * 更新商品 SPU 库存 + * + * @param id 商品 SPU 编号 + * @param incrCount 增加的库存数量 + */ + default void updateStock(Long id, Integer incrCount) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() + // 负数,所以使用 + 号 + .setSql(" stock = stock +" + incrCount) + .eq(ProductSpuDO::getId, id); + update(null, updateWrapper); + } + + /** + * 获得 Spu 列表 + * + * @param reqVO 查询条件 + * @return Spu 列表 + */ + default List selectList(ProductSpuExportReqVO reqVO) { + Integer tabType = reqVO.getTabType(); + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX<>(); + queryWrapper.eqIfPresent(ProductSpuDO::getName, reqVO.getName()); + queryWrapper.eqIfPresent(ProductSpuDO::getCategoryId, reqVO.getCategoryId()); + queryWrapper.betweenIfPresent(ProductSpuDO::getCreateTime, reqVO.getCreateTime()); + appendTabQuery(tabType, queryWrapper); + return selectList(queryWrapper); + } + + /** + * 添加后台 Tab 选项的查询条件 + * + * @param tabType 标签类型 + * @param query 查询条件 + */ + static void appendTabQuery(Integer tabType, LambdaQueryWrapperX query) { + // 出售中商品 + if (ObjectUtil.equals(ProductSpuPageReqVO.FOR_SALE, tabType)) { + query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus()); + } + // 仓储中商品 + if (ObjectUtil.equals(ProductSpuPageReqVO.IN_WAREHOUSE, tabType)) { + query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus()); + } + // 已售空商品 + if (ObjectUtil.equals(ProductSpuPageReqVO.SOLD_OUT, tabType)) { + query.eqIfPresent(ProductSpuDO::getStock, 0); + } + // 警戒库存 + if (ObjectUtil.equals(ProductSpuPageReqVO.ALERT_STOCK, tabType)) { + query.le(ProductSpuDO::getStock, ProductConstants.ALERT_STOCK) + // 如果库存触发警戒库存且状态为回收站的话则不在警戒库存列表展示 + .notIn(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus()); + } + // 回收站 + if (ObjectUtil.equals(ProductSpuPageReqVO.RECYCLE_BIN, tabType)) { + query.eqIfPresent(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus()); + } + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/package-info.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/package-info.java new file mode 100644 index 000000000..d2e1c934a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 product 模块的 framework 封装 + * + * @author 芋道源码 + */ +package cn.iocoder.yudao.module.product.framework; diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/config/ProductWebConfiguration.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/config/ProductWebConfiguration.java new file mode 100644 index 000000000..9d1bf7db9 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/config/ProductWebConfiguration.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.product.framework.web.config; + +import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; +import org.springdoc.core.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * product 模块的 web 组件的 Configuration + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class ProductWebConfiguration { + + /** + * product 模块的 API 分组 + */ + @Bean + public GroupedOpenApi productGroupedOpenApi() { + return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("product"); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/package-info.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/package-info.java new file mode 100644 index 000000000..f4adb2d76 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/package-info.java @@ -0,0 +1,4 @@ +/** + * product 模块的 web 配置 + */ +package cn.iocoder.yudao.module.product.framework.web; diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/package-info.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/package-info.java new file mode 100644 index 000000000..e32942933 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/package-info.java @@ -0,0 +1,8 @@ +/** + * product 模块,主要实现交易相关功能 + * 例如:订单、退款、购物车等功能。 + * + * 1. Controller URL:以 /product/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 product_ 开头,方便在数据库中区分 + */ +package cn.iocoder.yudao.module.product; diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandService.java new file mode 100644 index 000000000..aa401ed07 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandService.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.product.service.brand; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.*; +import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 商品品牌 Service 接口 + * + * @author 芋道源码 + */ +public interface ProductBrandService { + + /** + * 创建品牌 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createBrand(@Valid ProductBrandCreateReqVO createReqVO); + + /** + * 更新品牌 + * + * @param updateReqVO 更新信息 + */ + void updateBrand(@Valid ProductBrandUpdateReqVO updateReqVO); + + /** + * 删除品牌 + * + * @param id 编号 + */ + void deleteBrand(Long id); + + /** + * 获得品牌 + * + * @param id 编号 + * @return 品牌 + */ + ProductBrandDO getBrand(Long id); + + /** + * 获得品牌列表 + * + * @param ids 编号 + * @return 品牌列表 + */ + List getBrandList(Collection ids); + + /** + * 获得品牌列表 + * + * @param listReqVO 请求参数 + * @return 品牌列表 + */ + List getBrandList(ProductBrandListReqVO listReqVO); + + /** + * 验证选择的商品分类是否合法 + * + * @param id 分类编号 + */ + void validateProductBrand(Long id); + + /** + * 获得品牌分页 + * + * @param pageReqVO 分页查询 + * @return 品牌分页 + */ + PageResult getBrandPage(ProductBrandPageReqVO pageReqVO); + + /** + * 获取指定状态的品牌列表 + * + * @param status 状态 + * @return 返回品牌列表 + */ + List getBrandListByStatus(Integer status); +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImpl.java new file mode 100644 index 000000000..b97123f6a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImpl.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.module.product.service.brand; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +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.ProductBrandListReqVO; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandPageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandUpdateReqVO; +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.dal.mysql.brand.ProductBrandMapper; +import com.google.common.annotations.VisibleForTesting; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; + +/** + * 品牌 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProductBrandServiceImpl implements ProductBrandService { + + @Resource + private ProductBrandMapper brandMapper; + + @Override + public Long createBrand(ProductBrandCreateReqVO createReqVO) { + // 校验 + validateBrandNameUnique(null, createReqVO.getName()); + + // 插入 + ProductBrandDO brand = ProductBrandConvert.INSTANCE.convert(createReqVO); + brandMapper.insert(brand); + // 返回 + return brand.getId(); + } + + @Override + public void updateBrand(ProductBrandUpdateReqVO updateReqVO) { + // 校验存在 + validateBrandExists(updateReqVO.getId()); + validateBrandNameUnique(updateReqVO.getId(), updateReqVO.getName()); + // 更新 + ProductBrandDO updateObj = ProductBrandConvert.INSTANCE.convert(updateReqVO); + brandMapper.updateById(updateObj); + } + + @Override + public void deleteBrand(Long id) { + // 校验存在 + validateBrandExists(id); + // 删除 + brandMapper.deleteById(id); + } + + private void validateBrandExists(Long id) { + if (brandMapper.selectById(id) == null) { + throw exception(BRAND_NOT_EXISTS); + } + } + + @VisibleForTesting + public void validateBrandNameUnique(Long id, String name) { + ProductBrandDO brand = brandMapper.selectByName(name); + if (brand == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的字典类型 + if (id == null) { + throw exception(BRAND_NAME_EXISTS); + } + if (!brand.getId().equals(id)) { + throw exception(BRAND_NAME_EXISTS); + } + } + + @Override + public ProductBrandDO getBrand(Long id) { + return brandMapper.selectById(id); + } + + @Override + public List getBrandList(Collection ids) { + return brandMapper.selectBatchIds(ids); + } + + @Override + public List getBrandList(ProductBrandListReqVO listReqVO) { + return brandMapper.selectList(listReqVO); + } + + @Override + public void validateProductBrand(Long id) { + ProductBrandDO brand = brandMapper.selectById(id); + if (brand == null) { + throw exception(BRAND_NOT_EXISTS); + } + if (brand.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { + throw exception(BRAND_DISABLED); + } + } + + @Override + public PageResult getBrandPage(ProductBrandPageReqVO pageReqVO) { + return brandMapper.selectPage(pageReqVO); + } + + @Override + public List getBrandListByStatus(Integer status) { + return brandMapper.selectListByStatus(status); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryService.java new file mode 100644 index 000000000..de1545bf9 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryService.java @@ -0,0 +1,87 @@ +package cn.iocoder.yudao.module.product.service.category; + +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.ProductCategoryUpdateReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 商品分类 Service 接口 + * + * @author 芋道源码 + */ +public interface ProductCategoryService { + + /** + * 创建商品分类 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createCategory(@Valid ProductCategoryCreateReqVO createReqVO); + + /** + * 更新商品分类 + * + * @param updateReqVO 更新信息 + */ + void updateCategory(@Valid ProductCategoryUpdateReqVO updateReqVO); + + /** + * 删除商品分类 + * + * @param id 编号 + */ + void deleteCategory(Long id); + + /** + * 获得商品分类 + * + * @param id 编号 + * @return 商品分类 + */ + ProductCategoryDO getCategory(Long id); + + /** + * 校验商品分类 + * + * @param id 分类编号 + */ + void validateCategory(Long id); + + /** + * 获得商品分类的层级 + * + * @param id 编号 + * @return 商品分类的层级 + */ + Integer getCategoryLevel(Long id); + + /** + * 获得商品分类列表 + * + * @param listReqVO 查询条件 + * @return 商品分类列表 + */ + List getEnableCategoryList(ProductCategoryListReqVO listReqVO); + + /** + * 获得开启状态的商品分类列表 + * + * @return 商品分类列表 + */ + List getEnableCategoryList(); + + /** + * 校验商品分类是否有效。如下情况,视为无效: + * 1. 商品分类编号不存在 + * 2. 商品分类被禁用 + * + * @param ids 商品分类编号数组 + */ + void validateCategoryList(Collection ids); +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java new file mode 100644 index 000000000..60c0ffbfa --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java @@ -0,0 +1,173 @@ +package cn.iocoder.yudao.module.product.service.category; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +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.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.dal.mysql.category.ProductCategoryMapper; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO.PARENT_ID_NULL; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; + +/** + * 商品分类 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProductCategoryServiceImpl implements ProductCategoryService { + + @Resource + private ProductCategoryMapper productCategoryMapper; + @Resource + @Lazy // 循环依赖,避免报错 + private ProductSpuService productSpuService; + + @Override + public Long createCategory(ProductCategoryCreateReqVO createReqVO) { + // 校验父分类存在 + validateParentProductCategory(createReqVO.getParentId()); + + // 插入 + ProductCategoryDO category = ProductCategoryConvert.INSTANCE.convert(createReqVO); + productCategoryMapper.insert(category); + // 返回 + return category.getId(); + } + + @Override + public void updateCategory(ProductCategoryUpdateReqVO updateReqVO) { + // 校验分类是否存在 + validateProductCategoryExists(updateReqVO.getId()); + // 校验父分类存在 + validateParentProductCategory(updateReqVO.getParentId()); + + // 更新 + ProductCategoryDO updateObj = ProductCategoryConvert.INSTANCE.convert(updateReqVO); + productCategoryMapper.updateById(updateObj); + } + + @Override + public void deleteCategory(Long id) { + // 校验分类是否存在 + validateProductCategoryExists(id); + // 校验是否还有子分类 + if (productCategoryMapper.selectCountByParentId(id) > 0) { + throw exception(CATEGORY_EXISTS_CHILDREN); + } + // 校验分类是否绑定了 SPU + Long spuCount = productSpuService.getSpuCountByCategoryId(id); + if (spuCount > 0) { + throw exception(CATEGORY_HAVE_BIND_SPU); + } + // 删除 + productCategoryMapper.deleteById(id); + } + + private void validateParentProductCategory(Long id) { + // 如果是根分类,无需验证 + if (Objects.equals(id, PARENT_ID_NULL)) { + return; + } + // 父分类不存在 + ProductCategoryDO category = productCategoryMapper.selectById(id); + if (category == null) { + throw exception(CATEGORY_PARENT_NOT_EXISTS); + } + // 父分类不能是二级分类 + if (!Objects.equals(category.getParentId(), PARENT_ID_NULL)) { + throw exception(CATEGORY_PARENT_NOT_FIRST_LEVEL); + } + } + + private void validateProductCategoryExists(Long id) { + ProductCategoryDO category = productCategoryMapper.selectById(id); + if (category == null) { + throw exception(CATEGORY_NOT_EXISTS); + } + } + + @Override + public void validateCategoryList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 获得商品分类信息 + List list = productCategoryMapper.selectBatchIds(ids); + Map categoryMap = CollectionUtils.convertMap(list, ProductCategoryDO::getId); + // 校验 + ids.forEach(id -> { + ProductCategoryDO category = categoryMap.get(id); + if (category == null) { + throw exception(CATEGORY_NOT_EXISTS); + } + if (!CommonStatusEnum.ENABLE.getStatus().equals(category.getStatus())) { + throw exception(CATEGORY_DISABLED, category.getName()); + } + }); + } + + @Override + public ProductCategoryDO getCategory(Long id) { + return productCategoryMapper.selectById(id); + } + + @Override + public void validateCategory(Long id) { + ProductCategoryDO category = productCategoryMapper.selectById(id); + if (category == null) { + throw exception(CATEGORY_NOT_EXISTS); + } + if (Objects.equals(category.getStatus(), CommonStatusEnum.DISABLE.getStatus())) { + throw exception(CATEGORY_DISABLED, category.getName()); + } + } + + @Override + public Integer getCategoryLevel(Long id) { + if (Objects.equals(id, PARENT_ID_NULL)) { + return 0; + } + int level = 1; + // for 的原因,是因为避免脏数据,导致可能的死循环。一般不会超过 100 层哈 + for (int i = 0; i < Byte.MAX_VALUE; i++) { + // 如果没有父节点,break 结束 + ProductCategoryDO category = productCategoryMapper.selectById(id); + if (category == null + || Objects.equals(category.getParentId(), PARENT_ID_NULL)) { + break; + } + // 继续递归父节点 + level++; + id = category.getParentId(); + } + return level; + } + + @Override + public List getEnableCategoryList(ProductCategoryListReqVO listReqVO) { + return productCategoryMapper.selectList(listReqVO); + } + + @Override + public List getEnableCategoryList() { + return productCategoryMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java new file mode 100644 index 000000000..e531f5513 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentService.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.product.service.comment; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentCreateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentReplyReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO; +import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +/** + * 商品评论 Service 接口 + * + * @author wangzhs + */ +@Service +@Validated +public interface ProductCommentService { + + /** + * 创建商品评论 + * 后台管理员创建评论使用 + * + * @param createReqVO 商品评价创建 Request VO 对象 + */ + void createComment(ProductCommentCreateReqVO createReqVO); + + /** + * 创建评论 + * 创建商品评论 APP 端创建商品评论使用 + * + * @param createReqDTO 创建请求 dto + * @return 返回评论 id + */ + Long createComment(ProductCommentCreateReqDTO createReqDTO); + + /** + * 修改评论是否可见 + * + * @param updateReqVO 修改评论可见 + */ + void updateCommentVisible(ProductCommentUpdateVisibleReqVO updateReqVO); + + /** + * 商家回复 + * + * @param replyVO 商家回复 + * @param userId 管理后台商家登陆人 ID + */ + void replyComment(ProductCommentReplyReqVO replyVO, Long userId); + + /** + * 【管理员】获得商品评价分页 + * + * @param pageReqVO 分页查询 + * @return 商品评价分页 + */ + PageResult getCommentPage(ProductCommentPageReqVO pageReqVO); + + /** + * 【会员】获得商品评价分页 + * + * @param pageVO 分页查询 + * @param visible 是否可见 + * @return 商品评价分页 + */ + PageResult getCommentPage(AppCommentPageReqVO pageVO, Boolean visible); + + /** + * 获得商品的评价统计 + * + * @param spuId spu id + * @param visible 是否可见 + * @return 评价统计 + */ + AppCommentStatisticsRespVO getCommentStatistics(Long spuId, Boolean visible); + + /** + * 得到评论列表 + * + * @param spuId 商品 id + * @param count 数量 + * @return {@link Object} + */ + List getCommentList(Long spuId, Integer count); + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java new file mode 100644 index 000000000..93b103cca --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java @@ -0,0 +1,167 @@ +package cn.iocoder.yudao.module.product.service.comment; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentCreateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentReplyReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO; +import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; + +/** + * 商品评论 Service 实现类 + * + * @author wangzhs + */ +@Service +@Validated +public class ProductCommentServiceImpl implements ProductCommentService { + + @Resource + private ProductCommentMapper productCommentMapper; + + @Resource + private ProductSpuService productSpuService; + + @Resource + @Lazy + private ProductSkuService productSkuService; + + @Resource + private MemberUserApi memberUserApi; + + @Override + public void createComment(ProductCommentCreateReqVO createReqVO) { + // 校验 SKU + ProductSkuDO skuDO = validateSku(createReqVO.getSkuId()); + // 校验 SPU + ProductSpuDO spuDO = validateSpu(skuDO.getSpuId()); + + // 创建评论 + ProductCommentDO comment = ProductCommentConvert.INSTANCE.convert(createReqVO, spuDO, skuDO); + productCommentMapper.insert(comment); + } + + @Override + public Long createComment(ProductCommentCreateReqDTO createReqDTO) { + // 校验 SKU + ProductSkuDO skuDO = validateSku(createReqDTO.getSkuId()); + // 校验 SPU + ProductSpuDO spuDO = validateSpu(skuDO.getSpuId()); + // 校验评论 + validateCommentExists(createReqDTO.getUserId(), createReqDTO.getOrderId()); + // 获取用户详细信息 + MemberUserRespDTO user = memberUserApi.getUser(createReqDTO.getUserId()).getCheckedData(); + + // 创建评论 + ProductCommentDO comment = ProductCommentConvert.INSTANCE.convert(createReqDTO, spuDO, skuDO, user); + productCommentMapper.insert(comment); + return comment.getId(); + } + + /** + * 判断当前订单的当前商品用户是否评价过 + * + * @param userId 用户编号 + * @param orderItemId 订单项编号 + */ + private void validateCommentExists(Long userId, Long orderItemId) { + ProductCommentDO exist = productCommentMapper.selectByUserIdAndOrderItemId(userId, orderItemId); + if (exist != null) { + throw exception(COMMENT_ORDER_EXISTS); + } + } + + private ProductSkuDO validateSku(Long skuId) { + ProductSkuDO sku = productSkuService.getSku(skuId); + if (sku == null) { + throw exception(SKU_NOT_EXISTS); + } + return sku; + } + + private ProductSpuDO validateSpu(Long spuId) { + ProductSpuDO spu = productSpuService.getSpu(spuId); + if (null == spu) { + throw exception(SPU_NOT_EXISTS); + } + return spu; + } + + @Override + public void updateCommentVisible(ProductCommentUpdateVisibleReqVO updateReqVO) { + // 校验评论是否存在 + validateCommentExists(updateReqVO.getId()); + + // 更新可见状态 + productCommentMapper.updateById(new ProductCommentDO().setId(updateReqVO.getId()) + .setVisible(true)); + } + + @Override + public void replyComment(ProductCommentReplyReqVO replyVO, Long userId) { + // 校验评论是否存在 + validateCommentExists(replyVO.getId()); + // 回复评论 + productCommentMapper.updateById(new ProductCommentDO().setId(replyVO.getId()) + .setReplyTime(LocalDateTime.now()).setReplyUserId(userId) + .setReplyStatus(Boolean.TRUE).setReplyContent(replyVO.getReplyContent())); + } + + private ProductCommentDO validateCommentExists(Long id) { + ProductCommentDO productComment = productCommentMapper.selectById(id); + if (productComment == null) { + throw exception(COMMENT_NOT_EXISTS); + } + return productComment; + } + + @Override + public AppCommentStatisticsRespVO getCommentStatistics(Long spuId, Boolean visible) { + return ProductCommentConvert.INSTANCE.convert( + // 查询商品 id = spuId 的所有好评数量 + productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.GOOD_COMMENT), + // 查询商品 id = spuId 的所有中评数量 + productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.MEDIOCRE_COMMENT), + // 查询商品 id = spuId 的所有差评数量 + productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.NEGATIVE_COMMENT) + ); + } + + @Override + public List getCommentList(Long spuId, Integer count) { + return ProductCommentConvert.INSTANCE.convertList02(productCommentMapper.selectCommentList(spuId, count).getList()); + } + + @Override + public PageResult getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) { + return productCommentMapper.selectPage(pageVO, visible); + } + + @Override + public PageResult getCommentPage(ProductCommentPageReqVO pageReqVO) { + return productCommentMapper.selectPage(pageReqVO); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java new file mode 100644 index 000000000..00aeddb8a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.product.service.favorite; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; + +import javax.validation.Valid; + +/** + * 商品收藏 Service 接口 + * + * @author jason + */ +public interface ProductFavoriteService { + + /** + * 创建商品收藏 + * + * @param userId 用户编号 + * @param spuId SPU 编号 + */ + Long createFavorite(Long userId, Long spuId); + + /** + * 取消商品收藏 + * + * @param userId 用户编号 + * @param spuId SPU 编号 + */ + void deleteFavorite(Long userId, Long spuId); + + /** + * 分页查询用户收藏列表 + * + * @param userId 用户编号 + * @param reqVO 请求 vo + */ + PageResult getFavoritePage(Long userId, @Valid AppFavoritePageReqVO reqVO); + + /** + * 获取收藏过商品 + * + * @param userId 用户编号 + * @param spuId SPU 编号 + */ + ProductFavoriteDO getFavorite(Long userId, Long spuId); + + /** + * 获取用户收藏数量 + * + * @param userId 用户编号 + * @return 数量 + */ + Long getFavoriteCount(Long userId); + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java new file mode 100644 index 000000000..983cbf83c --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.product.service.favorite; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; +import cn.iocoder.yudao.module.product.convert.favorite.ProductFavoriteConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import cn.iocoder.yudao.module.product.dal.mysql.favorite.ProductFavoriteMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.FAVORITE_EXISTS; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.FAVORITE_NOT_EXISTS; + +/** + * 商品收藏 Service 实现类 + * + * @author jason + */ +@Service +@Validated +public class ProductFavoriteServiceImpl implements ProductFavoriteService { + + @Resource + private ProductFavoriteMapper productFavoriteMapper; + + @Override + public Long createFavorite(Long userId, Long spuId) { + ProductFavoriteDO favorite = productFavoriteMapper.selectByUserIdAndSpuId(userId, spuId); + if (favorite != null) { + throw exception(FAVORITE_EXISTS); + } + + ProductFavoriteDO entity = ProductFavoriteConvert.INSTANCE.convert(userId, spuId); + productFavoriteMapper.insert(entity); + return entity.getId(); + } + + @Override + public void deleteFavorite(Long userId, Long spuId) { + ProductFavoriteDO favorite = productFavoriteMapper.selectByUserIdAndSpuId(userId, spuId); + if (favorite == null) { + throw exception(FAVORITE_NOT_EXISTS); + } + + productFavoriteMapper.deleteById(favorite.getId()); + } + + @Override + public PageResult getFavoritePage(Long userId, @Valid AppFavoritePageReqVO reqVO) { + return productFavoriteMapper.selectPageByUserAndType(userId, reqVO); + } + + @Override + public ProductFavoriteDO getFavorite(Long userId, Long spuId) { + return productFavoriteMapper.selectByUserIdAndSpuId(userId, spuId); + } + + @Override + public Long getFavoriteCount(Long userId) { + return productFavoriteMapper.selectCountByUserId(userId); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyService.java new file mode 100644 index 000000000..6a1ceb95a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyService.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.product.service.property; + +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.dal.dataobject.property.ProductPropertyDO; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * 商品属性项 Service 接口 + * + * @author 芋道源码 + */ +public interface ProductPropertyService { + + /** + * 创建属性项 + * 注意,如果已经存在该属性项,直接返回它的编号即可 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProperty(@Valid ProductPropertyCreateReqVO createReqVO); + + /** + * 更新属性项 + * + * @param updateReqVO 更新信息 + */ + void updateProperty(@Valid ProductPropertyUpdateReqVO updateReqVO); + + /** + * 删除属性项 + * + * @param id 编号 + */ + void deleteProperty(Long id); + + /** + * 获得属性项列表 + * + * @param listReqVO 集合查询 + * @return 属性项集合 + */ + List getPropertyList(ProductPropertyListReqVO listReqVO); + + /** + * 获取属性名称分页 + * + * @param pageReqVO 分页条件 + * @return 属性项分页 + */ + PageResult getPropertyPage(ProductPropertyPageReqVO pageReqVO); + + /** + * 获得指定编号的属性项 + * + * @param id 编号 + * @return 属性项 + */ + ProductPropertyDO getProperty(Long id); + + /** + * 根据属性项的编号的集合,获得对应的属性项数组 + * + * @param ids 属性项的编号的集合 + * @return 属性项数组 + */ + List getPropertyList(Collection ids); + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyServiceImpl.java new file mode 100644 index 000000000..44bf95e71 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyServiceImpl.java @@ -0,0 +1,119 @@ +package cn.iocoder.yudao.module.product.service.property; + +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyCreateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyListReqVO; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyPageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyUpdateReqVO; +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.mysql.property.ProductPropertyMapper; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; + +/** + * 商品属性项 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProductPropertyServiceImpl implements ProductPropertyService { + + @Resource + private ProductPropertyMapper productPropertyMapper; + + @Resource + @Lazy // 延迟加载,解决循环依赖问题 + private ProductPropertyValueService productPropertyValueService; + + @Resource + private ProductSkuService productSkuService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createProperty(ProductPropertyCreateReqVO createReqVO) { + // 如果已经添加过该属性项,直接返回 + ProductPropertyDO dbProperty = productPropertyMapper.selectByName(createReqVO.getName()); + if (dbProperty != null) { + return dbProperty.getId(); + } + + // 插入 + ProductPropertyDO property = ProductPropertyConvert.INSTANCE.convert(createReqVO); + productPropertyMapper.insert(property); + // 返回 + return property.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateProperty(ProductPropertyUpdateReqVO updateReqVO) { + validatePropertyExists(updateReqVO.getId()); + // 校验名字重复 + ProductPropertyDO productPropertyDO = productPropertyMapper.selectByName(updateReqVO.getName()); + if (productPropertyDO != null && + ObjUtil.notEqual(productPropertyDO.getId(), updateReqVO.getId())) { + throw exception(PROPERTY_EXISTS); + } + + // 更新 + ProductPropertyDO updateObj = ProductPropertyConvert.INSTANCE.convert(updateReqVO); + productPropertyMapper.updateById(updateObj); + // 更新 sku 相关属性 + productSkuService.updateSkuProperty(updateObj.getId(), updateObj.getName()); + } + + @Override + public void deleteProperty(Long id) { + // 校验存在 + validatePropertyExists(id); + // 校验其下是否有规格值 + if (productPropertyValueService.getPropertyValueCountByPropertyId(id) > 0) { + throw exception(PROPERTY_DELETE_FAIL_VALUE_EXISTS); + } + + // 删除 + productPropertyMapper.deleteById(id); + // 同步删除属性值 + productPropertyValueService.deletePropertyValueByPropertyId(id); + } + + private void validatePropertyExists(Long id) { + if (productPropertyMapper.selectById(id) == null) { + throw exception(PROPERTY_NOT_EXISTS); + } + } + + @Override + public List getPropertyList(ProductPropertyListReqVO listReqVO) { + return productPropertyMapper.selectList(listReqVO); + } + + @Override + public PageResult getPropertyPage(ProductPropertyPageReqVO pageReqVO) { + return productPropertyMapper.selectPage(pageReqVO); + } + + @Override + public ProductPropertyDO getProperty(Long id) { + return productPropertyMapper.selectById(id); + } + + @Override + public List getPropertyList(Collection ids) { + return productPropertyMapper.selectBatchIds(ids); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueService.java new file mode 100644 index 000000000..553e2578d --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueService.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.module.product.service.property; + +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.ProductPropertyValueUpdateReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO; +import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO; + +import java.util.Collection; +import java.util.List; + +/** + * 商品属性值 Service 接口 + * + * @author LuoWenFeng + */ +public interface ProductPropertyValueService { + + /** + * 创建属性值 + * 注意,如果已经存在该属性值,直接返回它的编号即可 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPropertyValue(ProductPropertyValueCreateReqVO createReqVO); + + /** + * 更新属性值 + * + * @param updateReqVO 更新信息 + */ + void updatePropertyValue(ProductPropertyValueUpdateReqVO updateReqVO); + + /** + * 删除属性值 + * + * @param id 编号 + */ + void deletePropertyValue(Long id); + + /** + * 获得属性值 + * + * @param id 编号 + * @return 属性值 + */ + ProductPropertyValueDO getPropertyValue(Long id); + + /** + * 根据属性项编号数组,获得属性值列表 + * + * @param propertyIds 属性项目编号数组 + * @return 属性值列表 + */ + List getPropertyValueListByPropertyId(Collection propertyIds); + + /** + * 根据编号数组,获得属性值列表 + * + * @param ids 编号数组 + * @return 属性值明细列表 + */ + List getPropertyValueDetailList(Collection ids); + + /** + * 根据属性项编号,活的属性值数量 + * + * @param propertyId 属性项编号数 + * @return 属性值数量 + */ + Integer getPropertyValueCountByPropertyId(Long propertyId); + + /** + * 获取属性值的分页 + * + * @param pageReqVO 查询条件 + * @return 属性值的分页 + */ + PageResult getPropertyValuePage(ProductPropertyValuePageReqVO pageReqVO); + + /** + * 删除指定属性项编号下的属性值们 + * + * @param propertyId 属性项的编号 + */ + void deletePropertyValueByPropertyId(Long propertyId); + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java new file mode 100644 index 000000000..0f74bca1f --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java @@ -0,0 +1,134 @@ +package cn.iocoder.yudao.module.product.service.property; + +import cn.hutool.core.collection.CollUtil; +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.ProductPropertyValueUpdateReqVO; +import cn.iocoder.yudao.module.product.convert.property.ProductPropertyValueConvert; +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.mysql.property.ProductPropertyValueMapper; +import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; +import org.springframework.context.annotation.Lazy; +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; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PROPERTY_VALUE_EXISTS; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PROPERTY_VALUE_NOT_EXISTS; + +/** + * 商品属性值 Service 实现类 + * + * @author LuoWenFeng + */ +@Service +@Validated +public class ProductPropertyValueServiceImpl implements ProductPropertyValueService { + + @Resource + private ProductPropertyValueMapper productPropertyValueMapper; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private ProductPropertyService productPropertyService; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private ProductSkuService productSkuService; + + @Override + public Long createPropertyValue(ProductPropertyValueCreateReqVO createReqVO) { + // 如果已经添加过该属性值,直接返回 + ProductPropertyValueDO dbValue = productPropertyValueMapper.selectByName( + createReqVO.getPropertyId(), createReqVO.getName()); + if (dbValue != null) { + return dbValue.getId(); + } + + // 新增 + ProductPropertyValueDO value = ProductPropertyValueConvert.INSTANCE.convert(createReqVO); + productPropertyValueMapper.insert(value); + return value.getId(); + } + + @Override + public void updatePropertyValue(ProductPropertyValueUpdateReqVO updateReqVO) { + validatePropertyValueExists(updateReqVO.getId()); + // 校验名字唯一 + ProductPropertyValueDO productPropertyValueDO = productPropertyValueMapper.selectByName + (updateReqVO.getPropertyId(), updateReqVO.getName()); + if (productPropertyValueDO != null && !productPropertyValueDO.getId().equals(updateReqVO.getId())) { + throw exception(PROPERTY_VALUE_EXISTS); + } + + // 更新 + ProductPropertyValueDO updateObj = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO); + productPropertyValueMapper.updateById(updateObj); + // 更新 sku 相关属性 + productSkuService.updateSkuPropertyValue(updateObj.getId(), updateObj.getName()); + } + + @Override + public void deletePropertyValue(Long id) { + validatePropertyValueExists(id); + productPropertyValueMapper.deleteById(id); + } + + private void validatePropertyValueExists(Long id) { + if (productPropertyValueMapper.selectById(id) == null) { + throw exception(PROPERTY_VALUE_NOT_EXISTS); + } + } + + @Override + public ProductPropertyValueDO getPropertyValue(Long id) { + return productPropertyValueMapper.selectById(id); + } + + @Override + public List getPropertyValueListByPropertyId(Collection propertyIds) { + return productPropertyValueMapper.selectListByPropertyId(propertyIds); + } + + @Override + public List getPropertyValueDetailList(Collection ids) { + // 获得属性值列表 + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + List values = productPropertyValueMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(values)) { + return Collections.emptyList(); + } + // 获得属性项列表 + List keys = productPropertyService.getPropertyList( + convertSet(values, ProductPropertyValueDO::getPropertyId)); + // 组装明细 + return ProductPropertyValueConvert.INSTANCE.convertList(values, keys); + } + + @Override + public Integer getPropertyValueCountByPropertyId(Long propertyId) { + return productPropertyValueMapper.selectCountByPropertyId(propertyId); + } + + @Override + public PageResult getPropertyValuePage(ProductPropertyValuePageReqVO pageReqVO) { + return productPropertyValueMapper.selectPage(pageReqVO); + } + + @Override + public void deletePropertyValueByPropertyId(Long propertyId) { + productPropertyValueMapper.deleteByPropertyId(propertyId); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/bo/ProductPropertyValueDetailRespBO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/bo/ProductPropertyValueDetailRespBO.java new file mode 100644 index 000000000..6776731f9 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/bo/ProductPropertyValueDetailRespBO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.product.service.property.bo; + +import lombok.Data; + +/** + * 商品属性项的明细 Response BO + * + * @author 芋道源码 + */ +@Data +public class ProductPropertyValueDetailRespBO { + + /** + * 属性的编号 + */ + private Long propertyId; + + /** + * 属性的名称 + */ + private String propertyName; + + /** + * 属性值的编号 + */ + private Long valueId; + + /** + * 属性值的名称 + */ + private String valueName; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java new file mode 100755 index 000000000..fbc9830bf --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java @@ -0,0 +1,127 @@ +package cn.iocoder.yudao.module.product.service.sku; + +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.dal.dataobject.sku.ProductSkuDO; + +import java.util.Collection; +import java.util.List; + +/** + * 商品 SKU Service 接口 + * + * @author 芋道源码 + */ +public interface ProductSkuService { + + /** + * 删除商品 SKU + * + * @param id 编号 + */ + void deleteSku(Long id); + + /** + * 获得商品 SKU 信息 + * + * @param id 编号 + * @return 商品 SKU 信息 + */ + ProductSkuDO getSku(Long id); + + /** + * 获得商品 SKU 列表 + * + * @return 商品sku列表 + */ + List getSkuList(); + + /** + * 获得商品 SKU 列表 + * + * @param ids 编号 + * @return 商品sku列表 + */ + List getSkuList(Collection ids); + + /** + * 对 sku 的组合的属性等进行合法性校验 + * + * @param list sku组合的集合 + */ + void validateSkuList(List list, Boolean specType); + + /** + * 批量创建 SKU + * + * @param spuId 商品 SPU 编号 + * @param list SKU 对象集合 + */ + void createSkuList(Long spuId, List list); + + /** + * 根据 SPU 编号,批量更新它的 SKU 信息 + * + * @param spuId SPU 编码 + * @param skus SKU 的集合 + */ + void updateSkuList(Long spuId, List skus); + + /** + * 更新 SKU 库存(增量) + *

+ * 如果更新的库存不足,会抛出异常 + * + * @param updateStockReqDTO 更行请求 + */ + void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO); + + /** + * 获得商品 SKU 集合 + * + * @param spuId spu 编号 + * @return 商品sku 集合 + */ + List getSkuListBySpuId(Long spuId); + + /** + * 获得 spu 对应的 SKU 集合 + * + * @param spuIds spu 编码集合 + * @return 商品 sku 集合 + */ + List getSkuListBySpuId(Collection spuIds); + + /** + * 通过 spuId 删除 sku 信息 + * + * @param spuId spu 编码 + */ + void deleteSkuBySpuId(Long spuId); + + /** + * 获得库存预警的 SKU 数组 + * + * @return SKU 数组 + */ + List getSkuListByAlarmStock(); + + /** + * 更新 sku 属性 + * + * @param propertyId 属性 id + * @param propertyName 属性名 + * @return int 影响的行数 + */ + int updateSkuProperty(Long propertyId, String propertyName); + + /** + * 更新 sku 属性值 + * + * @param propertyValueId 属性值 id + * @param propertyValueName 属性值名字 + * @return int 影响的行数 + */ + int updateSkuPropertyValue(Long propertyValueId, String propertyValueName); + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java new file mode 100755 index 000000000..cd0ba6b46 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java @@ -0,0 +1,278 @@ +package cn.iocoder.yudao.module.product.service.sku; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; +import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO; +import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; +import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert; +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.sku.ProductSkuDO; +import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; + +/** + * 商品 SKU Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProductSkuServiceImpl implements ProductSkuService { + + @Resource + private ProductSkuMapper productSkuMapper; + + @Resource + @Lazy // 循环依赖,避免报错 + private ProductSpuService productSpuService; + @Resource + @Lazy // 循环依赖,避免报错 + private ProductPropertyService productPropertyService; + @Resource + private ProductPropertyValueService productPropertyValueService; + + @Override + public void deleteSku(Long id) { + // 校验存在 + validateSkuExists(id); + // 删除 + productSkuMapper.deleteById(id); + } + + private void validateSkuExists(Long id) { + if (productSkuMapper.selectById(id) == null) { + throw exception(SKU_NOT_EXISTS); + } + } + + @Override + public ProductSkuDO getSku(Long id) { + return productSkuMapper.selectById(id); + } + + @Override + public List getSkuList() { + return productSkuMapper.selectList(); + } + + @Override + public List getSkuList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return productSkuMapper.selectBatchIds(ids); + } + + @Override + public void validateSkuList(List skus, Boolean specType) { + // 0、校验skus是否为空 + if (CollUtil.isEmpty(skus)) { + throw exception(SKU_NOT_EXISTS); + } + // 单规格,赋予单规格默认属性 + if (ObjectUtil.equal(specType, false)) { + ProductSkuCreateOrUpdateReqVO skuVO = skus.get(0); + List properties = new ArrayList<>(); + ProductSkuBaseVO.Property property = new ProductSkuBaseVO.Property(); + property.setPropertyId(ProductPropertyDO.ID_DEFAULT); + property.setPropertyName(ProductPropertyDO.NAME_DEFAULT); + property.setValueId(ProductPropertyValueDO.ID_DEFAULT); + property.setValueName(ProductPropertyValueDO.NAME_DEFAULT); + properties.add(property); + skuVO.setProperties(properties); + return; // 单规格不需要后续的校验 + } + + // 1、校验属性项存在 + Set propertyIds = skus.stream().filter(p -> p.getProperties() != null) + // 遍历多个 Property 属性 + .flatMap(p -> p.getProperties().stream()) + // 将每个 Property 转换成对应的 propertyId,最后形成集合 + .map(ProductSkuCreateOrUpdateReqVO.Property::getPropertyId) + .collect(Collectors.toSet()); + List propertyList = productPropertyService.getPropertyList(propertyIds); + if (propertyList.size() != propertyIds.size()) { + throw exception(PROPERTY_NOT_EXISTS); + } + + // 2. 校验,一个 SKU 下,没有重复的属性。校验方式是,遍历每个 SKU ,看看是否有重复的属性 propertyId + Map propertyValueMap = convertMap(productPropertyValueService.getPropertyValueListByPropertyId(propertyIds), ProductPropertyValueDO::getId); + skus.forEach(sku -> { + Set skuPropertyIds = convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId()); + if (skuPropertyIds.size() != sku.getProperties().size()) { + throw exception(SKU_PROPERTIES_DUPLICATED); + } + }); + + // 3. 再校验,每个 Sku 的属性值的数量,是一致的。 + int attrValueIdsSize = skus.get(0).getProperties().size(); + for (int i = 1; i < skus.size(); i++) { + if (attrValueIdsSize != skus.get(i).getProperties().size()) { + throw exception(SPU_ATTR_NUMBERS_MUST_BE_EQUALS); + } + } + + // 4. 最后校验,每个 Sku 之间不是重复的 + // 每个元素,都是一个 Sku 的 attrValueId 集合。这样,通过最外层的 Set ,判断是否有重复的. + Set> skuAttrValues = new HashSet<>(); + for (ProductSkuCreateOrUpdateReqVO sku : skus) { + // 添加失败,说明重复 + if (!skuAttrValues.add(convertSet(sku.getProperties(), ProductSkuCreateOrUpdateReqVO.Property::getValueId))) { + throw exception(SPU_SKU_NOT_DUPLICATE); + } + } + } + + @Override + public void createSkuList(Long spuId, List skuCreateReqList) { + productSkuMapper.insertBatch(ProductSkuConvert.INSTANCE.convertList06(skuCreateReqList, spuId)); + } + + @Override + public List getSkuListBySpuId(Long spuId) { + return productSkuMapper.selectListBySpuId(spuId); + } + + @Override + public List getSkuListBySpuId(Collection spuIds) { + return productSkuMapper.selectListBySpuId(spuIds); + } + + @Override + public void deleteSkuBySpuId(Long spuId) { + productSkuMapper.deleteBySpuId(spuId); + } + + @Override + public List getSkuListByAlarmStock() { + return productSkuMapper.selectListByAlarmStock(); + } + + @Override + public int updateSkuProperty(Long propertyId, String propertyName) { + // 获取所有的 sku + List skuDOList = productSkuMapper.selectList(); + // 处理后需要更新的 sku + List updateSkus = new ArrayList<>(); + if (CollUtil.isEmpty(skuDOList)) { + return 0; + } + skuDOList.stream().filter(sku -> sku.getProperties() != null) + .forEach(sku -> sku.getProperties().forEach(property -> { + if (property.getPropertyId().equals(propertyId)) { + property.setPropertyName(propertyName); + updateSkus.add(sku); + } + })); + if (CollUtil.isEmpty(updateSkus)) { + return 0; + } + + productSkuMapper.updateBatch(updateSkus); + return updateSkus.size(); + } + + @Override + public int updateSkuPropertyValue(Long propertyValueId, String propertyValueName) { + // 获取所有的 sku + List skuDOList = productSkuMapper.selectList(); + // 处理后需要更新的 sku + List updateSkus = new ArrayList<>(); + if (CollUtil.isEmpty(skuDOList)) { + return 0; + } + skuDOList.stream() + .filter(sku -> sku.getProperties() != null) + .forEach(sku -> sku.getProperties().forEach(property -> { + if (property.getValueId().equals(propertyValueId)) { + property.setValueName(propertyValueName); + updateSkus.add(sku); + } + })); + if (CollUtil.isEmpty(updateSkus)) { + return 0; + } + + productSkuMapper.updateBatch(updateSkus); + return updateSkus.size(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSkuList(Long spuId, List skus) { + // 构建属性与 SKU 的映射关系; + Map existsSkuMap = convertMap(productSkuMapper.selectListBySpuId(spuId), + ProductSkuConvert.INSTANCE::buildPropertyKey, ProductSkuDO::getId); + + // 拆分三个集合,新插入的、需要更新的、需要删除的 + List insertSkus = new ArrayList<>(); + List updateSkus = new ArrayList<>(); + List allUpdateSkus = ProductSkuConvert.INSTANCE.convertList06(skus, spuId); + allUpdateSkus.forEach(sku -> { + String propertiesKey = ProductSkuConvert.INSTANCE.buildPropertyKey(sku); + // 1、找得到的,进行更新 + Long existsSkuId = existsSkuMap.remove(propertiesKey); + if (existsSkuId != null) { + sku.setId(existsSkuId); + updateSkus.add(sku); + return; + } + // 2、找不到,进行插入 + sku.setSpuId(spuId); + insertSkus.add(sku); + }); + + // 执行最终的批量操作 + if (CollUtil.isNotEmpty(insertSkus)) { + productSkuMapper.insertBatch(insertSkus); + } + if (CollUtil.isNotEmpty(updateSkus)) { + updateSkus.forEach(sku -> productSkuMapper.updateById(sku)); + } + if (CollUtil.isNotEmpty(existsSkuMap)) { + productSkuMapper.deleteBatchIds(existsSkuMap.values()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) { + // 更新 SKU 库存 + updateStockReqDTO.getItems().forEach(item -> { + if (item.getIncrCount() > 0) { + productSkuMapper.updateStockIncr(item.getId(), item.getIncrCount()); + } else if (item.getIncrCount() < 0) { + int updateStockIncr = productSkuMapper.updateStockDecr(item.getId(), item.getIncrCount()); + if (updateStockIncr == 0) { + throw exception(SKU_STOCK_NOT_ENOUGH); + } + } + }); + + // 更新 SPU 库存 + List skus = productSkuMapper.selectBatchIds( + convertSet(updateStockReqDTO.getItems(), ProductSkuUpdateStockReqDTO.Item::getId)); + Map spuStockIncrCounts = ProductSkuConvert.INSTANCE.convertSpuStockMap( + updateStockReqDTO.getItems(), skus); + productSpuService.updateSpuStock(spuStockIncrCounts); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java new file mode 100755 index 000000000..68232e0f2 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java @@ -0,0 +1,151 @@ +package cn.iocoder.yudao.module.product.service.spu; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*; +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; + +import javax.validation.Valid; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * 商品 SPU Service 接口 + * + * @author 芋道源码 + */ +public interface ProductSpuService { + + /** + * 创建商品 SPU + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSpu(@Valid ProductSpuCreateReqVO createReqVO); + + /** + * 更新商品 SPU + * + * @param updateReqVO 更新信息 + */ + void updateSpu(@Valid ProductSpuUpdateReqVO updateReqVO); + + /** + * 删除商品 SPU + * + * @param id 编号 + */ + void deleteSpu(Long id); + + /** + * 获得商品 SPU + * + * @param id 编号 + * @return 商品 SPU + */ + ProductSpuDO getSpu(Long id); + + /** + * 获得商品 SPU 列表 + * + * @param ids 编号数组 + * @return 商品 SPU 列表 + */ + List getSpuList(Collection ids); + + /** + * 获得商品 SPU 映射 + * + * @param ids 编号数组 + * @return 商品 SPU 映射 + */ + default Map getSpuMap(Collection ids) { + return convertMap(getSpuList(ids), ProductSpuDO::getId); + } + + /** + * 获得指定状态的商品 SPU 列表 + * + * @param status 状态 + * @return 商品 SPU 列表 + */ + List getSpuListByStatus(Integer status); + + /** + * 获得所有商品 SPU 列表 + * + * @param reqVO 导出条件 + * @return 商品 SPU 列表 + */ + List getSpuList(ProductSpuExportReqVO reqVO); + + /** + * 获得商品 SPU 分页,提供给挂你兰后台使用 + * + * @param pageReqVO 分页查询 + * @return 商品spu分页 + */ + PageResult getSpuPage(ProductSpuPageReqVO pageReqVO); + + /** + * 获得商品 SPU 分页,提供给用户 App 使用 + * + * @param pageReqVO 分页查询 + * @return 商品 SPU 分页 + */ + PageResult getSpuPage(AppProductSpuPageReqVO pageReqVO); + + /** + * 获得商品 SPU 列表,提供给用户 App 使用 + * + * @param recommendType 推荐类型 + * @param count 数量 + * @return 商品 SPU 列表 + */ + List getSpuList(String recommendType, Integer count); + + /** + * 更新商品 SPU 库存(增量) + * + * @param stockIncrCounts SPU 编号与库存变化(增量)的映射 + */ + void updateSpuStock(Map stockIncrCounts); + + /** + * 更新 SPU 状态 + * + * @param updateReqVO 更新请求 + */ + void updateSpuStatus(ProductSpuUpdateStatusReqVO updateReqVO); + + /** + * 获取 SPU 列表标签对应的 Count 数量 + * + * @return Count 数量 + */ + Map getTabsCount(); + + /** + * 通过分类 categoryId 查询 SPU 个数 + * + * @param categoryId 分类 categoryId + * @return SPU 数量 + */ + Long getSpuCountByCategoryId(Long categoryId); + + + /** + * 校验商品是否有效。如下情况,视为无效: + * 1. 商品编号不存在 + * 2. 商品被禁用 + * + * @param ids 商品编号数组 + * @return 商品 SPU 列表 + */ + List validateSpuList(Collection ids); + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java new file mode 100755 index 000000000..80ce88645 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java @@ -0,0 +1,296 @@ +package cn.iocoder.yudao.module.product.service.spu; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +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.category.vo.ProductCategoryListReqVO; +import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*; +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO; +import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper; +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +import cn.iocoder.yudao.module.product.service.brand.ProductBrandService; +import cn.iocoder.yudao.module.product.service.category.ProductCategoryService; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; +import com.google.common.collect.Maps; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO.CATEGORY_LEVEL; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*; + +/** + * 商品 SPU Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ProductSpuServiceImpl implements ProductSpuService { + + @Resource + private ProductSpuMapper productSpuMapper; + + @Resource + @Lazy // 循环依赖,避免报错 + private ProductSkuService productSkuService; + @Resource + private ProductBrandService brandService; + @Resource + private ProductCategoryService categoryService; + +// @Resource +// @Lazy +// private CouponTemplateApi couponTemplateApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createSpu(ProductSpuCreateReqVO createReqVO) { + // 校验分类、品牌 + validateCategory(createReqVO.getCategoryId()); + brandService.validateProductBrand(createReqVO.getBrandId()); + // 校验优惠券 + Set giveCouponTemplateIds = convertSet(createReqVO.getGiveCouponTemplates(), ProductSpuCreateReqVO.GiveCouponTemplate::getId); +// validateCouponTemplate(giveCouponTemplateIds); + // 校验 SKU + List skuSaveReqList = createReqVO.getSkus(); + productSkuService.validateSkuList(skuSaveReqList, createReqVO.getSpecType()); + + ProductSpuDO spu = ProductSpuConvert.INSTANCE.convert(createReqVO); + // 初始化 SPU 中 SKU 相关属性 + initSpuFromSkus(spu, skuSaveReqList); + // 设置优惠券 + spu.setGiveCouponTemplateIds(CollUtil.newArrayList(giveCouponTemplateIds)); + // 插入 SPU + productSpuMapper.insert(spu); + // 插入 SKU + productSkuService.createSkuList(spu.getId(), skuSaveReqList); + // 返回 + return spu.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSpu(ProductSpuUpdateReqVO updateReqVO) { + // 校验 SPU 是否存在 + validateSpuExists(updateReqVO.getId()); + // 校验分类、品牌 + validateCategory(updateReqVO.getCategoryId()); + brandService.validateProductBrand(updateReqVO.getBrandId()); + // 校验优惠券 + Set giveCouponTemplateIds = convertSet(updateReqVO.getGiveCouponTemplates(), ProductSpuUpdateReqVO.GiveCouponTemplate::getId); +// validateCouponTemplate(giveCouponTemplateIds); + // 校验SKU + List skuSaveReqList = updateReqVO.getSkus(); + productSkuService.validateSkuList(skuSaveReqList, updateReqVO.getSpecType()); + + // 更新 SPU + ProductSpuDO updateObj = ProductSpuConvert.INSTANCE.convert(updateReqVO); + initSpuFromSkus(updateObj, skuSaveReqList); + // 设置优惠券 + updateObj.setGiveCouponTemplateIds(CollUtil.newArrayList(giveCouponTemplateIds)); + productSpuMapper.updateById(updateObj); + // 批量更新 SKU + productSkuService.updateSkuList(updateObj.getId(), updateReqVO.getSkus()); + } + + /** + * 基于 SKU 的信息,初始化 SPU 的信息 + * 主要是计数相关的字段,例如说市场价、最大最小价、库存等等 + * + * @param spu 商品 SPU + * @param skus 商品 SKU 数组 + */ + private void initSpuFromSkus(ProductSpuDO spu, List skus) { + // sku 单价最低的商品的价格 + spu.setPrice(getMinValue(skus, ProductSkuCreateOrUpdateReqVO::getPrice)); + // sku 单价最低的商品的市场价格 + spu.setMarketPrice(getMinValue(skus, ProductSkuCreateOrUpdateReqVO::getMarketPrice)); + // sku 单价最低的商品的成本价格 + spu.setCostPrice(getMinValue(skus, ProductSkuCreateOrUpdateReqVO::getCostPrice)); + // sku 单价最低的商品的条形码 TODO 芋艿:条形码字段,是不是可以删除 + spu.setBarCode(""); +// spu.setBarCode(getMinValue(skus, ProductSkuCreateOrUpdateReqVO::getBarCode)); + // skus 库存总数 + spu.setStock(getSumValue(skus, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum)); + // 若是 spu 已有状态则不处理 + if (spu.getStatus() == null) { + // 默认状态为上架 + spu.setStatus(ProductSpuStatusEnum.ENABLE.getStatus()); + // 默认商品销量 + spu.setSalesCount(0); + // 默认商品浏览量 + spu.setBrowseCount(0); + } + // 如果活动顺序为空则默认初始化 + // TODO 芋艿:后续再优化 +// if (CollUtil.isEmpty(spu.getActivityOrders())) { +// spu.setActivityOrders(Arrays.stream(PromotionTypeEnum.ARRAYS).boxed().collect(Collectors.toList())); +// } + } + + /** + * 校验商品分类是否合法 + * + * @param id 商品分类编号 + */ + private void validateCategory(Long id) { + categoryService.validateCategory(id); + // 校验层级 + if (categoryService.getCategoryLevel(id) < CATEGORY_LEVEL) { + throw exception(SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR); + } + } + +// private void validateCouponTemplate(Collection ids) { +// List couponTemplateList = couponTemplateApi.getCouponTemplateListByIds(ids); +// if (couponTemplateList.size() != ids.size()) { +// throw exception(SPU_SAVE_FAIL_COUPON_TEMPLATE_NOT_EXISTS); +// } +// } + + @Override + public List validateSpuList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + // 获得商品信息 + List list = productSpuMapper.selectBatchIds(ids); + Map spuMap = CollectionUtils.convertMap(list, ProductSpuDO::getId); + // 校验 + ids.forEach(id -> { + ProductSpuDO spu = spuMap.get(id); + if (spu == null) { + throw exception(SPU_NOT_EXISTS); + } + if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) { + throw exception(SPU_NOT_ENABLE, spu.getName()); + } + }); + return list; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSpu(Long id) { + // 校验存在 + validateSpuExists(id); + // 校验商品状态不是回收站不能删除 + ProductSpuDO spuDO = productSpuMapper.selectById(id); + // 判断 SPU 状态是否为回收站 + if (ObjectUtil.notEqual(spuDO.getStatus(), ProductSpuStatusEnum.RECYCLE.getStatus())) { + throw exception(SPU_NOT_RECYCLE); + } + + // 删除 SPU + productSpuMapper.deleteById(id); + // 删除关联的 SKU + productSkuService.deleteSkuBySpuId(id); + } + + private void validateSpuExists(Long id) { + if (productSpuMapper.selectById(id) == null) { + throw exception(SPU_NOT_EXISTS); + } + } + + @Override + public ProductSpuDO getSpu(Long id) { + return productSpuMapper.selectById(id); + } + + @Override + public List getSpuList(Collection ids) { + return productSpuMapper.selectBatchIds(ids); + } + + @Override + public List getSpuListByStatus(Integer status) { + return productSpuMapper.selectList(ProductSpuDO::getStatus, status); + } + + @Override + public List getSpuList(ProductSpuExportReqVO reqVO) { + return productSpuMapper.selectList(reqVO); + } + + @Override + public PageResult getSpuPage(ProductSpuPageReqVO pageReqVO) { + return productSpuMapper.selectPage(pageReqVO); + } + + @Override + public PageResult getSpuPage(AppProductSpuPageReqVO pageReqVO) { + // 查找时,如果查找某个分类编号,则包含它的子分类。因为顶级分类不包含商品 + Set categoryIds = new HashSet<>(); + if (pageReqVO.getCategoryId() != null && pageReqVO.getCategoryId() > 0) { + categoryIds.add(pageReqVO.getCategoryId()); + List categoryChildren = categoryService.getEnableCategoryList(new ProductCategoryListReqVO() + .setParentId(pageReqVO.getCategoryId()).setStatus(CommonStatusEnum.ENABLE.getStatus())); + categoryIds.addAll(CollectionUtils.convertList(categoryChildren, ProductCategoryDO::getId)); + } + // 分页查询 + return productSpuMapper.selectPage(pageReqVO, categoryIds); + } + + @Override + public List getSpuList(String recommendType, Integer count) { + return productSpuMapper.selectListByRecommendType(recommendType, count); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSpuStock(Map stockIncrCounts) { + stockIncrCounts.forEach((id, incCount) -> productSpuMapper.updateStock(id, incCount)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSpuStatus(ProductSpuUpdateStatusReqVO updateReqVO) { + // 校验存在 + validateSpuExists(updateReqVO.getId()); + + // 更新状态 + ProductSpuDO productSpuDO = productSpuMapper.selectById(updateReqVO.getId()).setStatus(updateReqVO.getStatus()); + productSpuMapper.updateById(productSpuDO); + } + + @Override + public Map getTabsCount() { + Map counts = Maps.newLinkedHashMapWithExpectedSize(5); + // 查询销售中的商品数量 + counts.put(ProductSpuPageReqVO.FOR_SALE, + productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.ENABLE.getStatus())); + // 查询仓库中的商品数量 + counts.put(ProductSpuPageReqVO.IN_WAREHOUSE, + productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.DISABLE.getStatus())); + // 查询售空的商品数量 + counts.put(ProductSpuPageReqVO.SOLD_OUT, + productSpuMapper.selectCount(ProductSpuDO::getStock, 0)); + // 查询触发警戒库存的商品数量 + counts.put(ProductSpuPageReqVO.ALERT_STOCK, + productSpuMapper.selectCount()); + // 查询回收站中的商品数量 + counts.put(ProductSpuPageReqVO.RECYCLE_BIN, + productSpuMapper.selectCount(ProductSpuDO::getStatus, ProductSpuStatusEnum.RECYCLE.getStatus())); + return counts; + } + + @Override + public Long getSpuCountByCategoryId(Long categoryId) { + return productSpuMapper.selectCount(ProductSpuDO::getCategoryId, categoryId); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/resources/application-dev.yaml b/yudao-module-mall/yudao-module-product-biz/src/main/resources/application-dev.yaml new file mode 100644 index 000000000..d8a81c9a3 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/resources/application-dev.yaml @@ -0,0 +1,113 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 5 # 初始连接数 + min-idle: 10 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + name: ruoyi-vue-pro + url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true + driver-class-name: com.mysql.jdbc.Driver + username: root + password: 3WLiVUBEwTbvAfsh + slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 + name: ruoyi-vue-pro + url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true + driver-class-name: com.mysql.jdbc.Driver + username: root + password: 3WLiVUBEwTbvAfsh + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + redis: + host: 400-infra.server.iocoder.cn # 地址 + port: 6379 # 端口 + database: 1 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### +spring: + cloud: + stream: + rocketmq: + # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 + binder: + name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 + +--- #################### 定时任务相关配置 #################### +xxl: + job: + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + # Spring Boot Admin Server 服务端的相关配置 + context-path: /admin # 配置 Spring + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + xss: + enable: false + web: + admin-ui: + url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 + demo: true # 开启演示模式 diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/resources/application-local.yaml b/yudao-module-mall/yudao-module-product-biz/src/main/resources/application-local.yaml new file mode 100644 index 000000000..1a2e58dbf --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/resources/application-local.yaml @@ -0,0 +1,141 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源 + - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 1 # 初始连接数 + min-idle: 1 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + name: ruoyi-vue-pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 +# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 +# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例 +# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 +# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 + username: root + password: 123456 +# username: sa +# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + slave: # 模拟从库,可根据自己需要修改 + name: ruoyi-vue-pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 +# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 +# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 +# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 +# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 + username: root + password: 123456 +# username: sa +# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 0 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### +spring: + cloud: + stream: + rocketmq: + # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 + binder: + name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 + binding-retry-interval: 7200 # 消息绑定重试间隔时间,单位:秒,默认为 30 秒。考虑到本地可能不启动 RocketMQ 服务,设置为 2 小时 + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + enabled: false # 是否开启调度中心,默认为 true 开启 + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +# 日志文件配置 +logging: + level: + # 配置自己写的 MyBatis Mapper 打印日志 + cn.iocoder.yudao.module.system.dal.mysql: debug + cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper: INFO # 配置 SensitiveWordMapper 的日志级别为 info + cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 配置 SmsChannelMapper 的日志级别为 info + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + env: # 多环境的配置项 + tag: ${HOSTNAME} + web: + admin-ui: + url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 + security: + mock-enable: true + xss: + enable: false + access-log: # 访问日志的配置项 + enable: false + error-code: # 错误码相关配置项 + enable: false + demo: false # 关闭演示模式 diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/resources/application.yaml b/yudao-module-mall/yudao-module-product-biz/src/main/resources/application.yaml new file mode 100644 index 000000000..5ed19cb6f --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/resources/application.yaml @@ -0,0 +1,134 @@ +spring: + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务 + + # Servlet 配置 + servlet: + # 文件上传相关配置项 + multipart: + max-file-size: 16MB # 单个文件大小 + max-request-size: 32MB # 设置总上传的文件大小 + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类 + + # Jackson 配置项 + jackson: + serialization: + write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳 + write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401 + write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳 + fail-on-empty-beans: false # 允许序列化无属性的 Bean + + # Cache 配置项 + cache: + type: REDIS + redis: + time-to-live: 1h # 设置过期时间为 1 小时 + +--- #################### 接口文档配置 #################### + +springdoc: + api-docs: + enabled: true # 1. 是否开启 Swagger 接文档的元数据 + path: /v3/api-docs + swagger-ui: + enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面 + path: /swagger-ui.html + default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档 + +knife4j: + enable: true # 2.2 是否开启 Swagger 文档的 Knife4j UI 界面 + setting: + language: zh_cn + +# MyBatis Plus 的配置项 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 + global-config: + db-config: + id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 + # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) + banner: false # 关闭控制台的 Banner 打印 + type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject + encryptor: + password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成 + +mybatis-plus-join: + banner: false # 关闭控制台的 Banner 打印 + +# Spring Data Redis 配置 +spring: + data: + redis: + repositories: + enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度 + +--- #################### RPC 远程调用相关配置 #################### + +--- #################### MQ 消息队列相关配置 #################### + +spring: + cloud: + # Spring Cloud Stream 配置项,对应 BindingServiceProperties 类 + stream: + function: + definition: busConsumer; + # Binding 配置项,对应 BindingProperties Map + # Spring Cloud Stream RocketMQ 配置项 + rocketmq: + # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类 + binder: + name-server: 127.0.0.1:9876 # RocketMQ Namesrv 地址 + default: # 默认 bindings 全局配置 + producer: # RocketMQ Producer 配置项,对应 RocketMQProducerProperties 类 + group: product_producer_group # 生产者分组 + send-type: SYNC # 发送模式,SYNC 同步 + bindings: + springCloudBusInput: + consumer: + message-model: BROADCASTING # 重要,解决 Spring Cloud Bus RocketMQ 默认不是 BROADCASTING 广播消费的问题 + + # Spring Cloud Bus 配置项,对应 BusProperties 类 + bus: + enabled: true # 是否开启,默认为 true + id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式 + destination: springCloudBus # 目标消息队列,默认为 springCloudBus + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + executor: + appname: ${spring.application.name} # 执行器 AppName + logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 + accessToken: default_token # 执行器通讯TOKEN + +--- #################### 芋道相关配置 #################### + +yudao: + info: + version: 1.0.0 + base-package: cn.iocoder.yudao.module.product + swagger: + title: 管理后台 + description: 提供管理员管理的所有功能 + version: ${yudao.info.version} + base-package: ${yudao.info.base-package} + captcha: + enable: true # 验证码的开关,默认为 true; + error-code: # 错误码相关配置项 + constants-class-list: + - cn.iocoder.yudao.module.product.enums.ErrorCodeConstants + tenant: # 多租户相关配置项 + enable: true + ignore-urls: + ignore-tables: + +debug: false diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/resources/bootstrap-local.yaml b/yudao-module-mall/yudao-module-product-biz/src/main/resources/bootstrap-local.yaml new file mode 100644 index 000000000..2de0efbf7 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/resources/bootstrap-local.yaml @@ -0,0 +1,23 @@ +--- #################### 注册中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 127.0.0.1:8848 + discovery: + namespace: dev # 命名空间。这里使用 dev 开发环境 + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + +--- #################### 配置中心相关配置 #################### + +spring: + cloud: + nacos: + # Nacos Config 配置项,对应 NacosConfigProperties 配置属性类 + config: + server-addr: 127.0.0.1:8848 # Nacos 服务器地址 + namespace: dev # 命名空间 dev 的ID,不能直接使用 dev 名称。创建命名空间的时候需要指定ID为 dev,这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + name: ${spring.application.name} # 使用的 Nacos 配置集的 dataId,默认为 spring.application.name + file-extension: yaml # 使用的 Nacos 配置集的 dataId 的文件拓展名,同时也是 Nacos 配置集的配置格式,默认为 properties diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/resources/bootstrap.yaml b/yudao-module-mall/yudao-module-product-biz/src/main/resources/bootstrap.yaml new file mode 100644 index 000000000..b724707d1 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/resources/bootstrap.yaml @@ -0,0 +1,14 @@ +spring: + application: + name: product-server + + profiles: + active: local + +server: + port: 48100 + +# 日志文件配置。注意,如果 logging.file.name 不放在 bootstrap.yaml 配置文件,而是放在 application.yaml 中,会导致出现 LOG_FILE_IS_UNDEFINED 文件 +logging: + file: + name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/resources/logback-spring.xml b/yudao-module-mall/yudao-module-product-biz/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..b1b9f3faf --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImplTest.java new file mode 100644 index 000000000..1b5c68ba4 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/brand/ProductBrandServiceImplTest.java @@ -0,0 +1,133 @@ +package cn.iocoder.yudao.module.product.service.brand; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandCreateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandPageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.brand.vo.ProductBrandUpdateReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO; +import cn.iocoder.yudao.module.product.dal.mysql.brand.ProductBrandMapper; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.BRAND_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +/** +* {@link ProductBrandServiceImpl} 的单元测试类 +* +* @author 芋道源码 +*/ +@Import(ProductBrandServiceImpl.class) +public class ProductBrandServiceImplTest extends BaseDbUnitTest { + + @Resource + private ProductBrandServiceImpl brandService; + + @Resource + private ProductBrandMapper brandMapper; + + @Test + public void testCreateBrand_success() { + // 准备参数 + ProductBrandCreateReqVO reqVO = randomPojo(ProductBrandCreateReqVO.class); + + // 调用 + Long brandId = brandService.createBrand(reqVO); + // 断言 + assertNotNull(brandId); + // 校验记录的属性是否正确 + ProductBrandDO brand = brandMapper.selectById(brandId); + assertPojoEquals(reqVO, brand); + } + + @Test + public void testUpdateBrand_success() { + // mock 数据 + ProductBrandDO dbBrand = randomPojo(ProductBrandDO.class); + brandMapper.insert(dbBrand);// @Sql: 先插入出一条存在的数据 + // 准备参数 + ProductBrandUpdateReqVO reqVO = randomPojo(ProductBrandUpdateReqVO.class, o -> { + o.setId(dbBrand.getId()); // 设置更新的 ID + }); + + // 调用 + brandService.updateBrand(reqVO); + // 校验是否更新正确 + ProductBrandDO brand = brandMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, brand); + } + + @Test + public void testUpdateBrand_notExists() { + // 准备参数 + ProductBrandUpdateReqVO reqVO = randomPojo(ProductBrandUpdateReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> brandService.updateBrand(reqVO), BRAND_NOT_EXISTS); + } + + @Test + public void testDeleteBrand_success() { + // mock 数据 + ProductBrandDO dbBrand = randomPojo(ProductBrandDO.class); + brandMapper.insert(dbBrand);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbBrand.getId(); + + // 调用 + brandService.deleteBrand(id); + // 校验数据不存在了 + assertNull(brandMapper.selectById(id)); + } + + @Test + public void testDeleteBrand_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> brandService.deleteBrand(id), BRAND_NOT_EXISTS); + } + + @Test + public void testGetBrandPage() { + // mock 数据 + ProductBrandDO dbBrand = randomPojo(ProductBrandDO.class, o -> { // 等会查询到 + o.setName("芋道源码"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setCreateTime(buildTime(2022, 2, 1)); + }); + brandMapper.insert(dbBrand); + // 测试 name 不匹配 + brandMapper.insert(cloneIgnoreId(dbBrand, o -> o.setName("源码"))); + // 测试 status 不匹配 + brandMapper.insert(cloneIgnoreId(dbBrand, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 createTime 不匹配 + brandMapper.insert(cloneIgnoreId(dbBrand, o -> o.setCreateTime(buildTime(2022, 3, 1)))); + // 准备参数 + ProductBrandPageReqVO reqVO = new ProductBrandPageReqVO(); + reqVO.setName("芋道"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setCreateTime((new LocalDateTime[]{buildTime(2022, 1, 1), buildTime(2022, 2, 25)})); + + // 调用 + PageResult pageResult = brandService.getBrandPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbBrand, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImplTest.java new file mode 100644 index 000000000..37e262d9a --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImplTest.java @@ -0,0 +1,161 @@ +package cn.iocoder.yudao.module.product.service.category; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +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.ProductCategoryUpdateReqVO; +import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; +import cn.iocoder.yudao.module.product.dal.mysql.category.ProductCategoryMapper; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO.PARENT_ID_NULL; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.CATEGORY_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link ProductCategoryServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import(ProductCategoryServiceImpl.class) +public class ProductCategoryServiceImplTest extends BaseDbUnitTest { + + @Resource + private ProductCategoryServiceImpl productCategoryService; + + @Resource + private ProductCategoryMapper productCategoryMapper; + + @Test + public void testCreateCategory_success() { + // 准备参数 + ProductCategoryCreateReqVO reqVO = randomPojo(ProductCategoryCreateReqVO.class); + + // mock 父类 + ProductCategoryDO parentProductCategory = randomPojo(ProductCategoryDO.class, o -> { + reqVO.setParentId(o.getId()); + o.setParentId(PARENT_ID_NULL); + }); + productCategoryMapper.insert(parentProductCategory); + + // 调用 + Long categoryId = productCategoryService.createCategory(reqVO); + // 断言 + assertNotNull(categoryId); + // 校验记录的属性是否正确 + ProductCategoryDO category = productCategoryMapper.selectById(categoryId); + assertPojoEquals(reqVO, category); + } + + @Test + public void testUpdateCategory_success() { + // mock 数据 + ProductCategoryDO dbCategory = randomPojo(ProductCategoryDO.class); + productCategoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据 + // 准备参数 + ProductCategoryUpdateReqVO reqVO = randomPojo(ProductCategoryUpdateReqVO.class, o -> { + o.setId(dbCategory.getId()); // 设置更新的 ID + }); + // mock 父类 + ProductCategoryDO parentProductCategory = randomPojo(ProductCategoryDO.class, o -> o.setId(reqVO.getParentId())); + productCategoryMapper.insert(parentProductCategory); + + // 调用 + productCategoryService.updateCategory(reqVO); + // 校验是否更新正确 + ProductCategoryDO category = productCategoryMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, category); + } + + @Test + public void testUpdateCategory_notExists() { + // 准备参数 + ProductCategoryUpdateReqVO reqVO = randomPojo(ProductCategoryUpdateReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> productCategoryService.updateCategory(reqVO), CATEGORY_NOT_EXISTS); + } + + @Test + public void testDeleteCategory_success() { + // mock 数据 + ProductCategoryDO dbCategory = randomPojo(ProductCategoryDO.class); + productCategoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbCategory.getId(); + + // 调用 + productCategoryService.deleteCategory(id); + // 校验数据不存在了 + assertNull(productCategoryMapper.selectById(id)); + } + + @Test + public void testDeleteCategory_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> productCategoryService.deleteCategory(id), CATEGORY_NOT_EXISTS); + } + + @Test + public void testGetCategoryLevel() { + // mock 数据 + ProductCategoryDO category1 = randomPojo(ProductCategoryDO.class, + o -> o.setParentId(PARENT_ID_NULL)); + productCategoryMapper.insert(category1); + ProductCategoryDO category2 = randomPojo(ProductCategoryDO.class, + o -> o.setParentId(category1.getId())); + productCategoryMapper.insert(category2); + ProductCategoryDO category3 = randomPojo(ProductCategoryDO.class, + o -> o.setParentId(category2.getId())); + productCategoryMapper.insert(category3); + + // 调用,并断言 + assertEquals(productCategoryService.getCategoryLevel(category1.getId()), 1); + assertEquals(productCategoryService.getCategoryLevel(category2.getId()), 2); + assertEquals(productCategoryService.getCategoryLevel(category3.getId()), 3); + } + + @Test + public void testGetCategoryList() { + // mock 数据 + ProductCategoryDO dbCategory = randomPojo(ProductCategoryDO.class, o -> { // 等会查询到 + o.setName("奥特曼"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setParentId(PARENT_ID_NULL); + }); + productCategoryMapper.insert(dbCategory); + // 测试 name 不匹配 + productCategoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName("奥特块"))); + // 测试 status 不匹配 + productCategoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 parentId 不匹配 + productCategoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setParentId(3333L))); + // 准备参数 + ProductCategoryListReqVO reqVO = new ProductCategoryListReqVO(); + reqVO.setName("特曼"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setParentId(PARENT_ID_NULL); + + // 调用 + List list = productCategoryService.getEnableCategoryList(reqVO); + List all = productCategoryService.getEnableCategoryList(new ProductCategoryListReqVO()); + // 断言 + assertEquals(1, list.size()); + assertEquals(4, all.size()); + assertPojoEquals(dbCategory, list.get(0)); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java new file mode 100644 index 000000000..fb6bc3649 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java @@ -0,0 +1,193 @@ +package cn.iocoder.yudao.module.product.service.comment; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentPageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentReplyReqVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentRespVO; +import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO; +import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO; +import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper; +import cn.iocoder.yudao.module.product.enums.comment.ProductCommentScoresEnum; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Lazy; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +// TODO 芋艿:单测详细 review 下 +/** + * {@link ProductCommentServiceImpl} 的单元测试类 + * + * @author wangzhs + */ +@Import(ProductCommentServiceImpl.class) +public class ProductCommentServiceImplTest extends BaseDbUnitTest { + + @Resource + private ProductCommentMapper productCommentMapper; + + @Resource + @Lazy + private ProductCommentServiceImpl productCommentService; + + @MockBean + private ProductSpuService productSpuService; + @MockBean + private ProductSkuService productSkuService; + + public String generateNo() { + return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999); + } + + public Long generateId() { + return RandomUtil.randomLong(100000, 999999); + } + + @Test + public void testCreateCommentAndGet_success() { + // mock 测试 + ProductCommentDO productComment = randomPojo(ProductCommentDO.class); + productCommentMapper.insert(productComment); + + // 断言 + // 校验记录的属性是否正确 + ProductCommentDO comment = productCommentMapper.selectById(productComment.getId()); + assertPojoEquals(productComment, comment); + } + + @Test + public void testGetCommentPage_success() { + // 准备参数 + ProductCommentDO productComment = randomPojo(ProductCommentDO.class, o -> { + o.setUserNickname("王二狗"); + o.setSpuName("感冒药"); + o.setScores(ProductCommentScoresEnum.FOUR.getScores()); + o.setReplyStatus(Boolean.TRUE); + o.setVisible(Boolean.TRUE); + o.setId(generateId()); + o.setUserId(generateId()); + o.setAnonymous(Boolean.TRUE); + o.setOrderId(generateId()); + o.setOrderItemId(generateId()); + o.setSpuId(generateId()); + o.setSkuId(generateId()); + o.setDescriptionScores(ProductCommentScoresEnum.FOUR.getScores()); + o.setBenefitScores(ProductCommentScoresEnum.FOUR.getScores()); + o.setContent("真好吃"); + o.setReplyUserId(generateId()); + o.setReplyContent("确实"); + o.setReplyTime(LocalDateTime.now()); + o.setCreateTime(LocalDateTime.now()); + o.setUpdateTime(LocalDateTime.now()); + }); + productCommentMapper.insert(productComment); + + Long orderId = productComment.getOrderId(); + Long spuId = productComment.getSpuId(); + + // 测试 userNickname 不匹配 + productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setUserNickname("王三").setScores(ProductCommentScoresEnum.ONE.getScores()))); + // 测试 orderId 不匹配 + productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setOrderId(generateId()))); + // 测试 spuId 不匹配 + productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setSpuId(generateId()))); + // 测试 spuName 不匹配 + productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setSpuName("感康"))); + // 测试 scores 不匹配 + productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setScores(ProductCommentScoresEnum.ONE.getScores()))); + // 测试 replied 不匹配 + productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setReplyStatus(Boolean.FALSE))); + // 测试 visible 不匹配 + productCommentMapper.insert(cloneIgnoreId(productComment, o -> o.setVisible(Boolean.FALSE))); + + // 调用 + ProductCommentPageReqVO productCommentPageReqVO = new ProductCommentPageReqVO(); + productCommentPageReqVO.setUserNickname("王二"); + productCommentPageReqVO.setOrderId(orderId); + productCommentPageReqVO.setSpuId(spuId); + productCommentPageReqVO.setSpuName("感冒药"); + productCommentPageReqVO.setScores(ProductCommentScoresEnum.FOUR.getScores()); + productCommentPageReqVO.setReplyStatus(Boolean.TRUE); + + PageResult commentPage = productCommentService.getCommentPage(productCommentPageReqVO); + PageResult result = ProductCommentConvert.INSTANCE.convertPage(productCommentMapper.selectPage(productCommentPageReqVO)); + assertEquals(result.getTotal(), commentPage.getTotal()); + + PageResult all = productCommentService.getCommentPage(new ProductCommentPageReqVO()); + assertEquals(8, all.getTotal()); + + // 测试获取所有商品分页评论数据 + PageResult result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE); + assertEquals(7, result1.getTotal()); + + // 测试获取所有商品分页中评数据 + PageResult result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE); + assertEquals(2, result2.getTotal()); + + // 测试获取指定 spuId 商品分页中评数据 + PageResult result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE); + assertEquals(2, result3.getTotal()); + + // 测试分页 tab count + AppCommentStatisticsRespVO tabsCount = productCommentService.getCommentStatistics(spuId, Boolean.TRUE); + assertEquals(4, tabsCount.getGoodCount()); + assertEquals(2, tabsCount.getMediocreCount()); + assertEquals(0, tabsCount.getNegativeCount()); + + } + + @Test + public void testUpdateCommentVisible_success() { + // mock 测试 + ProductCommentDO productComment = randomPojo(ProductCommentDO.class, o -> { + o.setVisible(Boolean.TRUE); + }); + productCommentMapper.insert(productComment); + + Long productCommentId = productComment.getId(); + + ProductCommentUpdateVisibleReqVO updateReqVO = new ProductCommentUpdateVisibleReqVO(); + updateReqVO.setId(productCommentId); + updateReqVO.setVisible(Boolean.FALSE); + productCommentService.updateCommentVisible(updateReqVO); + + ProductCommentDO productCommentDO = productCommentMapper.selectById(productCommentId); + assertFalse(productCommentDO.getVisible()); + } + + + @Test + public void testCommentReply_success() { + // mock 测试 + ProductCommentDO productComment = randomPojo(ProductCommentDO.class); + productCommentMapper.insert(productComment); + + Long productCommentId = productComment.getId(); + + ProductCommentReplyReqVO replyVO = new ProductCommentReplyReqVO(); + replyVO.setId(productCommentId); + replyVO.setReplyContent("测试"); + productCommentService.replyComment(replyVO, 1L); + + ProductCommentDO productCommentDO = productCommentMapper.selectById(productCommentId); + assertEquals("测试", productCommentDO.getReplyContent()); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java new file mode 100644 index 000000000..9df947b43 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java @@ -0,0 +1,205 @@ +package cn.iocoder.yudao.module.product.service.sku; + +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.framework.test.core.util.AssertUtils; +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.dal.dataobject.sku.ProductSkuDO; +import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; + +/** + * {@link ProductSkuServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(ProductSkuServiceImpl.class) +public class ProductSkuServiceTest extends BaseDbUnitTest { + + @Resource + private ProductSkuService productSkuService; + + @Resource + private ProductSkuMapper productSkuMapper; + + @MockBean + private ProductSpuService productSpuService; + @MockBean + private ProductPropertyService productPropertyService; + @MockBean + private ProductPropertyValueService productPropertyValueService; + + public Long generateId() { + return RandomUtil.randomLong(100000, 999999); + } + + @Test + public void testUpdateSkuList() { + // mock 数据 + ProductSkuDO sku01 = randomPojo(ProductSkuDO.class, o -> { // 测试更新 + o.setSpuId(1L); + o.setProperties(singletonList(new ProductSkuDO.Property( + 10L, "颜色", 20L, "红色"))); + }); + productSkuMapper.insert(sku01); + ProductSkuDO sku02 = randomPojo(ProductSkuDO.class, o -> { // 测试删除 + o.setSpuId(1L); + o.setProperties(singletonList(new ProductSkuDO.Property( + 10L, "颜色", 30L, "蓝色"))); + + }); + productSkuMapper.insert(sku02); + // 准备参数 + Long spuId = 1L; + String spuName = "测试商品"; + List skus = Arrays.asList( + randomPojo(ProductSkuCreateOrUpdateReqVO.class, o -> { // 测试更新 + o.setProperties(singletonList(new ProductSkuCreateOrUpdateReqVO.Property( + 10L, "颜色", 20L, "红色"))); + }), + randomPojo(ProductSkuCreateOrUpdateReqVO.class, o -> { // 测试新增 + o.setProperties(singletonList(new ProductSkuCreateOrUpdateReqVO.Property( + 10L, "颜色", 20L, "红色"))); + }) + ); + + // 调用 + productSkuService.updateSkuList(spuId, skus); + // 断言 + List dbSkus = productSkuMapper.selectListBySpuId(spuId); + assertEquals(dbSkus.size(), 2); + // 断言更新的 + assertEquals(dbSkus.get(0).getId(), sku01.getId()); + assertPojoEquals(dbSkus.get(0), skus.get(0), "properties"); + assertEquals(skus.get(0).getProperties().size(), 1); + assertPojoEquals(dbSkus.get(0).getProperties().get(0), skus.get(0).getProperties().get(0)); + // 断言新增的 + assertNotEquals(dbSkus.get(1).getId(), sku02.getId()); + assertPojoEquals(dbSkus.get(1), skus.get(1), "properties"); + assertEquals(skus.get(1).getProperties().size(), 1); + assertPojoEquals(dbSkus.get(1).getProperties().get(0), skus.get(1).getProperties().get(0)); + } + + @Test + public void testUpdateSkuStock_incrSuccess() { + // 准备参数 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO() + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(10))); + // mock 数据 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> { + o.setId(1L).setSpuId(10L).setStock(20); + o.getProperties().forEach(p -> { + // 指定 id 范围 解决 Value too long + p.setPropertyId(generateId()); + p.setValueId(generateId()); + }); + })); + + // 调用 + productSkuService.updateSkuStock(updateStockReqDTO); + // 断言 + ProductSkuDO sku = productSkuMapper.selectById(1L); + assertEquals(sku.getStock(), 30); + verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> { + assertEquals(spuStockIncrCounts.size(), 1); + assertEquals(spuStockIncrCounts.get(10L), 10); + return true; + })); + } + + @Test + public void testUpdateSkuStock_decrSuccess() { + // 准备参数 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO() + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(-10))); + // mock 数据 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> { + o.setId(1L).setSpuId(10L).setStock(20); + o.getProperties().forEach(p -> { + // 指定 id 范围 解决 Value too long + p.setPropertyId(generateId()); + p.setValueId(generateId()); + }); + })); + + // 调用 + productSkuService.updateSkuStock(updateStockReqDTO); + // 断言 + ProductSkuDO sku = productSkuMapper.selectById(1L); + assertEquals(sku.getStock(), 10); + verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> { + assertEquals(spuStockIncrCounts.size(), 1); + assertEquals(spuStockIncrCounts.get(10L), -10); + return true; + })); + } + + @Test + public void testUpdateSkuStock_decrFail() { + // 准备参数 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO() + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncrCount(-30))); + // mock 数据 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> { + o.setId(1L).setSpuId(10L).setStock(20); + o.getProperties().forEach(p -> { + // 指定 id 范围 解决 Value too long + p.setPropertyId(generateId()); + p.setValueId(generateId()); + }); + })); + // 调用并断言 + AssertUtils.assertServiceException(() -> productSkuService.updateSkuStock(updateStockReqDTO), + SKU_STOCK_NOT_ENOUGH); + } + + @Test + public void testDeleteSku_success() { + ProductSkuDO dbSku = randomPojo(ProductSkuDO.class, o -> { + o.setId(generateId()).setSpuId(generateId()); + o.getProperties().forEach(p -> { + // 指定 id 范围 解决 Value too long + p.setPropertyId(generateId()); + p.setValueId(generateId()); + }); + }); + // mock 数据 + productSkuMapper.insert(dbSku); + // 准备参数 + Long id = dbSku.getId(); + + // 调用 + productSkuService.deleteSku(id); + // 校验数据不存在了 + assertNull(productSkuMapper.selectById(id)); + } + + @Test + public void testDeleteSku_notExists() { + // 准备参数 + Long id = 1L; + + // 调用, 并断言异常 + assertServiceException(() -> productSkuService.deleteSku(id), SKU_NOT_EXISTS); + } +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java new file mode 100755 index 000000000..dcda35550 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java @@ -0,0 +1,503 @@ +package cn.iocoder.yudao.module.product.service.spu; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuCreateReqVO; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuRespVO; +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuUpdateReqVO; +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 cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +import cn.iocoder.yudao.module.product.service.brand.ProductBrandServiceImpl; +import cn.iocoder.yudao.module.product.service.category.ProductCategoryServiceImpl; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; +import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; +import cn.iocoder.yudao.module.product.service.sku.ProductSkuServiceImpl; +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import javax.annotation.Resource; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.assertj.core.util.Lists.newArrayList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +// TODO @芋艿:review 下单元测试 + +/** + * {@link ProductSpuServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import(ProductSpuServiceImpl.class) +public class ProductSpuServiceImplTest extends BaseDbUnitTest { + + @Resource + private ProductSpuServiceImpl productSpuService; + + @Resource + private ProductSpuMapper productSpuMapper; + + @MockBean + private ProductSkuServiceImpl productSkuService; + @MockBean + private ProductCategoryServiceImpl categoryService; + @MockBean + private ProductBrandServiceImpl brandService; + @MockBean + private ProductPropertyService productPropertyService; + @MockBean + private ProductPropertyValueService productPropertyValueService; + + public String generateNo() { + return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999); + } + + public Long generateId() { + return RandomUtil.randomLong(100000, 999999); + } + + public int generaInt(){return RandomUtil.randomInt(1,9999999);} + + // TODO @芋艿:单测后续 review 哈 + + @Test + public void testCreateSpu_success() { + // 准备参数 + ProductSkuCreateOrUpdateReqVO skuCreateOrUpdateReqVO = randomPojo(ProductSkuCreateOrUpdateReqVO.class,o->{ + // 限制范围为正整数 + o.setCostPrice(generaInt()); + o.setPrice(generaInt()); + o.setMarketPrice(generaInt()); + o.setStock(generaInt()); + o.setWarnStock(10); + o.setFirstBrokeragePrice(generaInt()); + o.setSecondBrokeragePrice(generaInt()); + // 限制分数为两位数 + o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); + o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); + }); + ProductSpuCreateReqVO createReqVO = randomPojo(ProductSpuCreateReqVO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO)); + }); + when(categoryService.getCategoryLevel(eq(createReqVO.getCategoryId()))).thenReturn(2); + Long spu = productSpuService.createSpu(createReqVO); + ProductSpuDO productSpuDO = productSpuMapper.selectById(spu); + assertPojoEquals(createReqVO, productSpuDO); + } + + @Test + public void testUpdateSpu_success() { + // 准备参数 + ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(generaInt()); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + }); + productSpuMapper.insert(createReqVO); + // 准备参数 + ProductSkuCreateOrUpdateReqVO skuCreateOrUpdateReqVO = randomPojo(ProductSkuCreateOrUpdateReqVO.class,o->{ + // 限制范围为正整数 + o.setCostPrice(generaInt()); + o.setPrice(generaInt()); + o.setMarketPrice(generaInt()); + o.setStock(generaInt()); + o.setWarnStock(10); + o.setFirstBrokeragePrice(generaInt()); + o.setSecondBrokeragePrice(generaInt()); + // 限制分数为两位数 + o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); + o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); + }); + // 准备参数 + ProductSpuUpdateReqVO reqVO = randomPojo(ProductSpuUpdateReqVO.class, o -> { + o.setId(createReqVO.getId()); // 设置更新的 ID + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + o.setStatus(0); + o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO)); + }); + when(categoryService.getCategoryLevel(eq(reqVO.getCategoryId()))).thenReturn(2); + // 调用 + productSpuService.updateSpu(reqVO); + // 校验是否更新正确 + ProductSpuDO spu = productSpuMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, spu); + } + + @Test + public void testValidateSpuExists_exception() { + ProductSpuUpdateReqVO reqVO = randomPojo(ProductSpuUpdateReqVO.class); + // 调用 + Assertions.assertThrows(ServiceException.class, () -> productSpuService.updateSpu(reqVO)); + } + + @Test + void deleteSpu() { + // 准备参数 + ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(generaInt()); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + o.setStatus(-1); // 加入回收站才可删除 + }); + productSpuMapper.insert(createReqVO); + + // 调用 + productSpuService.deleteSpu(createReqVO.getId()); + + Assertions.assertNull(productSpuMapper.selectById(createReqVO.getId())); + } + + @Test + void getSpu() { + // 准备参数 + ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(generaInt()); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + }); + productSpuMapper.insert(createReqVO); + + ProductSpuDO spu = productSpuService.getSpu(createReqVO.getId()); + assertPojoEquals(createReqVO, spu); + } + + @Test + void getSpuList() { + // 准备参数 + ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(generaInt()); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + }), randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(generaInt()); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + })); + productSpuMapper.insertBatch(createReqVOs); + + // 调用 + List spuList = productSpuService.getSpuList(createReqVOs.stream().map(ProductSpuDO::getId).collect(Collectors.toList())); + Assertions.assertIterableEquals(createReqVOs, spuList); + } + + @Test + void getSpuPage_alarmStock_empty() { + // 准备参数 + ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(11); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + }), randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(11); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + })); + productSpuMapper.insertBatch(createReqVOs); + // 调用 + ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); + productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); + + PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); + + PageResult result = PageResult.empty(); + Assertions.assertIterableEquals(result.getList(), spuPage.getList()); + assertEquals(spuPage.getTotal(), result.getTotal()); + } + + @Test + void getSpuPage_alarmStock() { + // 准备参数 + ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(5); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + }), randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(9); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + })); + productSpuMapper.insertBatch(createReqVOs); + // 调用 + ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); + productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); + PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); + assertEquals(createReqVOs.size(), spuPage.getTotal()); + } + + @Test + void testGetSpuPage() { + // 准备参数 + ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(generaInt()); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + }); + + // 准备参数 + productSpuMapper.insert(createReqVO); + // 测试 status 不匹配 + productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setStatus(ProductSpuStatusEnum.DISABLE.getStatus()))); + productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setStatus(ProductSpuStatusEnum.RECYCLE.getStatus()))); + // 测试 SpecType 不匹配 + productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setSpecType(true))); + // 测试 BrandId 不匹配 + productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setBrandId(generateId()))); + // 测试 CategoryId 不匹配 + productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setCategoryId(generateId()))); + + // 调用 + ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); + // 查询条件 按需打开 + //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); + //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.RECYCLE_BIN); + //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.FOR_SALE); + //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.IN_WAREHOUSE); + //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.SOLD_OUT); + //productSpuPageReqVO.setName(createReqVO.getName()); + //productSpuPageReqVO.setCategoryId(createReqVO.getCategoryId()); + + PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); + + PageResult result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO)); + assertEquals(result.getTotal(), spuPage.getTotal()); + } + + /** + * 生成笛卡尔积 + * + * @param data 数据 + * @return 笛卡尔积 + */ + public static List> cartesianProduct(List> data) { + List> res = null; // 结果集(当前为第N个List,则该处存放的就为前N-1个List的笛卡尔积集合) + for (List list : data) { // 遍历数据 + List> temp = new ArrayList<>(); // 临时结果集,存放本次循环后生成的笛卡尔积集合 + if (res == null) { // 结果集为null表示第一次循环既list为第一个List + for (T t : list) { // 便利第一个List + // 利用stream生成List,第一个List的笛卡尔积集合约等于自己本身(需要创建一个List并把对象添加到当中),存放到临时结果集 + temp.add(Stream.of(t).collect(Collectors.toList())); + } + res = temp; // 将临时结果集赋值给结果集 + continue; // 跳过本次循环 + } + // 不为第一个List,计算前面的集合(笛卡尔积)和当前List的笛卡尔积集合 + for (T t : list) { // 便利 + for (List rl : res) { // 便利前面的笛卡尔积集合 + // 利用stream生成List + temp.add(Stream.concat(rl.stream(), Stream.of(t)).collect(Collectors.toList())); + } + } + res = temp; // 将临时结果集赋值给结果集 + } + // 返回结果 + return res; + } + + @Test + public void testUpdateSpuStock() { + // 准备参数 + Map stockIncrCounts = MapUtil.builder(1L, 10).put(2L, -20).build(); + // mock 方法(数据) + productSpuMapper.insert(randomPojo(ProductSpuDO.class, o ->{ + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(generaInt()); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + o.setId(1L).setStock(20); + })); + productSpuMapper.insert(randomPojo(ProductSpuDO.class, o -> { + o.setCategoryId(generateId()); + o.setBrandId(generateId()); + o.setDeliveryTemplateId(generateId()); + o.setUnit(RandomUtil.randomInt(1,20)); // 限制商品单位范围 + o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 + o.setActivityOrders(newArrayList(1,3,2,4,5)); // 活动排序 + o.setPrice(generaInt()); // 限制范围为正整数 + o.setMarketPrice(generaInt()); // 限制范围为正整数 + o.setCostPrice(generaInt()); // 限制范围为正整数 + o.setStock(generaInt()); // 限制范围为正整数 + o.setGiveIntegral(generaInt()); // 限制范围为正整数 + o.setSalesCount(generaInt()); // 限制范围为正整数 + o.setBrowseCount(generaInt()); // 限制范围为正整数 + o.setId(2L).setStock(30); + })); + + // 调用 + productSpuService.updateSpuStock(stockIncrCounts); + // 断言 + assertEquals(productSpuService.getSpu(1L).getStock(), 30); + assertEquals(productSpuService.getSpu(2L).getStock(), 10); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/resources/application-unit-test.yaml b/yudao-module-mall/yudao-module-product-biz/src/test/resources/application-unit-test.yaml new file mode 100644 index 000000000..31e5ae5c9 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/resources/application-unit-test.yaml @@ -0,0 +1,50 @@ +spring: + main: + lazy-initialization: true # 开启懒加载,加快速度 + banner-mode: off # 单元测试,禁用 Banner + +--- #################### 数据库相关配置 #################### + +spring: + # 数据源配置项 + datasource: + name: ruoyi-vue-pro + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 + initial-size: 1 # 单元测试,配置为 1,提升启动速度 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + redis: + host: 127.0.0.1 # 地址 + port: 16379 # 端口(单元测试,使用 16379 端口) + database: 0 # 数据库索引 + +mybatis-plus: + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject + +--- #################### 定时任务相关配置 #################### + +--- #################### 配置中心相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项(单元测试,禁用 Lock4j) + +# Resilience4j 配置项 + +--- #################### 监控相关配置 #################### + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + info: + base-package: cn.iocoder.yudao diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/resources/logback.xml b/yudao-module-mall/yudao-module-product-biz/src/test/resources/logback.xml new file mode 100644 index 000000000..daf756bff --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/resources/logback.xml @@ -0,0 +1,4 @@ + + + + diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/clean.sql b/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/clean.sql new file mode 100644 index 000000000..e9616cd0d --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/clean.sql @@ -0,0 +1,7 @@ +DELETE FROM "product_sku"; +DELETE FROM "product_spu"; +DELETE FROM "product_category"; +DELETE FROM "product_brand"; +DELETE FROM "product_property"; +DELETE FROM "product_property_value"; +DELETE FROM "product_comment"; diff --git a/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql new file mode 100644 index 000000000..f0f0c70ee --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql @@ -0,0 +1,157 @@ +CREATE TABLE IF NOT EXISTS `product_sku` ( + `id` bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + `spu_id` bigint NOT NULL COMMENT 'spu编号', + `properties` varchar(512) DEFAULT NULL COMMENT '属性数组,JSON 格式', + `price` int NOT NULL DEFAULT '-1' COMMENT '商品价格,单位:分', + `market_price` int DEFAULT NULL COMMENT '市场价,单位:分', + `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分', + `bar_code` varchar(64) DEFAULT NULL COMMENT 'SKU 的条形码', + `pic_url` varchar(256) NOT NULL COMMENT '图片地址', + `stock` int DEFAULT NULL COMMENT '库存', + `weight` double DEFAULT NULL COMMENT '商品重量,单位:kg 千克', + `volume` double DEFAULT NULL COMMENT '商品体积,单位:m^3 平米', + `sub_commission_first_price` int DEFAULT NULL COMMENT '一级分销的佣金,单位:分', + `sub_commission_second_price` int DEFAULT NULL COMMENT '二级分销的佣金,单位:分', + `sales_count` int DEFAULT NULL COMMENT '商品销量', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY("id") +) COMMENT '商品sku'; + +CREATE TABLE IF NOT EXISTS `product_spu` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品 SPU 编号,自增', + `name` varchar(128) NOT NULL COMMENT '商品名称', + `keyword` varchar(256) NOT NULL COMMENT '关键字', + `introduction` varchar(256) NOT NULL COMMENT '商品简介', + `description` text NOT NULL COMMENT '商品详情', + `bar_code` varchar(64) NOT NULL COMMENT '条形码', + `category_id` bigint NOT NULL COMMENT '商品分类编号', + `brand_id` int DEFAULT NULL COMMENT '商品品牌编号', + `pic_url` varchar(256) NOT NULL COMMENT '商品封面图', + `slider_pic_urls` varchar(2000) DEFAULT '' COMMENT '商品轮播图地址\n 数组,以逗号分隔\n 最多上传15张', + `video_url` varchar(256) DEFAULT NULL COMMENT '商品视频', + `unit` tinyint NOT NULL COMMENT '单位', + `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段', + `status` tinyint NOT NULL COMMENT '商品状态: 0 上架(开启) 1 下架(禁用)-1 回收', + `spec_type` bit(1) NOT NULL COMMENT '规格类型:0 单规格 1 多规格', + `price` int NOT NULL DEFAULT '-1' COMMENT '商品价格,单位使用:分', + `market_price` int NOT NULL COMMENT '市场价,单位使用:分', + `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分', + `stock` int NOT NULL DEFAULT '0' COMMENT '库存', + `delivery_template_id` bigint NOT NULL COMMENT '物流配置模板编号', + `recommend_hot` bit(1) NOT NULL COMMENT '是否热卖推荐: 0 默认 1 热卖', + `recommend_benefit` bit(1) NOT NULL COMMENT '是否优惠推荐: 0 默认 1 优选', + `recommend_best` bit(1) NOT NULL COMMENT '是否精品推荐: 0 默认 1 精品', + `recommend_new` bit(1) NOT NULL COMMENT '是否新品推荐: 0 默认 1 新品', + `recommend_good` bit(1) NOT NULL COMMENT '是否优品推荐', + `give_integral` int NOT NULL COMMENT '赠送积分', + `give_coupon_template_ids` varchar(512) DEFAULT '' COMMENT '赠送的优惠劵编号的数组', + `sub_commission_type` bit(1) NOT NULL COMMENT '分销类型', + `activity_orders` varchar(16) NOT NULL DEFAULT '' COMMENT '活动显示排序0=默认, 1=秒杀,2=砍价,3=拼团', + `sales_count` int DEFAULT '0' COMMENT '商品销量', + `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量', + `browse_count` int DEFAULT '0' COMMENT '商品点击量', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY("id") +) COMMENT '商品spu'; + +CREATE TABLE IF NOT EXISTS `product_category` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号', + `parent_id` bigint NOT NULL COMMENT '父分类编号', + `name` varchar(255) NOT NULL COMMENT '分类名称', + `pic_url` varchar(255) NOT NULL COMMENT '移动端分类图', + `big_pic_url` varchar(255) DEFAULT NULL COMMENT 'PC 端分类图', + `sort` int DEFAULT '0' COMMENT '分类排序', + `status` tinyint NOT NULL COMMENT '开启状态', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY("id") +) COMMENT '商品分类'; + +CREATE TABLE IF NOT EXISTS `product_brand` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '品牌编号', + `name` varchar(255) NOT NULL COMMENT '品牌名称', + `pic_url` varchar(255) NOT NULL COMMENT '品牌图片', + `sort` int DEFAULT '0' COMMENT '品牌排序', + `description` varchar(1024) DEFAULT NULL COMMENT '品牌描述', + `status` tinyint NOT NULL COMMENT '状态', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY("id") +) COMMENT '商品品牌'; + +CREATE TABLE IF NOT EXISTS `product_property` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(64) DEFAULT NULL COMMENT '规格名称', + `status` tinyint DEFAULT NULL COMMENT '状态: 0 开启 ,1 禁用', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY("id") +) COMMENT '规格名称'; + +CREATE TABLE IF NOT EXISTS `product_property_value` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `property_id` bigint DEFAULT NULL COMMENT '规格键id', + `name` varchar(128) DEFAULT NULL COMMENT '规格值名字', + `status` tinyint DEFAULT NULL COMMENT '状态: 1 开启 ,2 禁用', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY("id") +) COMMENT '规格值'; + +DROP TABLE IF EXISTS `product_comment` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '评论编号,主键自增', + `user_id` bigint DEFAULT NULL COMMENT '评价人的用户编号关联 MemberUserDO 的 id 编号', + `user_nickname` varchar(255) DEFAULT NULL COMMENT '评价人名称', + `user_avatar` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '评价人头像', + `anonymous` bit(1) DEFAULT NULL COMMENT '是否匿名', + `order_id` bigint DEFAULT NULL COMMENT '交易订单编号关联 TradeOrderDO 的 id 编号', + `order_item_id` bigint DEFAULT NULL COMMENT '交易订单项编号关联 TradeOrderItemDO 的 id 编号', + `spu_id` bigint DEFAULT NULL COMMENT '商品 SPU 编号关联 ProductSpuDO 的 id', + `spu_name` varchar(255) DEFAULT NULL COMMENT '商品 SPU 名称', + `sku_id` bigint DEFAULT NULL COMMENT '商品 SKU 编号关联 ProductSkuDO 的 id 编号', + `visible` bit(1) DEFAULT NULL COMMENT '是否可见true:显示false:隐藏', + `scores` tinyint DEFAULT NULL COMMENT '评分星级1-5分', + `description_scores` tinyint DEFAULT NULL COMMENT '描述星级1-5 星', + `benefit_scores` tinyint DEFAULT NULL COMMENT '服务星级1-5 星', + `content` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '评论内容', + `pic_urls` varchar(4096) DEFAULT NULL COMMENT '评论图片地址数组', + `reply_status` bit(1) DEFAULT NULL COMMENT '商家是否回复', + `reply_user_id` bigint DEFAULT NULL COMMENT '回复管理员编号关联 AdminUserDO 的 id 编号', + `reply_content` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '商家回复内容', + `reply_time` datetime DEFAULT NULL COMMENT '商家回复时间', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '商品评论'; \ No newline at end of file