Merge remote-tracking branch 'origin/master' into feature/springdoc

# Conflicts:
#	README.md
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/ProductPropertyViewRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageReqVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageRespVO.java
#	yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuRespVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderItemRespVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderRespVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayNotifyOrderReqVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayRefundOrderReqVO.java
#	yudao-server/pom.xml
#	yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/AppShopOrderController.java
This commit is contained in:
xingyu
2023-01-04 10:48:18 +08:00
471 changed files with 17761 additions and 4522 deletions

View File

@@ -15,18 +15,15 @@
<name>${project.artifactId}</name>
<description>
商城大模块,由 product 商品、promotion 营销、trade 交易 coupon等组成
商城大模块,由 product 商品、promotion 营销、trade 交易等组成
</description>
<modules>
<!-- <module>yudao-module-coupon-api</module>-->
<!-- <module>yudao-module-coupon-biz</module>-->
<module>yudao-module-promotion-api</module>
<module>yudao-module-promotion-biz</module>
<module>yudao-module-product-api</module>
<module>yudao-module-product-biz</module>
<module>yudao-module-trade-api</module>
<module>yudao-module-trade-biz</module>
</modules>
</project>

View File

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

View File

@@ -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;
}

View File

@@ -18,17 +18,17 @@ public class ProductSkuRespDTO {
* 商品 SKU 编号,自增
*/
private Long id;
/**
* 商品 SKU 名字
*/
private String name;
/**
* SPU 编号
*/
private Long spuId;
/**
* SPU 名字
*/
private String spuName;
/**
* 规格值数组JSON 格式
* 属性数组JSON 格式
*/
private List<Property> properties;
/**

View File

@@ -15,28 +15,30 @@ public interface ErrorCodeConstants {
ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, "父分类不能是二级分类");
ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, "存在子分类,无法删除");
ErrorCode CATEGORY_DISABLED = new ErrorCode(1008001004, "商品分类({})已禁用,无法使用");
ErrorCode CATEGORY_LEVEL_ERROR = new ErrorCode(1008001005, "商品分类不正确,原因:必须使用第三级的商品分类下");
// ========== 商品品牌相关编号 1008002000 ==========
ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1008002000, "品牌不存在");
ErrorCode BRAND_DISABLED = new ErrorCode(1008002001, "品牌不存在");
ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1008002002, "品牌名称已存在");
// ========== 商品规格名称 1008003000 ==========
ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1008003000, "规格名称不存在");
ErrorCode PROPERTY_EXISTS = new ErrorCode(1008003001, "规格名称已存在");
// ========== 商品属性项 1008003000 ==========
ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1008003000, "属性项不存在");
ErrorCode PROPERTY_EXISTS = new ErrorCode(1008003001, "属性项的名称已存在");
ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1008003002, "属性项下存在属性值,无法删除");
// ========== 规格值 1008004000 ==========
ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "规格值不存在");
ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1008004001, "规格值已存在");
// ========== 商品属性值 1008004000 ==========
ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "属性值不存在");
ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1008004001, "属性值的名称已存在");
// ========== 商品 SPU 1008005000 ==========
ErrorCode SPU_NOT_EXISTS = new ErrorCode(1008005000, "商品 SPU 不存在");
ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1008005001, "商品分类不正确,原因:必须使用第三级的商品分类下");
ErrorCode SPU_NOT_ENABLE = new ErrorCode(1008005002, "商品 SPU 不处于上架状态");
// ========== 商品 SKU 1008006000 ==========
ErrorCode SKU_NOT_EXISTS = new ErrorCode(1008006000, "商品 SKU 不存在");
ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复");
ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU规格数必须一致");
ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU属性项必须一致");
ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU必须不重复");
ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");

View File

@@ -21,11 +21,11 @@ public enum ProductSpuSpecTypeEnum implements IntArrayValuable {
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductSpuSpecTypeEnum::getType).toArray();
/**
* 规格
* 规格类型
*/
private final Integer type;
/**
* 规格名
* 规格名
*/
private final String name;

View File

@@ -35,4 +35,14 @@ public enum ProductSpuStatusEnum implements IntArrayValuable {
return ARRAYS;
}
/**
* 判断是否处于【上架】状态
*
* @param status 状态
* @return 是否处于【上架】状态
*/
public static boolean isEnable(Integer status) {
return ENABLE.getStatus().equals(status);
}
}

View File

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

View File

@@ -1,9 +1,14 @@
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.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
@@ -13,12 +18,13 @@ 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 = "管理后台 - 规格名称")
@Tag(name = "管理后台 - 商品属性项")
@RestController
@RequestMapping("/product/property")
@Validated
@@ -26,16 +32,18 @@ public class ProductPropertyController {
@Resource
private ProductPropertyService productPropertyService;
@Resource
private ProductPropertyValueService productPropertyValueService;
@PostMapping("/create")
@Operation(summary = "创建规格名称")
@Operation(summary = "创建属性项")
@PreAuthorize("@ss.hasPermission('product:property:create')")
public CommonResult<Long> createProperty(@Valid @RequestBody ProductPropertyCreateReqVO createReqVO) {
return success(productPropertyService.createProperty(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新规格名称")
@Operation(summary = "更新属性项")
@PreAuthorize("@ss.hasPermission('product:property:update')")
public CommonResult<Boolean> updateProperty(@Valid @RequestBody ProductPropertyUpdateReqVO updateReqVO) {
productPropertyService.updateProperty(updateReqVO);
@@ -43,7 +51,7 @@ public class ProductPropertyController {
}
@DeleteMapping("/delete")
@Operation(summary = "删除规格名称")
@Operation(summary = "删除属性项")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('product:property:delete')")
public CommonResult<Boolean> deleteProperty(@RequestParam("id") Long id) {
@@ -52,32 +60,40 @@ public class ProductPropertyController {
}
@GetMapping("/get")
@Operation(summary = "获得规格名称")
@Operation(summary = "获得属性项")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('product:property:query')")
public CommonResult<ProductPropertyRespVO> getProperty(@RequestParam("id") Long id) {
return success(productPropertyService.getProperty(id));
return success(ProductPropertyConvert.INSTANCE.convert(productPropertyService.getProperty(id)));
}
@GetMapping("/list")
@Operation(summary = "获得规格名称列表")
@Operation(summary = "获得属性项列表")
@PreAuthorize("@ss.hasPermission('product:property:query')")
public CommonResult<List<ProductPropertyRespVO>> getPropertyList(@Valid ProductPropertyListReqVO listReqVO) {
return success(productPropertyService.getPropertyList(listReqVO));
return success(ProductPropertyConvert.INSTANCE.convertList(productPropertyService.getPropertyList(listReqVO)));
}
@GetMapping("/page")
@Operation(summary = "获得规格名称分页")
@Operation(summary = "获得属性项分页")
@PreAuthorize("@ss.hasPermission('product:property:query')")
public CommonResult<PageResult<ProductPropertyRespVO>> getPropertyPage(@Valid ProductPropertyPageReqVO pageVO) {
return success(productPropertyService.getPropertyPage(pageVO));
return success(ProductPropertyConvert.INSTANCE.convertPage(productPropertyService.getPropertyPage(pageVO)));
}
@GetMapping("/listAndValue")
@Operation(summary = "获得规格名称列表")
@GetMapping("/get-value-list")
@Operation(summary = "获得属性项列表")
@PreAuthorize("@ss.hasPermission('product:property:query')")
public CommonResult<List<ProductPropertyAndValueRespVO>> getPropertyAndValueList(@Valid ProductPropertyListReqVO listReqVO) {
return success(productPropertyService.getPropertyAndValueList(listReqVO));
// 查询属性项
List<ProductPropertyDO> keys = productPropertyService.getPropertyList(listReqVO);
if (CollUtil.isEmpty(keys)) {
return success(Collections.emptyList());
}
// 查询属性值
List<ProductPropertyValueDO> values = productPropertyValueService.getPropertyValueListByPropertyId(
convertSet(keys, ProductPropertyDO::getId));
return success(ProductPropertyConvert.INSTANCE.convertList(keys, values));
}
}

View File

@@ -2,11 +2,11 @@ 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.property.ProductPropertyPageReqVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
import cn.iocoder.yudao.module.product.convert.propertyvalue.ProductPropertyValueConvert;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
@@ -20,7 +20,7 @@ import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 规格值名称")
@Tag(name = "管理后台 - 商品属性值")
@RestController
@RequestMapping("/product/property/value")
@Validated
@@ -30,14 +30,14 @@ public class ProductPropertyValueController {
private ProductPropertyValueService productPropertyValueService;
@PostMapping("/create")
@Operation(summary = "创建规格名称")
@Operation(summary = "创建属性值")
@PreAuthorize("@ss.hasPermission('product:property:create')")
public CommonResult<Long> createProperty(@Valid @RequestBody ProductPropertyValueCreateReqVO createReqVO) {
return success(productPropertyValueService.createPropertyValue(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新规格名称")
@Operation(summary = "更新属性值")
@PreAuthorize("@ss.hasPermission('product:property:update')")
public CommonResult<Boolean> updateProperty(@Valid @RequestBody ProductPropertyValueUpdateReqVO updateReqVO) {
productPropertyValueService.updatePropertyValue(updateReqVO);
@@ -45,7 +45,7 @@ public class ProductPropertyValueController {
}
@DeleteMapping("/delete")
@Operation(summary = "删除规格名称")
@Operation(summary = "删除属性值")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('product:property:delete')")
public CommonResult<Boolean> deleteProperty(@RequestParam("id") Long id) {
@@ -54,17 +54,17 @@ public class ProductPropertyValueController {
}
@GetMapping("/get")
@Operation(summary = "获得规格名称")
@Operation(summary = "获得属性值")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('product:property:query')")
public CommonResult<ProductPropertyValueRespVO> getProperty(@RequestParam("id") Long id) {
return success(productPropertyValueService.getPropertyValue(id));
return success(ProductPropertyValueConvert.INSTANCE.convert(productPropertyValueService.getPropertyValue(id)));
}
@GetMapping("/page")
@Operation(summary = "获得规格名称分页")
@Operation(summary = "获得属性值分页")
@PreAuthorize("@ss.hasPermission('product:property:query')")
public CommonResult<PageResult<ProductPropertyValueRespVO>> getPropertyValuePage(@Valid ProductPropertyValuePageReqVO pageVO) {
return success(productPropertyValueService.getPropertyValueListPage(pageVO));
return success(ProductPropertyValueConvert.INSTANCE.convertPage(productPropertyValueService.getPropertyValuePage(pageVO)));
}
}

View File

@@ -1,43 +0,0 @@
package cn.iocoder.yudao.module.product.controller.admin.property.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
import java.util.List;
/**
* @Description: ProductPropertyViewRespVO
* @Author: franky
* @CreateDate: 2022/7/5 21:29
* @Version: 1.0.0
*/
@Schema(description = "管理后台 - 规格名称详情展示 Request VO")
@Data
@ToString(callSuper = true)
public class ProductPropertyViewRespVO {
@Schema(description = "规格名称id", example = "1")
public Long propertyId;
@Schema(description = "规格名称", example = "内存")
public String name;
@Schema(description = "规格属性值集合", example = "[{\"v1\":11,\"v2\":\"64G\"},{\"v1\":10,\"v2\":\"32G\"}]")
public List<Tuple2> propertyValues;
@Data
@Schema(description = "规格属性值元组")
public static class Tuple2 {
private final long id;
private final String name;
public Tuple2(Long id, String name) {
this.id = id;
this.name = name;
}
}
}

View File

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

View File

@@ -1,26 +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;
import javax.validation.constraints.NotNull;
/**
* 规格名称 Base VO提供给添加、修改、详细的子 VO 使用
* 商品属性项 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class ProductPropertyBaseVO {
@Schema(description = "规格名称", required = true, example = "颜色")
@NotBlank(message = "规格名称不能为空")
@Schema(description = "名称", required = true, example = "颜色")
@NotBlank(message = "名称不能为空")
private String name;
@Schema(description = "备注", example = "颜色")
private String remark;
@Schema(description = "状态-参见 CommonStatusEnum 枚举", required = true, example = "1")
@NotNull(message = "状态不能为空")
private Integer status;
}

View File

@@ -1,10 +1,11 @@
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")
@Schema(description = "管理后台 - 属性项创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)

View File

@@ -4,15 +4,12 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
@Schema(description = "管理后台 - 规格名称 List Request VO")
@Schema(description = "管理后台 - 属性项 List Request VO")
@Data
@ToString(callSuper = true)
public class ProductPropertyListReqVO {
@Schema(description = "规格名称", example = "颜色")
@Schema(description = "名称", example = "颜色")
private String name;
@Schema(description = "状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
private Integer status;
}

View File

@@ -11,16 +11,16 @@ 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")
@Schema(description = "管理后台 - 属性项 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ProductPropertyPageReqVO extends PageParam {
@Schema(description = "规格名称", example = "颜色")
@Schema(description = "名称", example = "颜色")
private String name;
@Schema(description = "状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
@Schema(description = "状态-参见 CommonStatusEnum 枚举", required = true, example = "1")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)

View File

@@ -7,13 +7,13 @@ import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 规格 + 规格值 Response VO")
@Schema(description = "管理后台 - 属性项 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ProductPropertyRespVO extends ProductPropertyBaseVO {
@Schema(description = "规格的编号", required = true, example = "1024")
@Schema(description = "编号", required = true, example = "1024")
private Long id;
@Schema(description = "创建时间", required = true)

View File

@@ -1,9 +1,13 @@
package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 规格名称更新 Request 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)

View File

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

View File

@@ -1,8 +1,9 @@
package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
@Schema(description = "管理后台 - 规格值创建 Request VO")
@Schema(description = "管理后台 - 商品属性值创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)

View File

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

View File

@@ -5,26 +5,20 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotEmpty;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 规格名称值分页 Request VO")
@Schema(description = "管理后台 - 商品属性值分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ProductPropertyValuePageReqVO extends PageParam {
@Schema(description = "规格id", example = "1024")
@Schema(description = "属性项的编号", example = "1024")
private String propertyId;
@Schema(description = "规格值", example = "红色")
@Schema(description = "名称", example = "红色")
private String name;
@Schema(description = "状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
@Schema(description = "状态 - 参见 CommonStatusEnum 枚举", required = true, example = "1")
private Integer status;
}

View File

@@ -7,13 +7,13 @@ import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 规格值 Response VO")
@Schema(description = "管理后台 - 商品属性值 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ProductPropertyValueRespVO extends ProductPropertyValueBaseVO {
@Schema(description = "主键", required = true, example = "10")
@Schema(description = "编号", required = true, example = "10")
private Long id;
@Schema(description = "创建时间")

View File

@@ -1,9 +1,10 @@
package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 规格值更新 Request VO")
@Schema(description = "管理后台 - 商品属性值更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)

View File

@@ -3,7 +3,9 @@ package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@@ -15,54 +17,56 @@ import javax.validation.constraints.NotNull;
@Data
public class ProductSkuBaseVO {
@Schema(description = "商品 SKU 名字", required = true, example = "芋道")
@Schema(description = "商品 SKU 名字", required = true, example = "芋道")
@NotEmpty(message = "商品 SKU 名字不能为空")
private String name;
@Schema(description = "销售价格,单位:分", required = true, example = "1024")
@Schema(description = "销售价格,单位:分", required = true, example = "1024", notes = "单位:分")
@NotNull(message = "销售价格,单位:分不能为空")
private Integer price;
@Schema(description = "市场价,单位:分", example = "1024")
@Schema(description = "市场价", example = "1024", notes = "单位:分")
private Integer marketPrice;
@Schema(description = "成本价,单位:分", example = "1024")
@Schema(description = "成本价", example = "1024", notes = "单位:分")
private Integer costPrice;
@Schema(description = "条形码", example = "haha")
@Schema(description = "条形码", example = "haha")
private String barCode;
@Schema(description = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
@Schema(description = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
@NotNull(message = "图片地址不能为空")
private String picUrl;
@Schema(description = "SKU 状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
@Schema(description = "SKU 状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举")
@NotNull(message = "SKU 状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "库存", required = true, example = "1")
@Schema(description = "库存", required = true, example = "1")
@NotNull(message = "库存不能为空")
private Integer stock;
@Schema(description = "预警预存", example = "1")
@Schema(description = "预警预存", example = "1")
private Integer warnStock;
@Schema(description = "商品重量,单位kg 千克", example = "1")
@Schema(description = "商品重量 - 单位kg 千克", example = "1")
private Double weight;
@Schema(description = "商品体积,单位m^3 平米", example = "1024")
@Schema(description = "商品体积 - 单位m^3 平米", example = "1024")
private Double volume;
@Schema(description = "规格值")
@ApiModel("商品属性")
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Property {
@Schema(description = "属性编号", required = true, example = "1")
@Schema(description = "属性编号", required = true, example = "1")
@NotNull(message = "属性编号不能为空")
private Long propertyId;
@Schema(description = "属性值编号", required = true, example = "1024")
@Schema(description = "属性值编号", required = true, example = "1024")
@NotNull(message = "属性值编号不能为空")
private Long valueId;

View File

@@ -13,11 +13,8 @@ import java.util.List;
@ToString(callSuper = true)
public class ProductSkuCreateOrUpdateReqVO extends ProductSkuBaseVO {
@Schema(description = "商品 SKU 编号", example = "1")
private Long id;
/**
* 规格值数组
* 属性数组
*/
private List<Property> properties;

View File

@@ -21,7 +21,7 @@ public class ProductSkuRespVO extends ProductSkuBaseVO {
private LocalDateTime createTime;
/**
* 规格值数组
* 属性数组
*/
private List<Property> properties;

View File

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

View File

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

View File

@@ -11,81 +11,66 @@ import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 商品 SPU Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
* 商品 SPU Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class ProductSpuBaseVO {
@Schema(description = "商品名称", required = true, example = "芋道")
@Schema(description = = "商品名称", required = true, example = "芋道")
@NotEmpty(message = "商品名称不能为空")
private String name;
@Schema(description = "商品编码", example = "yudaoyuanma")
@Schema(description = = "商品编码", example = "yudaoyuanma")
private String code;
@Schema(description = "促销语", example = "好吃!")
@Schema(description = = "促销语", example = "好吃!")
private String sellPoint;
@Schema(description = "商品详情", required = true, example = "我是商品描述")
@Schema(description = = "商品详情", required = true, example = "我是商品描述")
@NotNull(message = "商品详情不能为空")
private String description;
@Schema(description = "商品分类编号", required = true, example = "1")
@Schema(description = = "商品分类编号", required = true, example = "1")
@NotNull(message = "商品分类编号不能为空")
private Long categoryId;
@Schema(description = "商品品牌编号", example = "1")
@Schema(description = = "商品品牌编号", example = "1")
private Long brandId;
@Schema(description = "商品图片的数组", required = true)
@Schema(description = = "商品图片的数组", required = true)
@NotNull(message = "商品图片的数组不能为空")
private List<String> picUrls;
@Schema(description = "商品视频", required = true)
@Schema(description = = "商品视频", required = true)
private String videoUrl;
@Schema(description = "排序字段", required = true, example = "1")
@Schema(description = = "排序字段", required = true, example = "1")
private Integer sort;
@Schema(description = "商品状态,参见 ProductSpuStatusEnum 枚举类", required = true, example = "1")
@Schema(description = = "商品状态 参见 ProductSpuStatusEnum 枚举类", required = true, example = "1")
@NotNull(message = "商品状态不能为空")
@InEnum(ProductSpuStatusEnum.class)
private Integer status;
// ========== SKU 相关字段 =========
@Schema(description = "规格类型,参见 ProductSpuSpecTypeEnum 枚举类", required = true, example = "1")
@Schema(description = = "规格类型 参见 ProductSpuSpecTypeEnum 枚举类", required = true, example = "1")
@NotNull(message = "规格类型不能为空")
@InEnum(ProductSpuSpecTypeEnum.class)
private Integer specType;
@Schema(description = "是否展示库存", required = true, example = "true")
@Schema(description = = "是否展示库存", required = true, example = "true")
@NotNull(message = "是否展示库存不能为空")
private Boolean showStock;
@Schema(description = "库存", required = true, example = "true")
private Integer totalStock;
@Schema(description = "市场价", example = "1024")
@Schema(description = = "市场价", example = "1024")
private Integer marketPrice;
@Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
private Integer minPrice;
@Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
private Integer maxPrice;
// ========== 统计相关字段 =========
@Schema(description = "商品销量", example = "1024")
private Integer salesCount;
@Schema(description = "虚拟销量", required = true, example = "1024")
@Schema(description = = "虚拟销量", required = true, example = "1024")
@NotNull(message = "虚拟销量不能为空")
private Integer virtualSalesCount;
@Schema(description = "点击量", example = "1024")
private Integer clickCount;
}

View File

@@ -1,26 +1,21 @@
package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.ProductPropertyViewRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 商品 SPU 详细 Response VO,包括关联的 SKU 等信息")
@Schema(description = "管理后台 - 商品 SPU 详细 Response VO 包括关联的 SKU 等信息")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ProductSpuDetailRespVO extends ProductSpuBaseVO {
public class ProductSpuDetailRespVO extends ProductSpuRespVO {
@Schema(description = "主键", required = true, example = "1")
private Long id;
@Schema(description = "创建时间")
private LocalDateTime createTime;
// ========== SKU 相关字段 =========
/**
* SKU 数组
@@ -34,31 +29,10 @@ public class ProductSpuDetailRespVO extends ProductSpuBaseVO {
public static class Sku extends ProductSkuBaseVO {
/**
* 规格的数组
* 属性数组
*/
private List<ProductSpuDetailRespVO.Property> properties;
private List<ProductPropertyValueDetailRespVO> properties;
}
@Schema(description = "管理后台 - 商品规格的详细 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public static class Property extends ProductSkuBaseVO.Property {
@Schema(description = "规格的名字", required = true, example = "颜色")
private String propertyName;
@Schema(description = "规格值的名字", required = true, example = "蓝色")
private String valueName;
}
@Schema(description = "分类 id 数组,一直递归到一级父节点", example = "4")
private Long categoryId;
// TODO @芋艿:在瞅瞅~
@Schema(description = "规格属性修改和详情展示组合", example = "[{\"propertyId\":2,\"name\":\"内存\",\"propertyValues\":[{\"v1\":11,\"v2\":\"64G\"},{\"v1\":10,\"v2\":\"32G\"}]},{\"propertyId\":3,\"name\":\"尺寸\",\"propertyValues\":[{\"v1\":16,\"v2\":\"6.1\"},{\"v1\":15,\"v2\":\"5.7\"}]}]")
private List<ProductPropertyViewRespVO> productPropertyViews;
}

View File

@@ -18,13 +18,13 @@ public class ProductSpuPageReqVO extends PageParam {
@Schema(description = "商品编码", example = "yudaoyuanma")
private String code;
@Schema(description = "分类id", example = "1")
@Schema(description = "分类编号", example = "1")
private Long categoryId;
@Schema(description = "商品品牌编号", example = "1")
private Long brandId;
@Schema(description = "上下架状态,参见 ProductSpuStatusEnum 枚举值", example = "1")
@Schema(description = "上下架状态 参见 ProductSpuStatusEnum 枚举值", example = "1")
private Integer status;
@Schema(description = "销量最小值", example = "1")

View File

@@ -6,7 +6,6 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 商品 SPU Response VO")
@Data
@@ -20,4 +19,22 @@ public class ProductSpuRespVO extends ProductSpuBaseVO {
@Schema(description = "创建时间")
private LocalDateTime createTime;
// ========== SKU 相关字段 =========
@ApiModelProperty(value = "库存", required = true, example = "true")
private Integer totalStock;
@ApiModelProperty(value = " 最小价格,单位使用:分", required = true, example = "1024")
private Integer minPrice;
@ApiModelProperty(value = "最大价格,单位使用:分", required = true, example = "1024")
private Integer maxPrice;
@ApiModelProperty(value = "商品销量", example = "1024")
private Integer salesCount;
// ========== 统计相关字段 =========
@ApiModelProperty(value = "点击量", example = "1024")
private Integer clickCount;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
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.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.AssertTrue;
@ApiModel("用户 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";
@ApiModelProperty(value = "分类编号", example = "1")
private Long categoryId;
@ApiModelProperty(value = "关键字", example = "好看")
private String keyword;
@ApiModelProperty(value = "排序字段", example = "price", notes = "参见 AppSpuPageReqVO.SORT_FIELD_XXX 常量")
private String sortField;
@ApiModelProperty(value = "排序方式", example = "true", notes = "true - 升序false - 降序")
private Boolean sortAsc;
@AssertTrue(message = "排序字段不合法")
@JsonIgnore
public boolean isSortFieldValid() {
if (StrUtil.isEmpty(sortField)) {
return true;
}
return StrUtil.equalsAny(sortField, SORT_FIELD_PRICE, SORT_FIELD_SALES_COUNT);
}
}

View File

@@ -1,17 +0,0 @@
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "App - 商品spu分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AppSpuPageReqVO extends PageParam {
@Schema(description = "分类id")
private Long categoryId;
}

View File

@@ -1,51 +0,0 @@
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "App - 商品spu分页 Request VO")
@Data
public class AppSpuPageRespVO {
@Schema(description = "主键", required = true, example = "1")
private Long id;
@Schema(description = "商品名称")
private String name;
@Schema(description = "卖点", required = true)
@NotNull(message = "卖点不能为空")
private String sellPoint;
@Schema(description = "描述", required = true)
@NotNull(message = "描述不能为空")
private String description;
@Schema(description = "分类id", required = true)
@NotNull(message = "分类id不能为空")
private Long categoryId;
@Schema(description = "商品主图地址,* 数组,以逗号分隔,最多上传15张", required = true)
@NotNull(message = "商品主图地址,* 数组,以逗号分隔,最多上传15张不能为空")
private List<String> picUrls;
@Schema(description = "排序字段", required = true)
@NotNull(message = "排序字段不能为空")
private Integer sort;
@Schema(description = "点赞初始人数")
private Integer likeCount;
@Schema(description = "价格 单位使用:分")
private Integer price;
@Schema(description = "库存数量")
private Integer quantity;
@Schema(description = "上下架状态: 0 上架(开启) 1 下架(禁用)")
private Integer status;
}

View File

@@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
*
* </p>
*
* @author LuoWenFeng
*/
@Schema(description = "App - 商品spu Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class AppSpuRespVO extends ProductSpuRespVO {
}

View File

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

View File

@@ -1,18 +1,25 @@
package cn.iocoder.yudao.module.product.convert.propertyvalue;
import java.util.*;
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
* 属性值 Convert
*
* @author 芋道源码
*/
@@ -31,6 +38,18 @@ public interface ProductPropertyValueConvert {
PageResult<ProductPropertyValueRespVO> convertPage(PageResult<ProductPropertyValueDO> page);
List<ProductPropertyValueDO> convertList03(List<ProductPropertyValueCreateReqVO> list);
default List<ProductPropertyValueDetailRespBO> convertList(List<ProductPropertyValueDO> values, List<ProductPropertyDO> keys) {
Map<Long, ProductPropertyDO> keyMap = convertMap(keys, ProductPropertyDO::getId);
return CollectionUtils.convertList(values, value -> {
ProductPropertyValueDetailRespBO valueDetail = new ProductPropertyValueDetailRespBO()
.setValueId(value.getId()).setValueName(value.getName());
// 设置属性项
MapUtils.findAndThen(keyMap, value.getPropertyId(),
key -> valueDetail.setPropertyId(key.getId()).setPropertyName(key.getName()));
return valueDetail;
});
}
List<ProductPropertyValueDetailRespDTO> convertList02(List<ProductPropertyValueDetailRespBO> list);
}

View File

@@ -1,5 +1,7 @@
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;
@@ -10,9 +12,8 @@ import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -32,12 +33,16 @@ public interface ProductSkuConvert {
List<ProductSkuRespVO> convertList(List<ProductSkuDO> list);
List<ProductSkuDO> convertSkuDOList(List<ProductSkuCreateOrUpdateReqVO> list);
List<ProductSkuDO> convertList06(List<ProductSkuCreateOrUpdateReqVO> list);
default List<ProductSkuDO> convertList06(List<ProductSkuCreateOrUpdateReqVO> list, Long spuId, String spuName) {
List<ProductSkuDO> result = convertList06(list);
result.forEach(item -> item.setSpuId(spuId).setSpuName(spuName));
return result;
}
ProductSkuRespDTO convert02(ProductSkuDO bean);
List<ProductSkuRespDTO> convertList02(List<ProductSkuDO> list);
List<ProductSpuDetailRespVO.Sku> convertList03(List<ProductSkuDO> list);
List<ProductSkuRespDTO> convertList04(List<ProductSkuDO> list);
@@ -66,4 +71,23 @@ public interface ProductSkuConvert {
return spuIdAndStockMap;
}
default Collection<Long> convertPropertyValueIds(List<ProductSkuDO> list) {
if (CollUtil.isEmpty(list)) {
return new HashSet<>();
}
return list.stream().filter(item -> item.getProperties() != null)
.flatMap(p -> p.getProperties().stream()) // 遍历多个 Property 属性
.map(ProductSkuDO.Property::getValueId) // 将每个 Property 转换成对应的 propertyId最后形成集合
.collect(Collectors.toSet());
}
default String buildPropertyKey(ProductSkuDO bean) {
if (CollUtil.isEmpty(bean.getProperties())) {
return StrUtil.EMPTY;
}
List<ProductSkuDO.Property> properties = new ArrayList<>(bean.getProperties());
properties.sort(Comparator.comparing(ProductSkuDO.Property::getValueId));
return properties.stream().map(m -> String.valueOf(m.getValueId())).collect(Collectors.joining());
}
}

View File

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

View File

@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.product.dal.dataobject.property;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -8,7 +7,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 规格名称 DO
* 商品属性项 DO
*
* @author 芋道源码
*/
@@ -28,20 +27,12 @@ public class ProductPropertyDO extends BaseDO {
@TableId
private Long id;
/**
* 规格名称
* 名称
*/
private String name;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String remark;
// TODO 芋艿rule规格属性 (发布商品时,和 SKU 关联);规格参数(搜索商品时,与 Category 关联搜索)
}

View File

@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.product.dal.dataobject.property;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -9,7 +8,7 @@ import lombok.*;
/**
* 规格值 DO
* 商品属性值 DO
*
* @author 芋道源码
*/
@@ -29,21 +28,15 @@ public class ProductPropertyValueDO extends BaseDO {
@TableId
private Long id;
/**
* 规格键编号
* 属性项的编号
*
* 关联 {@link ProductPropertyDO#getId()}
*/
private Long propertyId;
/**
* 规格值名字
* 名称
*/
private String name;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*

View File

@@ -35,10 +35,6 @@ public class ProductSkuDO extends BaseDO {
*/
@TableId
private Long id;
/**
* 商品 SKU 名字
*/
private String name;
/**
* SPU 编号
* <p>
@@ -46,7 +42,13 @@ public class ProductSkuDO extends BaseDO {
*/
private Long spuId;
/**
* 规格值数组JSON 格式
* SPU 名字
*
* 冗余 {@link ProductSkuDO#getSpuName()}
*/
private String spuName;
/**
* 属性数组JSON 格式
*/
@TableField(typeHandler = PropertyTypeHandler.class)
private List<Property> properties;

View File

@@ -3,31 +3,30 @@ 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
*
* @author 芋道源码
*/
@Mapper
public interface ProductPropertyMapper extends BaseMapperX<ProductPropertyDO> {
default PageResult<ProductPropertyDO> selectPage(ProductPropertyPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<ProductPropertyDO>()
.likeIfPresent(ProductPropertyDO::getName, reqVO.getName())
.eqIfPresent(ProductPropertyDO::getStatus, reqVO.getStatus())
.betweenIfPresent(ProductPropertyDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(ProductPropertyDO::getId));
}
default ProductPropertyDO selectByName(String name) {
return selectOne(new LambdaQueryWrapperX<ProductPropertyDO>()
.eqIfPresent(ProductPropertyDO::getName, name));
return selectOne(ProductPropertyDO::getName, name);
}
default List<ProductPropertyDO> selectList(ProductPropertyListReqVO listReqVO) {
return selectList(new LambdaQueryWrapperX<ProductPropertyDO>()
.eqIfPresent(ProductPropertyDO::getName, listReqVO.getName()));
}
}

View File

@@ -7,17 +7,13 @@ import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.Produc
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
*
* @author 芋道源码
*/
@Mapper
public interface ProductPropertyValueMapper extends BaseMapperX<ProductPropertyValueDO> {
default List<ProductPropertyValueDO> selectListByPropertyId(List<Long> propertyIds) {
default List<ProductPropertyValueDO> selectListByPropertyId(Collection<Long> propertyIds) {
return selectList(new LambdaQueryWrapperX<ProductPropertyValueDO>()
.inIfPresent(ProductPropertyValueDO::getPropertyId, propertyIds));
}
@@ -28,17 +24,20 @@ public interface ProductPropertyValueMapper extends BaseMapperX<ProductPropertyV
.eq(ProductPropertyValueDO::getName, name));
}
default void deletePropertyValueByPropertyId(Long propertyId) {
delete(new LambdaQueryWrapperX<ProductPropertyValueDO>().eq(ProductPropertyValueDO::getPropertyId, propertyId)
.eq(ProductPropertyValueDO::getDeleted, false));
default void deleteByPropertyId(Long propertyId) {
delete(new LambdaQueryWrapperX<ProductPropertyValueDO>()
.eq(ProductPropertyValueDO::getPropertyId, propertyId));
}
default PageResult<ProductPropertyValueDO> selectPage(ProductPropertyValuePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<ProductPropertyValueDO>()
.eqIfPresent(ProductPropertyValueDO::getPropertyId, reqVO.getPropertyId())
.likeIfPresent(ProductPropertyValueDO::getName, reqVO.getName())
.eqIfPresent(ProductPropertyValueDO::getStatus, reqVO.getStatus())
.orderByDesc(ProductPropertyValueDO::getId));
}
default Integer selectCountByPropertyId(Long propertyId) {
return selectCount(ProductPropertyValueDO::getPropertyId, propertyId).intValue();
}
}

View File

@@ -8,6 +8,7 @@ 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;
/**
@@ -22,6 +23,17 @@ public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> {
return selectList(ProductSkuDO::getSpuId, spuId);
}
default List<ProductSkuDO> selectListBySpuIdAndStatus(Long spuId,
Integer status) {
return selectList(new LambdaQueryWrapperX<ProductSkuDO>()
.eq(ProductSkuDO::getSpuId, spuId)
.eqIfPresent(ProductSkuDO::getStatus, status));
}
default List<ProductSkuDO> selectListBySpuId(Collection<Long> spuIds) {
return selectList(ProductSkuDO::getSpuId, spuIds);
}
default void deleteBySpuId(Long spuId) {
delete(new LambdaQueryWrapperX<ProductSkuDO>().eq(ProductSkuDO::getSpuId, spuId));
}

View File

@@ -4,10 +4,12 @@ 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.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 com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Objects;
import java.util.Set;
/**
@@ -44,6 +46,19 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
.orderByDesc(ProductSpuDO::getSort));
}
default PageResult<ProductSpuDO> selectPage(AppProductSpuPageReqVO pageReqVO, Integer status) {
LambdaQueryWrapperX<ProductSpuDO> query = new LambdaQueryWrapperX<ProductSpuDO>()
.eqIfPresent(ProductSpuDO::getCategoryId, pageReqVO.getCategoryId())
.eqIfPresent(ProductSpuDO::getStatus, status);
// 排序逻辑
if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_PRICE)) {
query.orderBy(true, pageReqVO.getSortAsc(), ProductSpuDO::getMaxPrice);
} else if (Objects.equals(pageReqVO.getSortField(), AppProductSpuPageReqVO.SORT_FIELD_SALES_COUNT)) {
query.orderBy(true, pageReqVO.getSortAsc(), ProductSpuDO::getSalesCount);
}
return selectPage(pageReqVO, query);
}
/**
* 更新商品 SPU 库存
*

View File

@@ -2,7 +2,7 @@
* trade 模块,主要实现交易相关功能
* 例如:订单、退款、购物车等功能。
*
* 1. Controller URL以 /trade/ 开头,避免和其它 Module 冲突
* 2. DataObject 表名:以 trade_ 开头,方便在数据库中区分
* 1. Controller URL以 /product/ 开头,避免和其它 Module 冲突
* 2. DataObject 表名:以 product_ 开头,方便在数据库中区分
*/
package cn.iocoder.yudao.module.product;

View File

@@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCateg
import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
@@ -47,12 +46,19 @@ public interface ProductCategoryService {
ProductCategoryDO getCategory(Long id);
/**
* 获得商品分类列表
* 校验商品分类
*
* @param ids 编号
* @return 商品分类列表
* @param id 分类编号
*/
List<ProductCategoryDO> getEnableCategoryList(Collection<Long> ids);
void validateCategory(Long id);
/**
* 获得商品分类的层级
*
* @param id 编号
* @return 商品分类的层级
*/
Integer getCategoryLevel(Long id);
/**
* 获得商品分类列表
@@ -62,14 +68,6 @@ public interface ProductCategoryService {
*/
List<ProductCategoryDO> getEnableCategoryList(ProductCategoryListReqVO listReqVO);
/**
* 验证选择的商品分类的级别是否合法
* 例如说,商品发布的时候,必须在第 3 级别
*
* @param id 分类编号
*/
void validateCategoryLevel(Long id);
/**
* 获得开启状态的商品分类列表
*

View File

@@ -11,7 +11,6 @@ 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.Objects;
@@ -90,31 +89,40 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
}
}
@Override
public void validateCategoryLevel(Long id) {
// TODO @芋艿:在看看,杂能优化下
Long parentId = id;
int i = 2;
for (; i >= 0; --i) {
ProductCategoryDO category = productCategoryMapper.selectById(parentId);
parentId = category.getParentId();
if(Objects.equals(parentId, ProductCategoryDO.PARENT_ID_NULL)){
break;
}
}
if (!Objects.equals(parentId, ProductCategoryDO.PARENT_ID_NULL) || i != 0) {
throw exception(CATEGORY_LEVEL_ERROR);
}
}
@Override
public ProductCategoryDO getCategory(Long id) {
return productCategoryMapper.selectById(id);
}
@Override
public List<ProductCategoryDO> getEnableCategoryList(Collection<Long> ids) {
return productCategoryMapper.selectBatchIds(ids);
public void validateCategory(Long id) {
ProductCategoryDO category = productCategoryMapper.selectById(id);
if (category == null) {
throw exception(CATEGORY_NOT_EXISTS);
}
if (Objects.equals(category.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
throw exception(CATEGORY_DISABLED, category.getName());
}
}
@Override
public Integer getCategoryLevel(Long id) {
if (Objects.equals(id, ProductCategoryDO.PARENT_ID_NULL)) {
return 0;
}
int level = 1;
for (int i = 0; i < 100; i++) {
ProductCategoryDO category = productCategoryMapper.selectById(id);
// 如果没有父节点break 结束
if (category == null
|| Objects.equals(category.getParentId(), ProductCategoryDO.PARENT_ID_NULL)) {
break;
}
// 继续递归父节点
level++;
id = category.getParentId();
}
return level;
}
@Override

View File

@@ -1,7 +1,6 @@
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.ProductPropertyRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.*;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
@@ -10,14 +9,15 @@ import java.util.Collection;
import java.util.List;
/**
* 规格名称 Service 接口
* 商品属性项 Service 接口
*
* @author 芋道源码
*/
public interface ProductPropertyService {
/**
* 创建规格名称
* 创建属性项
* 注意,如果已经存在该属性项,直接返回它的编号即可
*
* @param createReqVO 创建信息
* @return 编号
@@ -25,56 +25,48 @@ public interface ProductPropertyService {
Long createProperty(@Valid ProductPropertyCreateReqVO createReqVO);
/**
* 更新规格名称
* 更新属性项
*
* @param updateReqVO 更新信息
*/
void updateProperty(@Valid ProductPropertyUpdateReqVO updateReqVO);
/**
* 删除规格名称
* 删除属性项
*
* @param id 编号
*/
void deleteProperty(Long id);
/**
* 获得规格名称列表
* 获得属性项列表
* @param listReqVO 集合查询
* @return 规格名称集合
* @return 属性项集合
*/
List<ProductPropertyRespVO> getPropertyList(ProductPropertyListReqVO listReqVO);
List<ProductPropertyDO> getPropertyList(ProductPropertyListReqVO listReqVO);
/**
* 获取属性名称分页
*
* @param pageReqVO 分页条件
* @return 规格名称分页
* @return 属性项分页
*/
PageResult<ProductPropertyRespVO> getPropertyPage(ProductPropertyPageReqVO pageReqVO);
PageResult<ProductPropertyDO> getPropertyPage(ProductPropertyPageReqVO pageReqVO);
/**
* 获得指定编号的规格名称
* 获得指定编号的属性项
*
* @param id 编号
* @return 规格名称
* @return 属性项
*/
ProductPropertyRespVO getProperty(Long id);
ProductPropertyDO getProperty(Long id);
/**
* 根据规格属性编号的集合,获得对应的规格 + 规格值的集合
* 根据属性项的编号的集合,获得对应的属性项数组
*
* @param ids 规格编号的集合
* @return 对应的规格
* @param ids 属性项的编号的集合
* @return 属性项数组
*/
List<ProductPropertyRespVO> getPropertyList(Collection<Long> ids);
/**
* 获得规格名称 + 值的列表
*
* @param listReqVO 列表查询
* @return 规格名称 + 值的列表
*/
List<ProductPropertyAndValueRespVO> getPropertyAndValueList(ProductPropertyListReqVO listReqVO);
List<ProductPropertyDO> getPropertyList(Collection<Long> ids);
}

View File

@@ -1,16 +1,15 @@
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.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.*;
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.convert.propertyvalue.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.ProductPropertyMapper;
import cn.iocoder.yudao.module.product.dal.mysql.property.ProductPropertyValueMapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -18,15 +17,12 @@ 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.enums.ErrorCodeConstants.PROPERTY_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PROPERTY_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
/**
* 规格名称 Service 实现类
* 商品属性项 Service 实现类
*
* @author 芋道源码
*/
@@ -38,15 +34,18 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
private ProductPropertyMapper productPropertyMapper;
@Resource
private ProductPropertyValueMapper productPropertyValueMapper;
@Lazy // 延迟加载,解决循环依赖问题
private ProductPropertyValueService productPropertyValueService;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createProperty(ProductPropertyCreateReqVO createReqVO) {
// 校验存在
if (productPropertyMapper.selectByName(createReqVO.getName()) != null) {
throw exception(PROPERTY_EXISTS);
// 如果已经添加过该属性项,直接返回
ProductPropertyDO dbProperty = productPropertyMapper.selectByName(createReqVO.getName());
if (dbProperty != null) {
return dbProperty.getId();
}
// 插入
ProductPropertyDO property = ProductPropertyConvert.INSTANCE.convert(createReqVO);
productPropertyMapper.insert(property);
@@ -57,12 +56,14 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updateProperty(ProductPropertyUpdateReqVO updateReqVO) {
// 校验存在
this.validatePropertyExists(updateReqVO.getId());
validatePropertyExists(updateReqVO.getId());
// 校验名字重复
ProductPropertyDO productPropertyDO = productPropertyMapper.selectByName(updateReqVO.getName());
if (productPropertyDO != null && !productPropertyDO.getId().equals(updateReqVO.getId())) {
if (productPropertyDO != null &&
ObjUtil.notEqual(productPropertyDO.getId(), updateReqVO.getId())) {
throw exception(PROPERTY_EXISTS);
}
// 更新
ProductPropertyDO updateObj = ProductPropertyConvert.INSTANCE.convert(updateReqVO);
productPropertyMapper.updateById(updateObj);
@@ -71,11 +72,16 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
@Override
public void deleteProperty(Long id) {
// 校验存在
this.validatePropertyExists(id);
validatePropertyExists(id);
// 校验其下是否有规格值
if (productPropertyValueService.getPropertyValueCountByPropertyId(id) > 0) {
throw exception(PROPERTY_DELETE_FAIL_VALUE_EXISTS);
}
// 删除
productPropertyMapper.deleteById(id);
//同步删除属性值
productPropertyValueMapper.deletePropertyValueByPropertyId(id);
// 同步删除属性值
productPropertyValueService.deletePropertyValueByPropertyId(id);
}
private void validatePropertyExists(Long id) {
@@ -85,41 +91,23 @@ public class ProductPropertyServiceImpl implements ProductPropertyService {
}
@Override
public List<ProductPropertyRespVO> getPropertyList(ProductPropertyListReqVO listReqVO) {
return ProductPropertyConvert.INSTANCE.convertList(productPropertyMapper.selectList(new LambdaQueryWrapperX<ProductPropertyDO>()
.likeIfPresent(ProductPropertyDO::getName, listReqVO.getName())
.eqIfPresent(ProductPropertyDO::getStatus, listReqVO.getStatus())));
public List<ProductPropertyDO> getPropertyList(ProductPropertyListReqVO listReqVO) {
return productPropertyMapper.selectList(listReqVO);
}
@Override
public PageResult<ProductPropertyRespVO> getPropertyPage(ProductPropertyPageReqVO pageReqVO) {
//获取属性列表
PageResult<ProductPropertyDO> pageResult = productPropertyMapper.selectPage(pageReqVO);
return ProductPropertyConvert.INSTANCE.convertPage(pageResult);
public PageResult<ProductPropertyDO> getPropertyPage(ProductPropertyPageReqVO pageReqVO) {
return productPropertyMapper.selectPage(pageReqVO);
}
@Override
public ProductPropertyRespVO getProperty(Long id) {
ProductPropertyDO property = productPropertyMapper.selectById(id);
return ProductPropertyConvert.INSTANCE.convert(property);
public ProductPropertyDO getProperty(Long id) {
return productPropertyMapper.selectById(id);
}
@Override
public List<ProductPropertyRespVO> getPropertyList(Collection<Long> ids) {
return ProductPropertyConvert.INSTANCE.convertList(productPropertyMapper.selectBatchIds(ids));
public List<ProductPropertyDO> getPropertyList(Collection<Long> ids) {
return productPropertyMapper.selectBatchIds(ids);
}
@Override
public List<ProductPropertyAndValueRespVO> getPropertyAndValueList(ProductPropertyListReqVO listReqVO) {
List<ProductPropertyRespVO> propertyList = getPropertyList(listReqVO);
// 查询属性值
List<ProductPropertyValueDO> valueDOList = productPropertyValueMapper.selectListByPropertyId(CollectionUtils.convertList(propertyList, ProductPropertyRespVO::getId));
Map<Long, List<ProductPropertyValueDO>> valueDOMap = CollectionUtils.convertMultiMap(valueDOList, ProductPropertyValueDO::getPropertyId);
return CollectionUtils.convertList(propertyList, m -> {
ProductPropertyAndValueRespVO productPropertyAndValueRespVO = ProductPropertyConvert.INSTANCE.convert(m);
productPropertyAndValueRespVO.setValues(ProductPropertyValueConvert.INSTANCE.convertList(valueDOMap.get(m.getId())));
return productPropertyAndValueRespVO;
});
}
}

View File

@@ -3,22 +3,23 @@ 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.ProductPropertyValueRespVO;
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;
/**
* <p>
* 规格值 Service 接口
* </p>
* 商品属性值 Service 接口
*
* @author LuoWenFeng
*/
public interface ProductPropertyValueService {
/**
* 创建规格
* 创建属性
* 注意,如果已经存在该属性值,直接返回它的编号即可
*
* @param createReqVO 创建信息
* @return 编号
@@ -26,40 +27,64 @@ public interface ProductPropertyValueService {
Long createPropertyValue(ProductPropertyValueCreateReqVO createReqVO);
/**
* 更新规格
* 更新属性
*
* @param updateReqVO 更新信息
*/
void updatePropertyValue(ProductPropertyValueUpdateReqVO updateReqVO);
/**
* 删除规格
* 删除属性
*
* @param id 编号
*/
void deletePropertyValue(Long id);
/**
* 获得规格
* 获得属性
*
* @param id 编号
* @return 规格名称
* @return 属性值
*/
ProductPropertyValueRespVO getPropertyValue(Long id);
ProductPropertyValueDO getPropertyValue(Long id);
/**
* 获得规格值
* 根据属性项编号数组,获得属性值列表
*
* @param id 编号
* @return 规格名称
* @param propertyIds 属性项目编号数组
* @return 属性值列表
*/
List<ProductPropertyValueRespVO> getPropertyValueListByPropertyId(List<Long> id);
List<ProductPropertyValueDO> getPropertyValueListByPropertyId(Collection<Long> propertyIds);
/**
* 获取规格值 分页
* 根据编号数组,获得属性值列表
*
* @param ids 编号数组
* @return 属性值明细列表
*/
List<ProductPropertyValueDetailRespBO> getPropertyValueDetailList(Collection<Long> ids);
/**
* 根据属性项编号,活的属性值数量
*
* @param propertyId 属性项编号数
* @return 属性值数量
*/
Integer getPropertyValueCountByPropertyId(Long propertyId);
/**
* 获取属性值的分页
*
* @param pageReqVO 查询条件
* @return
* @return 属性值的分页
*/
PageResult<ProductPropertyValueRespVO> getPropertyValueListPage(ProductPropertyValuePageReqVO pageReqVO);
PageResult<ProductPropertyValueDO> getPropertyValuePage(ProductPropertyValuePageReqVO pageReqVO);
/**
* 删除指定属性项编号下的属性值们
*
* @param propertyId 属性项的编号
*/
void deletePropertyValueByPropertyId(Long propertyId);
}

View File

@@ -1,26 +1,31 @@
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.ProductPropertyValueRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
import cn.iocoder.yudao.module.product.convert.propertyvalue.ProductPropertyValueConvert;
import cn.iocoder.yudao.module.product.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 com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
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 java.util.Objects;
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 实现类
* 商品属性值 Service 实现类
*
* @author LuoWenFeng
*/
@@ -31,45 +36,92 @@ public class ProductPropertyValueServiceImpl implements ProductPropertyValueServ
@Resource
private ProductPropertyValueMapper productPropertyValueMapper;
@Resource
@Lazy // 延迟加载,避免循环依赖
private ProductPropertyService productPropertyService;
@Override
public Long createPropertyValue(ProductPropertyValueCreateReqVO createReqVO) {
if (productPropertyValueMapper.selectByName(createReqVO.getPropertyId(), createReqVO.getName()) != null) {
throw exception(PROPERTY_VALUE_EXISTS);
// 如果已经添加过该属性值,直接返回
ProductPropertyValueDO dbValue = productPropertyValueMapper.selectByName(
createReqVO.getPropertyId(), createReqVO.getName());
if (dbValue != null) {
return dbValue.getId();
}
ProductPropertyValueDO convert = ProductPropertyValueConvert.INSTANCE.convert(createReqVO);
productPropertyValueMapper.insert(convert);
return convert.getId();
// 新增
ProductPropertyValueDO value = ProductPropertyValueConvert.INSTANCE.convert(createReqVO);
productPropertyValueMapper.insert(value);
return value.getId();
}
@Override
public void updatePropertyValue(ProductPropertyValueUpdateReqVO updateReqVO) {
ProductPropertyValueDO productPropertyValueDO = productPropertyValueMapper.selectByName(updateReqVO.getPropertyId(), updateReqVO.getName());
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 convert = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO);
productPropertyValueMapper.updateById(convert);
// 更新
ProductPropertyValueDO updateObj = ProductPropertyValueConvert.INSTANCE.convert(updateReqVO);
productPropertyValueMapper.updateById(updateObj);
}
@Override
public void deletePropertyValue(Long id) {
validatePropertyValueExists(id);
productPropertyValueMapper.deleteById(id);
}
@Override
public ProductPropertyValueRespVO getPropertyValue(Long id) {
ProductPropertyValueDO productPropertyValueDO = productPropertyValueMapper.selectOne(new LambdaQueryWrapper<ProductPropertyValueDO>()
.eq(ProductPropertyValueDO::getId, id));
return ProductPropertyValueConvert.INSTANCE.convert(productPropertyValueDO);
private void validatePropertyValueExists(Long id) {
if (productPropertyValueMapper.selectById(id) == null) {
throw exception(PROPERTY_VALUE_NOT_EXISTS);
}
}
@Override
public List<ProductPropertyValueRespVO> getPropertyValueListByPropertyId(List<Long> id) {
return ProductPropertyValueConvert.INSTANCE.convertList(productPropertyValueMapper.selectList("property_id", id));
public ProductPropertyValueDO getPropertyValue(Long id) {
return productPropertyValueMapper.selectById(id);
}
@Override
public PageResult<ProductPropertyValueRespVO> getPropertyValueListPage(ProductPropertyValuePageReqVO pageReqVO) {
return ProductPropertyValueConvert.INSTANCE.convertPage(productPropertyValueMapper.selectPage(pageReqVO));
public List<ProductPropertyValueDO> getPropertyValueListByPropertyId(Collection<Long> propertyIds) {
return productPropertyValueMapper.selectListByPropertyId(propertyIds);
}
@Override
public List<ProductPropertyValueDetailRespBO> getPropertyValueDetailList(Collection<Long> ids) {
// 获得属性值列表
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
List<ProductPropertyValueDO> values = productPropertyValueMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(values)) {
return Collections.emptyList();
}
// 获得属性项列表
List<ProductPropertyDO> 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<ProductPropertyValueDO> getPropertyValuePage(ProductPropertyValuePageReqVO pageReqVO) {
return productPropertyValueMapper.selectPage(pageReqVO);
}
@Override
public void deletePropertyValueByPropertyId(Long propertyId) {
productPropertyValueMapper.deleteByPropertyId(propertyId);
}
}

View File

@@ -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;
}

View File

@@ -3,6 +3,7 @@ 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 org.springframework.lang.Nullable;
import java.util.Collection;
import java.util.List;
@@ -49,23 +50,25 @@ public interface ProductSkuService {
*
* @param list sku组合的集合
*/
void validateSkus(List<ProductSkuCreateOrUpdateReqVO> list, Integer specType);
void validateSkuList(List<ProductSkuCreateOrUpdateReqVO> list, Integer specType);
/**
* 批量创建 SKU
*
* @param spuId 商品 SPU 编号
* @param spuName 商品 SPU 名称
* @param list SKU 对象集合
*/
void createSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> list);
void createSkuList(Long spuId, String spuName, List<ProductSkuCreateOrUpdateReqVO> list);
/**
* 根据 SPU 编号,批量更新它的 SKU 信息
*
* @param spuId SPU 编码
* @param spuName 商品 SPU 名称
* @param skus SKU 的集合
*/
void updateSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus);
void updateSkuList(Long spuId, String spuName, List<ProductSkuCreateOrUpdateReqVO> skus);
/**
* 更新 SKU 库存(增量)
@@ -77,20 +80,30 @@ public interface ProductSkuService {
void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
/**
* 获得商品 sku 集合
* 获得商品 SKU 集合
*
* @param spuId spu 编号
* @return 商品sku 集合
*/
List<ProductSkuDO> getSkusBySpuId(Long spuId);
List<ProductSkuDO> getSkuListBySpuId(Long spuId);
/**
* 获得 spu 对应的 sku 集合
* 基于 SPU 编号和状态,获得商品 SKU 集合
*
* @param spuId SPU 编号
* @param status 状态
* @return 商品 SKU 集合
*/
List<ProductSkuDO> getSkuListBySpuIdAndStatus(Long spuId,
@Nullable Integer status);
/**
* 获得 spu 对应的 SKU 集合
*
* @param spuIds spu 编码集合
* @return 商品 sku 集合
*/
List<ProductSkuDO> getSkusBySpuIds(List<Long> spuIds);
List<ProductSkuDO> getSkuListBySpuId(List<Long> spuIds);
/**
* 通过 spuId 删除 sku 信息
@@ -104,7 +117,6 @@ public interface ProductSkuService {
*
* @return SKU 数组
*/
List<ProductSkuDO> getSkusByAlarmStock();
List<ProductSkuDO> getSkuListByAlarmStock();
}

View File

@@ -1,13 +1,13 @@
package cn.iocoder.yudao.module.product.service.sku;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
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.enums.ErrorCodeConstants;
@@ -25,6 +25,7 @@ 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.*;
@@ -78,22 +79,24 @@ public class ProductSkuServiceImpl implements ProductSkuService {
}
@Override
public void validateSkus(List<ProductSkuCreateOrUpdateReqVO> skus, Integer specType) {
public void validateSkuList(List<ProductSkuCreateOrUpdateReqVO> skus, Integer specType) {
// 非多规格,不需要校验
if (ObjectUtil.notEqual(specType, ProductSpuSpecTypeEnum.DISABLE.getType())) {
return;
}
// 1、校验规格属性存在
Set<Long> propertyIds = skus.stream().filter(p -> p.getProperties() != null).flatMap(p -> p.getProperties().stream()) // 遍历多个 Property 属性
.map(ProductSkuBaseVO.Property::getPropertyId).collect(Collectors.toSet()); // 将每个 Property 转换成对应的 propertyId最后形成集合
List<ProductPropertyRespVO> propertyList = productPropertyService.getPropertyList(propertyIds);
// 1、校验属性存在
Set<Long> propertyIds = skus.stream().filter(p -> p.getProperties() != null)
.flatMap(p -> p.getProperties().stream()) // 遍历多个 Property 属性
.map(ProductSkuBaseVO.Property::getPropertyId) // 将每个 Property 转换成对应的 propertyId,最后形成集合
.collect(Collectors.toSet());
List<ProductPropertyDO> propertyList = productPropertyService.getPropertyList(propertyIds);
if (propertyList.size() != propertyIds.size()) {
throw exception(PROPERTY_NOT_EXISTS);
}
// 2. 校验,一个 SKU 下,没有重复的规格。校验方式是,遍历每个 SKU ,看看是否有重复的规格 propertyId
Map<Long, ProductPropertyValueRespVO> propertyValueMap = CollectionUtils.convertMap(productPropertyValueService.getPropertyValueListByPropertyId(new ArrayList<>(propertyIds)), ProductPropertyValueRespVO::getId);
// 2. 校验,一个 SKU 下,没有重复的属性。校验方式是,遍历每个 SKU ,看看是否有重复的属性 propertyId
Map<Long, ProductPropertyValueDO> propertyValueMap = convertMap(productPropertyValueService.getPropertyValueListByPropertyId(propertyIds), ProductPropertyValueDO::getId);
skus.forEach(sku -> {
Set<Long> skuPropertyIds = convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId());
if (skuPropertyIds.size() != sku.getProperties().size()) {
@@ -101,7 +104,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
}
});
// 3. 再校验,每个 Sku 的规格值的数量,是一致的。
// 3. 再校验,每个 Sku 的属性值的数量,是一致的。
int attrValueIdsSize = skus.get(0).getProperties().size();
for (int i = 1; i < skus.size(); i++) {
if (attrValueIdsSize != skus.get(i).getProperties().size()) {
@@ -119,21 +122,23 @@ public class ProductSkuServiceImpl implements ProductSkuService {
}
@Override
public void createSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skuCreateReqList) {
// 批量插入 SKU
List<ProductSkuDO> skuDOList = ProductSkuConvert.INSTANCE.convertSkuDOList(skuCreateReqList);
skuDOList.forEach(v -> v.setSpuId(spuId));
productSkuMapper.insertBatch(skuDOList);
public void createSkuList(Long spuId, String spuName, List<ProductSkuCreateOrUpdateReqVO> skuCreateReqList) {
productSkuMapper.insertBatch(ProductSkuConvert.INSTANCE.convertList06(skuCreateReqList, spuId, spuName));
}
@Override
public List<ProductSkuDO> getSkusBySpuId(Long spuId) {
return productSkuMapper.selectList(ProductSkuDO::getSpuId, spuId);
public List<ProductSkuDO> getSkuListBySpuId(Long spuId) {
return productSkuMapper.selectListBySpuId(spuId);
}
@Override
public List<ProductSkuDO> getSkusBySpuIds(List<Long> spuIds) {
return productSkuMapper.selectList(ProductSkuDO::getSpuId, spuIds);
public List<ProductSkuDO> getSkuListBySpuIdAndStatus(Long spuId, Integer status) {
return productSkuMapper.selectListBySpuIdAndStatus(spuId, status);
}
@Override
public List<ProductSkuDO> getSkuListBySpuId(List<Long> spuIds) {
return productSkuMapper.selectListBySpuId(spuIds);
}
@Override
@@ -142,59 +147,44 @@ public class ProductSkuServiceImpl implements ProductSkuService {
}
@Override
public List<ProductSkuDO> getSkusByAlarmStock() {
public List<ProductSkuDO> getSkuListByAlarmStock() {
return productSkuMapper.selectListByAlarmStock();
}
@Override
@Transactional
public void updateSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus) {
// 查询 SPU 下已经存在的 SKU 的集合
List<ProductSkuDO> existsSkus = productSkuMapper.selectListBySpuId(spuId);
// 构建规格与 SKU 的映射关系;
// TODO @luowenfeng: 可以下 existsSkuMap2; 会简洁一点; 另外, 可以考虑抽一个小方法, 用于 Properties 生成一个串; 这样 177 也可以复用了
Map<String, Long> existsSkuMap = existsSkus.stream()
.map(v -> {
String collect = v.getProperties() == null? "null": v.getProperties()
.stream()
.map(m -> String.valueOf(m.getValueId()))
.collect(Collectors.joining());
return String.join("-", collect, String.valueOf(v.getId()));
})
.collect(Collectors.toMap(v -> v.split("-")[0], v -> Long.valueOf(v.split("-")[1])));
@Transactional(rollbackFor = Exception.class)
public void updateSkuList(Long spuId, String spuName, List<ProductSkuCreateOrUpdateReqVO> skus) {
// 构建属性与 SKU 的映射关系;
Map<String, Long> existsSkuMap = convertMap(productSkuMapper.selectListBySpuId(spuId),
ProductSkuConvert.INSTANCE::buildPropertyKey, ProductSkuDO::getId);
// 拆分三个集合,新插入的、需要更新的、需要删除的
List<ProductSkuDO> insertSkus = new ArrayList<>();
List<ProductSkuDO> updateSkus = new ArrayList<>();
List<Long> deleteSkus = new ArrayList<>();
List<ProductSkuDO> allUpdateSkus = ProductSkuConvert.INSTANCE.convertSkuDOList(skus);
allUpdateSkus.forEach(p -> {
String propertiesKey = p.getProperties() == null? "null": p.getProperties().stream().map(m -> String.valueOf(m.getValueId())).collect(Collectors.joining());
List<ProductSkuDO> allUpdateSkus = ProductSkuConvert.INSTANCE.convertList06(skus, null, spuName);
allUpdateSkus.forEach(sku -> {
String propertiesKey = ProductSkuConvert.INSTANCE.buildPropertyKey(sku);
// 1、找得到的进行更新
if (existsSkuMap.containsKey(propertiesKey)) {
updateSkus.add(p);
existsSkuMap.remove(propertiesKey);
Long existsSkuId = existsSkuMap.remove(propertiesKey);
if (existsSkuId != null) {
sku.setId(existsSkuId);
updateSkus.add(sku);
return;
}
// 2、找不到进行插入
p.setSpuId(spuId);
insertSkus.add(p);
sku.setSpuId(spuId);
insertSkus.add(sku);
});
// 3、多余的删除
if(!existsSkuMap.isEmpty()){
deleteSkus = new ArrayList<>(existsSkuMap.values());
}
// 4、执行修改 Sku
if (!insertSkus.isEmpty()) {
// 执行最终的批量操作
if (CollUtil.isNotEmpty(insertSkus)) {
productSkuMapper.insertBatch(insertSkus);
}
if (!updateSkus.isEmpty()) {
updateSkus.forEach(p -> productSkuMapper.updateById(p));
if (CollUtil.isNotEmpty(updateSkus)) {
updateSkus.forEach(sku -> productSkuMapper.updateById(sku));
}
if (!deleteSkus.isEmpty()) {
productSkuMapper.deleteBatchIds(deleteSkus);
if (CollUtil.isNotEmpty(existsSkuMap)) {
productSkuMapper.deleteBatchIds(existsSkuMap.values());
}
}

View File

@@ -2,8 +2,7 @@ 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.AppSpuPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
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;
@@ -42,21 +41,13 @@ public interface ProductSpuService {
*/
void deleteSpu(Long id);
/**
* 获得商品 SPU 详情
*
* @param id 编号
* @return 商品 SPU
*/
ProductSpuDetailRespVO getSpuDetail(Long id);
/**
* 获得商品 SPU
*
* @param id 编号
* @return 商品 SPU
*/
ProductSpuRespVO getSpu(Long id);
ProductSpuDO getSpu(Long id);
/**
* 获得商品 SPU 列表
@@ -89,15 +80,16 @@ public interface ProductSpuService {
* @param pageReqVO 分页查询
* @return 商品spu分页
*/
PageResult<ProductSpuRespVO> getSpuPage(ProductSpuPageReqVO pageReqVO);
PageResult<ProductSpuDO> getSpuPage(ProductSpuPageReqVO pageReqVO);
/**
* 获得商品 SPU 分页
*
* @param pageReqVO 分页查询
* @return 商品spu分页
* @param status 状态
* @return 商品 SPU 分页
*/
PageResult<AppSpuPageRespVO> getSpuPage(AppSpuPageReqVO pageReqVO);
PageResult<ProductSpuDO> getSpuPage(AppProductSpuPageReqVO pageReqVO, Integer status);
/**
* 更新商品 SPU 库存(增量)

View File

@@ -1,28 +1,19 @@
package cn.iocoder.yudao.module.product.service.spu;
import cn.hutool.core.bean.BeanUtil;
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.ProductPropertyViewRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
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.controller.admin.sku.vo.ProductSkuRespVO;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
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.ProductSpuUpdateReqVO;
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.sku.ProductSkuDO;
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.ProductSpuSpecTypeEnum;
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.property.ProductPropertyService;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -30,11 +21,15 @@ 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 java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR;
/**
* 商品 SPU Service 实现类
@@ -48,68 +43,85 @@ public class ProductSpuServiceImpl implements ProductSpuService {
@Resource
private ProductSpuMapper productSpuMapper;
@Resource
private ProductCategoryService categoryService;
@Resource
@Lazy // 循环依赖,避免报错
private ProductSkuService productSkuService;
@Resource
private ProductPropertyService productPropertyService;
@Resource
private ProductPropertyValueService productPropertyValueService;
@Resource
private ProductBrandService brandService;
@Resource
private ProductCategoryService categoryService;
@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public Long createSpu(ProductSpuCreateReqVO createReqVO) {
// 校验分类
categoryService.validateCategoryLevel(createReqVO.getCategoryId());
validateCategory(createReqVO.getCategoryId());
// 校验品牌
brandService.validateProductBrand(createReqVO.getBrandId());
// 校验SKU
List<ProductSkuCreateOrUpdateReqVO> skuCreateReqList = createReqVO.getSkus();
productSkuService.validateSkus(skuCreateReqList, createReqVO.getSpecType());
List<ProductSkuCreateOrUpdateReqVO> skuSaveReqList = createReqVO.getSkus();
productSkuService.validateSkuList(skuSaveReqList, createReqVO.getSpecType());
// 插入 SPU
ProductSpuDO spu = ProductSpuConvert.INSTANCE.convert(createReqVO);
spu.setMarketPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getMarketPrice));
spu.setMaxPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
spu.setMinPrice(CollectionUtils.getMinValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
spu.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
initSpuFromSkus(spu, skuSaveReqList);
productSpuMapper.insert(spu);
// 插入 SKU
productSkuService.createSkus(spu.getId(), skuCreateReqList);
productSkuService.createSkuList(spu.getId(), spu.getName(), skuSaveReqList);
// 返回
return spu.getId();
}
@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public void updateSpu(ProductSpuUpdateReqVO updateReqVO) {
// 校验 SPU 是否存在
validateSpuExists(updateReqVO.getId());
// 校验分类
categoryService.validateCategoryLevel(updateReqVO.getCategoryId());
validateCategory(updateReqVO.getCategoryId());
// 校验品牌
brandService.validateProductBrand(updateReqVO.getBrandId());
// 校验SKU
List<ProductSkuCreateOrUpdateReqVO> skuCreateReqList = updateReqVO.getSkus();
productSkuService.validateSkus(skuCreateReqList, updateReqVO.getSpecType());
List<ProductSkuCreateOrUpdateReqVO> skuSaveReqList = updateReqVO.getSkus();
productSkuService.validateSkuList(skuSaveReqList, updateReqVO.getSpecType());
// 更新 SPU
ProductSpuDO updateObj = ProductSpuConvert.INSTANCE.convert(updateReqVO);
updateObj.setMarketPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getMarketPrice));
updateObj.setMaxPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
updateObj.setMinPrice(CollectionUtils.getMinValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
updateObj.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
initSpuFromSkus(updateObj, skuSaveReqList);
productSpuMapper.updateById(updateObj);
// 批量更新 SKU
productSkuService.updateSkus(updateObj.getId(), updateReqVO.getSkus());
productSkuService.updateSkuList(updateObj.getId(), updateObj.getName(), updateReqVO.getSkus());
}
/**
* 基于 SKU 的信息,初始化 SPU 的信息
* 主要是计数相关的字段,例如说市场价、最大最小价、库存等等
*
* @param spu 商品 SPU
* @param skus 商品 SKU 数组
*/
private void initSpuFromSkus(ProductSpuDO spu, List<ProductSkuCreateOrUpdateReqVO> skus) {
spu.setMarketPrice(getMaxValue(skus, ProductSkuCreateOrUpdateReqVO::getMarketPrice));
spu.setMaxPrice(getMaxValue(skus, ProductSkuCreateOrUpdateReqVO::getPrice));
spu.setMinPrice(getMinValue(skus, ProductSkuCreateOrUpdateReqVO::getPrice));
spu.setTotalStock(getSumValue(skus, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
}
/**
* 校验商品分类是否合法
*
* @param id 商品分类编号
*/
private void validateCategory(Long id) {
categoryService.validateCategory(id);
// 校验层级
if (categoryService.getCategoryLevel(id) != 3) {
throw exception(SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR);
}
}
@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public void deleteSpu(Long id) {
// 校验存在
validateSpuExists(id);
@@ -126,48 +138,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
}
@Override
// TODO @芋艿:需要再 review 下
public ProductSpuDetailRespVO getSpuDetail(Long id) {
ProductSpuDO spu = productSpuMapper.selectById(id);
ProductSpuDetailRespVO respVO = BeanUtil.copyProperties(spu, ProductSpuDetailRespVO.class);
if (null != spu) {
List<ProductSpuDetailRespVO.Sku> skuReqs = ProductSkuConvert.INSTANCE.convertList03(productSkuService.getSkusBySpuId(id));
respVO.setSkus(skuReqs);
// 组合 sku 规格属性
if (spu.getSpecType().equals(ProductSpuSpecTypeEnum.DISABLE.getType())) {
List<ProductSkuRespVO.Property> properties = new ArrayList<>();
for (ProductSpuDetailRespVO.Sku productSkuRespVO : skuReqs) {
properties.addAll(productSkuRespVO.getProperties());
}
Map<Long, List<ProductSkuBaseVO.Property>> propertyMaps = properties.stream().collect(Collectors.groupingBy(ProductSkuBaseVO.Property::getPropertyId));
List<ProductPropertyValueRespVO> propertyValueList = productPropertyValueService.getPropertyValueListByPropertyId(new ArrayList<>(propertyMaps.keySet()));
List<ProductPropertyRespVO> propertyList = productPropertyService.getPropertyList(new ArrayList<>(propertyMaps.keySet()));
// 装载组装过后的属性
List<ProductPropertyViewRespVO> productPropertyViews = new ArrayList<>();
propertyList.forEach(p -> {
ProductPropertyViewRespVO productPropertyViewRespVO = new ProductPropertyViewRespVO();
productPropertyViewRespVO.setPropertyId(p.getId());
productPropertyViewRespVO.setName(p.getName());
List<ProductPropertyViewRespVO.Tuple2> propertyValues = new ArrayList<>();
// 转换成map是为了能快速获取
Map<Long, ProductPropertyValueRespVO> propertyValueMaps = CollectionUtils.convertMap(propertyValueList, ProductPropertyValueRespVO::getId);
propertyMaps.get(p.getId()).forEach(pv -> {
ProductPropertyViewRespVO.Tuple2 tuple2 = new ProductPropertyViewRespVO.Tuple2(pv.getValueId(), propertyValueMaps.get(pv.getValueId()).getName());
propertyValues.add(tuple2);
});
productPropertyViewRespVO.setPropertyValues(propertyValues.stream().distinct().collect(Collectors.toList()));
productPropertyViews.add(productPropertyViewRespVO);
});
respVO.setProductPropertyViews(productPropertyViews);
}
}
return respVO;
}
@Override
public ProductSpuRespVO getSpu(Long id) {
return ProductSpuConvert.INSTANCE.convert(productSpuMapper.selectById(id));
public ProductSpuDO getSpu(Long id) {
return productSpuMapper.selectById(id);
}
@Override
@@ -181,32 +153,22 @@ public class ProductSpuServiceImpl implements ProductSpuService {
}
@Override
public PageResult<ProductSpuRespVO> getSpuPage(ProductSpuPageReqVO pageReqVO) {
public PageResult<ProductSpuDO> getSpuPage(ProductSpuPageReqVO pageReqVO) {
// 库存告警的 SPU 编号的集合
Set<Long> alarmStockSpuIds = null;
if (Boolean.TRUE.equals(pageReqVO.getAlarmStock())) {
alarmStockSpuIds = CollectionUtils.convertSet(productSkuService.getSkusByAlarmStock(), ProductSkuDO::getSpuId);
alarmStockSpuIds = CollectionUtils.convertSet(productSkuService.getSkuListByAlarmStock(), ProductSkuDO::getSpuId);
if (CollUtil.isEmpty(alarmStockSpuIds)) {
return PageResult.empty();
}
}
// 分页查询
return ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(pageReqVO, alarmStockSpuIds));
return productSpuMapper.selectPage(pageReqVO, alarmStockSpuIds);
}
@Override
public PageResult<AppSpuPageRespVO> getSpuPage(AppSpuPageReqVO pageReqVO) {
// TODO 芋艿:貌似实现不太合理
PageResult<ProductSpuDO> productSpuDOPageResult = productSpuMapper.selectPage(ProductSpuConvert.INSTANCE.convert(pageReqVO));
PageResult<AppSpuPageRespVO> pageResult = new PageResult<>();
// TODO @芋艿 这里用convert如何解决
List<AppSpuPageRespVO> collect = productSpuDOPageResult.getList()
.stream()
.map(ProductSpuConvert.INSTANCE::convertAppResp)
.collect(Collectors.toList());
pageResult.setList(collect);
pageResult.setTotal(productSpuDOPageResult.getTotal());
return pageResult;
public PageResult<ProductSpuDO> getSpuPage(AppProductSpuPageReqVO pageReqVO, Integer status) {
return productSpuMapper.selectPage(pageReqVO, status);
}
@Override

View File

@@ -103,6 +103,25 @@ public class ProductCategoryServiceImplTest extends BaseDbUnitTest {
assertServiceException(() -> productCategoryService.deleteCategory(id), CATEGORY_NOT_EXISTS);
}
@Test
public void testGetCategoryLevel() {
// mock 数据
ProductCategoryDO category1 = randomPojo(ProductCategoryDO.class,
o -> o.setParentId(ProductCategoryDO.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 数据

View File

@@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.product.service.sku;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
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;
@@ -13,11 +15,16 @@ 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.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
@@ -42,6 +49,50 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
@MockBean
private ProductPropertyValueService productPropertyValueService;
@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<ProductSkuCreateOrUpdateReqVO> skus = Arrays.asList(
randomPojo(ProductSkuCreateOrUpdateReqVO.class, o -> { // 测试更新
o.setProperties(singletonList(new ProductSkuCreateOrUpdateReqVO.Property(10L, 20L)));
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
}),
randomPojo(ProductSkuCreateOrUpdateReqVO.class, o -> { // 测试新增
o.setProperties(singletonList(new ProductSkuCreateOrUpdateReqVO.Property(10L, 40L)));
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
})
);
// 调用
productSkuService.updateSkuList(spuId, spuName, skus);
// 断言
List<ProductSkuDO> 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() {
// 准备参数
@@ -95,4 +146,26 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
SKU_STOCK_NOT_ENOUGH);
}
@Test
public void testDeleteSku_success() {
// mock 数据
ProductSkuDO dbSku = randomPojo(ProductSkuDO.class);
productSkuMapper.insert(dbSku);// @Sql: 先插入出一条存在的数据
// 准备参数
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);
}
}

View File

@@ -1,56 +0,0 @@
package cn.iocoder.yudao.module.product.service.sku;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
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 org.junit.jupiter.api.Assertions.assertNull;
// TODO 芋艿:整合到 {@link ProductSkuServiceTest} 中
/**
* {@link ProductSkuServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(ProductSkuServiceImpl.class)
@Disabled // TODO 芋艿:临时去掉
public class SkuServiceImplTest extends BaseDbUnitTest {
@Resource
private ProductSkuServiceImpl ProductSkuService;
@Resource
private ProductSkuMapper ProductSkuMapper;
@Test
public void testDeleteSku_success() {
// mock 数据
ProductSkuDO dbSku = randomPojo(ProductSkuDO.class);
ProductSkuMapper.insert(dbSku);// @Sql: 先插入出一条存在的数据
// 准备参数
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);
}
}

View File

@@ -8,12 +8,12 @@ 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.SetUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
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.AppSpuPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
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.controller.app.spu.vo.AppProductSpuPageReqVO;
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;
@@ -94,9 +94,9 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
ProductSpuDO productSpuDO = productSpuMapper.selectById(spu);
createReqVO.setMarketPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getMarketPrice));
createReqVO.setMaxPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
createReqVO.setMinPrice(CollectionUtils.getMinValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
createReqVO.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
// createReqVO.setMaxPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
// createReqVO.setMinPrice(CollectionUtils.getMinValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
// createReqVO.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
assertPojoEquals(createReqVO, productSpuDO);
@@ -118,9 +118,9 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
List<ProductSkuCreateOrUpdateReqVO> skuCreateReqList = reqVO.getSkus();
reqVO.setMarketPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getMarketPrice));
reqVO.setMaxPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
reqVO.setMinPrice(CollectionUtils.getMinValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
reqVO.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
// reqVO.setMaxPrice(CollectionUtils.getMaxValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
// reqVO.setMinPrice(CollectionUtils.getMinValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getPrice));
// reqVO.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
// 校验是否更新正确
ProductSpuDO spu = productSpuMapper.selectById(reqVO.getId()); // 获取最新的
@@ -149,60 +149,13 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
Assertions.assertNull(productSpuMapper.selectById(createReqVO.getId()));
}
@Test
void getSpuDetail() {
// 准备spu参数
ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class, o -> {
o.setSpecType(ProductSpuSpecTypeEnum.DISABLE.getType());
});
productSpuMapper.insert(createReqVO);
// 创建两个属性
ArrayList<ProductPropertyRespVO> productPropertyRespVOS = Lists.newArrayList(
randomPojo(ProductPropertyRespVO.class),
randomPojo(ProductPropertyRespVO.class));
// 所有属性值
ArrayList<ProductPropertyValueRespVO> productPropertyValueRespVO = new ArrayList<>();
// 每个属性创建属性值
productPropertyRespVOS.forEach(v -> {
ProductPropertyValueRespVO productPropertyValueRespVO1 = randomPojo(ProductPropertyValueRespVO.class, o -> o.setPropertyId(v.getId()));
productPropertyValueRespVO.add(productPropertyValueRespVO1);
});
// 属性值建立笛卡尔积
Map<Long, List<ProductPropertyValueRespVO>> collect = productPropertyValueRespVO.stream().collect(Collectors.groupingBy(ProductPropertyValueRespVO::getPropertyId));
List<List<ProductPropertyValueRespVO>> lists = cartesianProduct(Lists.newArrayList(collect.values()));
// 准备sku参数
ArrayList<ProductSkuDO> productSkuDOS = Lists.newArrayList();
lists.forEach(pp -> {
List<ProductSkuDO.Property> property = pp.stream().map(ppv -> new ProductSkuDO.Property(ppv.getPropertyId(), ppv.getId())).collect(Collectors.toList());
ProductSkuDO productSkuDO = randomPojo(ProductSkuDO.class, o -> {
o.setProperties(property);
});
productSkuDOS.add(productSkuDO);
});
Mockito.when(productSkuService.getSkusBySpuId(createReqVO.getId())).thenReturn(productSkuDOS);
Mockito.when(productPropertyValueService.getPropertyValueListByPropertyId(new ArrayList<>(collect.keySet()))).thenReturn(productPropertyValueRespVO);
Mockito.when(productPropertyService.getPropertyList(new ArrayList<>(collect.keySet()))).thenReturn(productPropertyRespVOS);
// 调用
ProductSpuDetailRespVO spuDetail = productSpuService.getSpuDetail(createReqVO.getId());
assertPojoEquals(createReqVO, spuDetail);
}
@Test
void getSpu() {
// 准备参数
ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class);
productSpuMapper.insert(createReqVO);
ProductSpuRespVO spu = productSpuService.getSpu(createReqVO.getId());
ProductSpuDO spu = productSpuService.getSpu(createReqVO.getId());
assertPojoEquals(createReqVO, spu);
}
@@ -223,7 +176,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO();
productSpuPageReqVO.setAlarmStock(true);
PageResult<ProductSpuRespVO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
PageResult<Object> result = PageResult.empty();
Assertions.assertIterableEquals(result.getList(), spuPage.getList());
@@ -267,12 +220,12 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
o.setSpuId(createReqVO.getId());
}));
Mockito.when(productSkuService.getSkusByAlarmStock()).thenReturn(productSpuDOS);
Mockito.when(productSkuService.getSkuListByAlarmStock()).thenReturn(productSpuDOS);
// 调用
ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO();
productSpuPageReqVO.setAlarmStock(true);
PageResult<ProductSpuRespVO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
PageResult<ProductSpuRespVO> result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO, alarmStockSpuIds));
Assertions.assertIterableEquals(result.getList(), spuPage.getList());
@@ -324,7 +277,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
productSpuPageReqVO.setStatus(ProductSpuStatusEnum.ENABLE.getStatus());
productSpuPageReqVO.setCategoryId(categoryId);
PageResult<ProductSpuRespVO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
PageResult<ProductSpuRespVO> result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO, (Set<Long>) null));
assertEquals(result, spuPage);
@@ -339,21 +292,21 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
productSpuMapper.insert(createReqVO);
// 调用
AppSpuPageReqVO appSpuPageReqVO = new AppSpuPageReqVO();
AppProductSpuPageReqVO appSpuPageReqVO = new AppProductSpuPageReqVO();
appSpuPageReqVO.setCategoryId(2L);
PageResult<AppSpuPageRespVO> spuPage = productSpuService.getSpuPage(appSpuPageReqVO);
PageResult<ProductSpuDO> result = productSpuMapper.selectPage(
ProductSpuConvert.INSTANCE.convert(appSpuPageReqVO));
List<AppSpuPageRespVO> collect = result.getList()
.stream()
.map(ProductSpuConvert.INSTANCE::convertAppResp)
.collect(Collectors.toList());
Assertions.assertIterableEquals(collect, spuPage.getList());
assertEquals(spuPage.getTotal(), result.getTotal());
// PageResult<AppSpuPageItemRespVO> spuPage = productSpuService.getSpuPage(appSpuPageReqVO);
//
// PageResult<ProductSpuDO> result = productSpuMapper.selectPage(
// ProductSpuConvert.INSTANCE.convert(appSpuPageReqVO));
//
// List<AppSpuPageItemRespVO> collect = result.getList()
// .stream()
// .map(ProductSpuConvert.INSTANCE::convertAppResp)
// .collect(Collectors.toList());
//
// Assertions.assertIterableEquals(collect, spuPage.getList());
// assertEquals(spuPage.getTotal(), result.getTotal());
}

View File

@@ -9,7 +9,7 @@ spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
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:

View File

@@ -1,8 +1,7 @@
CREATE TABLE IF NOT EXISTS `product_sku` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`spu_id` bigint NOT NULL COMMENT 'spu编号',
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
`name` varchar DEFAULT NULL COMMENT '商品 SKU 名字',
`spu_name` varchar DEFAULT NULL COMMENT '商品 SPU 名字',
`properties` varchar DEFAULT NULL COMMENT '规格值数组-json格式 [{propertId: , valueId: }, {propertId: , valueId: }]',
`price` int NOT NULL DEFAULT '-1' COMMENT '销售价格单位',
`market_price` int DEFAULT NULL COMMENT '市场价',
@@ -52,3 +51,19 @@ CREATE TABLE IF NOT EXISTS `product_spu` (
`deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`)
) COMMENT '商品spu';
CREATE TABLE IF NOT EXISTS `product_category` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号',
`parent_id` bigint DEFAULT NULL COMMENT '父分类编号',
`name` varchar(128) NOT NULL COMMENT '分类名称',
`description` varchar(128) NOT NULL COMMENT '分类描述',
`pic_url` varchar DEFAULT NULL COMMENT '分类图片',
`sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
`status` bit(1) DEFAULT NULL COMMENT '状态',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` varchar DEFAULT NULL COMMENT '创建人',
`updater` varchar DEFAULT NULL COMMENT '更新人',
`deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`)
) COMMENT '商品分类';

View File

@@ -45,4 +45,16 @@ public interface ErrorCodeConstants {
// ========== Price 相关 1003007000 ============
ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1003007000, "支付价格计算异常,原因:价格小于等于 0");
// ========== 秒杀活动 1003008000 ==========
ErrorCode SECKILL_ACTIVITY_NOT_EXISTS = new ErrorCode(1003008000, "秒杀活动不存在");
ErrorCode SECKILL_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1003008002, "存在商品参加了其它秒杀活动");
ErrorCode SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1003008003, "秒杀活动已关闭,不能修改");
ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1003008004, "秒杀活动未关闭或未结束,不能删除");
ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1003008005, "秒杀活动已关闭,不能重复关闭");
ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1003008006, "秒杀活动已结束,不能关闭");
// ========== 秒杀时段 1003009000 ==========
ErrorCode SECKILL_TIME_NOT_EXISTS = new ErrorCode(1003009000, "秒杀时段不存在");
ErrorCode SECKILL_TIME_CONFLICTS = new ErrorCode(1003009001, "秒杀时段冲突");
}

View File

@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
@@ -66,7 +66,7 @@ public class CouponController {
}
// 读取用户信息,进行拼接
Set<Long> userIds = convertSet(pageResult.getList(), CouponDO::getUserId);
Map<Long, UserRespDTO> userMap = memberUserApi.getUserMap(userIds);
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
pageResulVO.getList().forEach(itemRespVO -> MapUtils.findAndThen(userMap, itemRespVO.getUserId(),
userRespDTO -> itemRespVO.setNickname(userRespDTO.getNickname())));
return success(pageResulVO);

View File

@@ -0,0 +1,96 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.*;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity.SeckillActivityService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
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.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 秒杀活动")
@RestController
@RequestMapping("/promotion/seckill-activity")
@Validated
public class SeckillActivityController {
@Resource
private SeckillActivityService seckillActivityService;
@PostMapping("/create")
@ApiOperation("创建秒杀活动")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:create')")
public CommonResult<Long> createSeckillActivity(@Valid @RequestBody SeckillActivityCreateReqVO createReqVO) {
return success(seckillActivityService.createSeckillActivity(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新秒杀活动")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:update')")
public CommonResult<Boolean> updateSeckillActivity(@Valid @RequestBody SeckillActivityUpdateReqVO updateReqVO) {
seckillActivityService.updateSeckillActivity(updateReqVO);
return success(true);
}
@PutMapping("/close")
@ApiOperation("关闭秒杀活动")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:close')")
public CommonResult<Boolean> closeSeckillActivity(@RequestParam("id") Long id) {
seckillActivityService.closeSeckillActivity(id);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除秒杀活动")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:delete')")
public CommonResult<Boolean> deleteSeckillActivity(@RequestParam("id") Long id) {
seckillActivityService.deleteSeckillActivity(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得秒杀活动")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<SeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
SeckillActivityDO seckillActivity = seckillActivityService.getSeckillActivity(id);
if (seckillActivity == null) {
return success(null);
}
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(id);
return success(SeckillActivityConvert.INSTANCE.convert(seckillActivity,seckillProducts));
}
@GetMapping("/list")
@ApiOperation("获得秒杀活动列表")
@ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<List<SeckillActivityRespVO>> getSeckillActivityList(@RequestParam("ids") Collection<Long> ids) {
List<SeckillActivityDO> list = seckillActivityService.getSeckillActivityList(ids);
return success(SeckillActivityConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@ApiOperation("获得秒杀活动分页")
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<PageResult<SeckillActivityRespVO>> getSeckillActivityPage(@Valid SeckillActivityPageReqVO pageVO) {
PageResult<SeckillActivityDO> pageResult = seckillActivityService.getSeckillActivityPage(pageVO);
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckilltime.SeckillTimeConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime.SeckillTimeDO;
import cn.iocoder.yudao.module.promotion.service.seckill.seckilltime.SeckillTimeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 秒杀时段")
@RestController
@RequestMapping("/promotion/seckill-time")
@Validated
public class SeckillTimeController {
@Resource
private SeckillTimeService seckillTimeService;
@PostMapping("/create")
@ApiOperation("创建秒杀时段")
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:create')")
public CommonResult<Long> createSeckillTime(@Valid @RequestBody SeckillTimeCreateReqVO createReqVO) {
return success(seckillTimeService.createSeckillTime(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新秒杀时段")
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:update')")
public CommonResult<Boolean> updateSeckillTime(@Valid @RequestBody SeckillTimeUpdateReqVO updateReqVO) {
seckillTimeService.updateSeckillTime(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除秒杀时段")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:delete')")
public CommonResult<Boolean> deleteSeckillTime(@RequestParam("id") Long id) {
seckillTimeService.deleteSeckillTime(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得秒杀时段")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:query')")
public CommonResult<SeckillTimeRespVO> getSeckillTime(@RequestParam("id") Long id) {
SeckillTimeDO seckillTime = seckillTimeService.getSeckillTime(id);
return success(SeckillTimeConvert.INSTANCE.convert(seckillTime));
}
@GetMapping("/list")
@ApiOperation("获得所有秒杀时段列表")
@PreAuthorize("@ss.hasPermission('promotion:seckill-time:query')")
public CommonResult<List<SeckillTimeRespVO>> getSeckillTimeList() {
List<SeckillTimeDO> list = seckillTimeService.getSeckillTimeList();
return success(SeckillTimeConvert.INSTANCE.convertList(list));
}
}

View File

@@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
/**
* 秒杀活动 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class SeckillActivityBaseVO {
@ApiModelProperty(value = "秒杀活动名称", required = true, example = "晚九点限时秒杀")
@NotNull(message = "秒杀活动名称不能为空")
private String name;
@ApiModelProperty(value = "活动开始时间", required = true)
@NotNull(message = "活动开始时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime startTime;
@ApiModelProperty(value = "活动结束时间", required = true)
@NotNull(message = "活动结束时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime endTime;
@ApiModel("商品")
@Data
public static class Product {
@ApiModelProperty(value = "商品 SPU 编号", required = true, example = "1")
@NotNull(message = "商品 SPU 编号不能为空")
private Long spuId;
@ApiModelProperty(value = "商品 SKU 编号", required = true, example = "1")
@NotNull(message = "商品 SKU 编号不能为空")
private Long skuId;
@ApiModelProperty(value = "秒杀金额", required = true, example = "12.00")
@NotNull(message = "秒杀金额不能为空")
private Integer seckillPrice;
@ApiModelProperty(value = "秒杀库存", example = "80")
@Min(value = 0, message = "秒杀库存需要大于等于 0")
private Integer stock;
@ApiModelProperty(value = "每人限购", example = "10", notes = "如果为0则不限购")
@Min(value = 0, message = "每人限购需要大于等于 0")
private Integer limitBuyCount;
}
}

View File

@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@ApiModel("管理后台 - 秒杀活动创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityCreateReqVO extends SeckillActivityBaseVO {
@ApiModelProperty(value = "备注", example = "限时秒杀活动")
private String remark;
@ApiModelProperty(value = "排序", required = true, example = "1")
@NotNull(message = "排序不能为空")
private Integer sort;
@ApiModelProperty(value = "秒杀时段id", required = true, example = "1,3")
@NotEmpty(message = "参与场次不能为空")
private List<Long> timeIds;
/**
* 商品列表
*/
@NotEmpty(message = "商品列表不能为空")
@Valid
private List<Product> products;
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@ApiModel("管理后台 - 秒杀活动的详细 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityDetailRespVO extends SeckillActivityRespVO {
/**
* 商品列表
*/
private List<Product> products;
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
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;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
@ApiModel("管理后台 - 秒杀活动分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityPageReqVO extends PageParam {
@ApiModelProperty(value = "秒杀活动名称", example = "晚九点限时秒杀")
private String name;
@ApiModelProperty(value = "活动状态", example = "进行中")
private Integer status;
@ApiModelProperty(value = "秒杀时段id", example = "1")
private Long timeId;
@ApiModelProperty(value = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
@ApiModel("管理后台 - 秒杀活动 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityRespVO extends SeckillActivityBaseVO {
@ApiModelProperty(value = "秒杀活动id", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "付款订单数", required = true, example = "1")
private Integer orderCount;
@ApiModelProperty(value = "付款人数", required = true, example = "1")
private Integer userCount;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
@ApiModelProperty(value = "秒杀时段id", required = true, example = "1,3")
private List<Long> timeIds;
@ApiModelProperty(value = "排序", required = true, example = "1")
private Integer sort;
@ApiModelProperty(value = "备注", example = "限时秒杀活动")
private String remark;
@ApiModelProperty(value = "活动状态", example = "进行中")
private Integer status;
}

View File

@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@ApiModel("管理后台 - 秒杀活动更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityUpdateReqVO extends SeckillActivityBaseVO {
@ApiModelProperty(value = "秒杀活动编号", required = true, example = "224")
@NotNull(message = "秒杀活动编号不能为空")
private Long id;
@ApiModelProperty(value = "备注", example = "限时秒杀活动")
private String remark;
@ApiModelProperty(value = "排序", required = true, example = "1")
@NotNull(message = "排序不能为空")
private Integer sort;
@ApiModelProperty(value = "秒杀时段id", required = true, example = "1,3")
@NotEmpty(message = "秒杀时段id不能为空")
private List<Long> timeIds;
/**
* 商品列表
*/
@NotEmpty(message = "商品列表不能为空")
@Valid
private List<Product> products;
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.time.LocalTime;
/**
* 秒杀时段 Base VO提供给添加、修改、详细的子 VO 使用
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
*/
@Data
public class SeckillTimeBaseVO {
@ApiModelProperty(value = "秒杀时段名称", required = true, example = "上午场")
@NotNull(message = "秒杀时段名称不能为空")
private String name;
@ApiModelProperty(value = "开始时间点", required = true, example = "16:30:40")
@NotNull(message = "开始时间点不能为空")
private LocalTime startTime;
@ApiModelProperty(value = "结束时间点", required = true, example = "16:30:40")
@NotNull(message = "结束时间点不能为空")
private LocalTime endTime;
}

View File

@@ -0,0 +1,12 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import lombok.*;
import io.swagger.annotations.*;
@ApiModel("管理后台 - 秒杀时段创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimeCreateReqVO extends SeckillTimeBaseVO {
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalTime;
@ApiModel("管理后台 - 秒杀时段分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimePageReqVO extends PageParam {
@ApiModelProperty(value = "秒杀时段名称", example = "上午场")
private String name;
@ApiModelProperty(value = "开始时间点", example = "16:30:40")
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime startTime;
@ApiModelProperty(value = "结束时间点", example = "16:30:40")
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime endTime;
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@ApiModel("管理后台 - 秒杀时段 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimeRespVO extends SeckillTimeBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "秒杀活动数量", required = true, example = "1")
private Integer seckillActivityCount;
@ApiModelProperty(value = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 秒杀时段更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimeUpdateReqVO extends SeckillTimeBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1")
@NotNull(message = "编号不能为空")
private Long id;
}

View File

@@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity;
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.promotion.controller.admin.seckill.vo.activity.*;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 秒杀活动 Convert
*
* @author 芋道源码
*/
@Mapper
public interface SeckillActivityConvert {
SeckillActivityConvert INSTANCE = Mappers.getMapper(SeckillActivityConvert.class);
SeckillProductDO convert(SeckillActivityBaseVO.Product product);
SeckillActivityDO convert(SeckillActivityCreateReqVO bean);
default String map(Long[] value) {
return value.toString();
}
SeckillActivityDO convert(SeckillActivityUpdateReqVO bean);
SeckillActivityRespVO convert(SeckillActivityDO bean);
List<SeckillActivityRespVO> convertList(List<SeckillActivityDO> list);
PageResult<SeckillActivityRespVO> convertPage(PageResult<SeckillActivityDO> page);
@Mappings({@Mapping(target = "products", source = "seckillProducts")})
SeckillActivityDetailRespVO convert(SeckillActivityDO seckillActivity, List<SeckillProductDO> seckillProducts);
/**
* 比较两个秒杀商品对象是否相等
*
* @param productDO 数据库中的商品
* @param productVO 前端传入的商品
* @return 是否匹配
*/
default boolean isEquals(SeckillProductDO productDO, SeckillActivityBaseVO.Product productVO) {
return ObjectUtil.equals(productDO.getSpuId(), productVO.getSpuId())
&& ObjectUtil.equals(productDO.getSkuId(), productVO.getSkuId())
&& ObjectUtil.equals(productDO.getSeckillPrice(), productVO.getSeckillPrice())
&& ObjectUtil.equals(productDO.getStock(), productVO.getStock())
&& ObjectUtil.equals(productDO.getLimitBuyCount(), productVO.getLimitBuyCount());
}
/**
* 比较两个秒杀商品对象是否相等
*
* @param productDO 商品1
* @param productVO 商品2
* @return 是否匹配
*/
default boolean isEquals(SeckillProductDO productDO, SeckillProductDO productVO) {
return ObjectUtil.equals(productDO.getSpuId(), productVO.getSpuId())
&& ObjectUtil.equals(productDO.getSkuId(), productVO.getSkuId())
&& ObjectUtil.equals(productDO.getSeckillPrice(), productVO.getSeckillPrice())
&& ObjectUtil.equals(productDO.getStock(), productVO.getStock())
&& ObjectUtil.equals(productDO.getLimitBuyCount(), productVO.getLimitBuyCount());
}
default List<SeckillProductDO> convertList(List<SeckillActivityBaseVO.Product> products, SeckillActivityDO seckillActivity) {
return CollectionUtils.convertList(products, product -> convert(product)
.setActivityId(seckillActivity.getId()).setTimeIds(seckillActivity.getTimeIds()));
}
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.promotion.convert.seckill.seckilltime;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.time.SeckillTimeUpdateReqVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime.SeckillTimeDO;
/**
* 秒杀时段 Convert
*
* @author 芋道源码
*/
@Mapper
public interface SeckillTimeConvert {
SeckillTimeConvert INSTANCE = Mappers.getMapper(SeckillTimeConvert.class);
SeckillTimeDO convert(SeckillTimeCreateReqVO bean);
SeckillTimeDO convert(SeckillTimeUpdateReqVO bean);
SeckillTimeRespVO convert(SeckillTimeDO bean);
List<SeckillTimeRespVO> convertList(List<SeckillTimeDO> list);
PageResult<SeckillTimeRespVO> convertPage(PageResult<SeckillTimeDO> page);
}

View File

@@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
/**
* 秒杀活动 DO
*
* @author halfninety
*/
@TableName(value = "promotion_seckill_activity", autoResultMap = true)
@KeySequence("promotion_seckill_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityDO extends BaseDO {
/**
* 秒杀活动编号
*/
@TableId
private Long id;
/**
* 秒杀活动名称
*/
private String name;
/**
* 活动状态
* <p>
* 枚举 {@link PromotionActivityStatusEnum 对应的类}
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 活动开始时间
*/
private LocalDateTime startTime;
/**
* 活动结束时间
*/
private LocalDateTime endTime;
/**
* 排序
*/
private Integer sort;
/**
* 秒杀时段 id
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> timeIds;
/**
* 付款订单数
*/
private Integer orderCount;
/**
* 付款人数
*/
private Integer userCount;
/**
* 订单实付金额,单位:分
*/
private Long totalPrice;
}

View File

@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
/**
* 秒杀参与商品
*
* @author halfninety
* @TableName promotion_seckill_product
*/
@TableName(value = "promotion_seckill_product", autoResultMap = true)
@KeySequence("promotion_seckill_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillProductDO extends BaseDO {
/**
* 秒杀参与商品编号
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 秒杀活动id
*/
private Long activityId;
/**
* 秒杀时段id
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> timeIds;
/**
* 商品id
*/
private Long spuId;
/**
* 商品sku_id
*/
private Long skuId;
/**
* 秒杀金额
*/
private Integer seckillPrice;
/**
* 秒杀库存
*/
private Integer stock;
/**
* 每人限购
*/
private Integer limitBuyCount;
}

View File

@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime;
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.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalTime;
/**
* 秒杀时段 DO
*
* @author 芋道源码
*/
@TableName("promotion_seckill_time")
@KeySequence("promotion_seckill_time_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillTimeDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 秒杀时段名称
*/
private String name;
/**
* 开始时间点
*/
private LocalTime startTime;
/**
* 结束时间点
*/
private LocalTime endTime;
/**
* 秒杀活动数量
*/
private Integer seckillActivityCount;
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
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.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 秒杀活动 Mapper
*
* @author halfninety
*/
@Mapper
public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> {
default PageResult<SeckillActivityDO> selectPage(SeckillActivityPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<SeckillActivityDO>()
.likeIfPresent(SeckillActivityDO::getName, reqVO.getName())
.eqIfPresent(SeckillActivityDO::getStatus, reqVO.getStatus())
.betweenIfPresent(SeckillActivityDO::getCreateTime, reqVO.getCreateTime())
.apply(ObjectUtil.isNotNull(reqVO.getTimeId()),"FIND_IN_SET(" + reqVO.getTimeId() + ",time_ids) > 0")
.orderByDesc(SeckillActivityDO::getId));
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 秒杀活动商品 Mapper
*
* @author halfninety
*/
@Mapper
public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
default List<SeckillProductDO> selectListByActivityId(Long id) {
return selectList(SeckillProductDO::getActivityId, id);
}
default List<SeckillProductDO> selectListBySkuIds(Collection<Long> skuIds) {
return selectList(SeckillProductDO::getSkuId, skuIds);
}
default void updateTimeIdsByActivityId(Long id, List<Long> timeIds) {
new LambdaUpdateChainWrapper<>(this)
.set(SeckillProductDO::getTimeIds, CollUtil.join(timeIds, ","))
.eq(SeckillProductDO::getActivityId, id)
.update();
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckilltime;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckilltime.SeckillTimeDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalTime;
import java.util.Collection;
import java.util.List;
/**
* 秒杀时段 Mapper
*
* @author halfninety
*/
@Mapper
public interface SeckillTimeMapper extends BaseMapperX<SeckillTimeDO> {
default List<SeckillTimeDO> selectListByTime(LocalTime time) {
return selectList(SeckillTimeDO::getStartTime, SeckillTimeDO::getEndTime, time);
}
default List<SeckillTimeDO> selectListByTime(LocalTime startTime, LocalTime endTime) {
return selectList(new LambdaQueryWrapper<SeckillTimeDO>()
.ge(SeckillTimeDO::getStartTime, startTime)
.le(SeckillTimeDO::getEndTime, endTime));
}
default void updateActivityCount(Collection<Long> ids, String type, Integer count) {
new LambdaUpdateChainWrapper<>(this)
.in(SeckillTimeDO::getId, ids)
.setSql("`seckill_activity_count` = `seckill_activity_count` " + type + count)
.update();
}
}

View File

@@ -7,7 +7,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
@@ -71,7 +71,7 @@ public class CouponServiceImpl implements CouponService {
Set<Long> userIds = null;
if (StrUtil.isNotEmpty(pageReqVO.getNickname())) {
userIds = CollectionUtils.convertSet(memberUserApi.getUserListByNickname(pageReqVO.getNickname()),
UserRespDTO::getId);
MemberUserRespDTO::getId);
if (CollUtil.isEmpty(userIds)) {
return PageResult.empty();
}

View File

@@ -265,7 +265,7 @@ public class PriceServiceImpl implements PriceService {
private void calculatePriceByRewardActivity(PriceCalculateRespDTO priceCalculate, List<PriceCalculateRespDTO.OrderItem> orderItems,
RewardActivityDO rewardActivity) {
// 获得最大匹配的满减送活动的规
// 获得最大匹配的满减送活动的规
RewardActivityDO.Rule rule = getLastMatchRewardActivityRule(rewardActivity, orderItems);
if (rule == null) {
// 获取不到的情况下,记录不满足的优惠明细
@@ -288,11 +288,11 @@ public class PriceServiceImpl implements PriceService {
}
/**
* 获得最大匹配的满减送活动的规
* 获得最大匹配的满减送活动的规
*
* @param rewardActivity 满减送活动
* @param orderItems 商品项
* @return 匹配的活动规
* @return 匹配的活动规
*/
private RewardActivityDO.Rule getLastMatchRewardActivityRule(RewardActivityDO rewardActivity,
List<PriceCalculateRespDTO.OrderItem> orderItems) {

View File

@@ -0,0 +1,80 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
/**
* 秒杀活动 Service 接口
*
* @author halfninety
*/
public interface SeckillActivityService {
/**
* 创建秒杀活动
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSeckillActivity(@Valid SeckillActivityCreateReqVO createReqVO);
/**
* 更新秒杀活动
*
* @param updateReqVO 更新信息
*/
void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO);
/**
* 关闭秒杀活动
*
* @param id 编号
*/
void closeSeckillActivity(Long id);
/**
* 删除秒杀活动
*
* @param id 编号
*/
void deleteSeckillActivity(Long id);
/**
* 获得秒杀活动
*
* @param id 编号
* @return 秒杀活动
*/
SeckillActivityDO getSeckillActivity(Long id);
/**
* 获得秒杀活动列表
*
* @param ids 编号
* @return 秒杀活动列表
*/
List<SeckillActivityDO> getSeckillActivityList(Collection<Long> ids);
/**
* 获得秒杀活动分页
*
* @param pageReqVO 分页查询
* @return 秒杀活动分页
*/
PageResult<SeckillActivityDO> getSeckillActivityPage(SeckillActivityPageReqVO pageReqVO);
/**
* 通过活动编号获取活动商品
*
* @param id 活动编号
* @return 活动商品列表
*/
List<SeckillProductDO> getSeckillProductListByActivityId(Long id);
}

View File

@@ -0,0 +1,226 @@
package cn.iocoder.yudao.module.promotion.service.seckill.seckillactivity;
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.promotion.controller.admin.seckill.vo.activity.SeckillActivityBaseVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionActivityStatusEnum;
import cn.iocoder.yudao.module.promotion.service.seckill.seckilltime.SeckillTimeService;
import cn.iocoder.yudao.module.promotion.util.PromotionUtils;
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.promotion.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList;
/**
* 秒杀活动 Service 实现类
*
* @author halfninety
*/
@Service
@Validated
public class SeckillActivityServiceImpl implements SeckillActivityService {
@Resource
private SeckillActivityMapper seckillActivityMapper;
@Resource
private SeckillProductMapper seckillProductMapper;
@Resource
private SeckillTimeService seckillTimeService;
@Override
public Long createSeckillActivity(SeckillActivityCreateReqVO createReqVO) {
// 校验商品是否冲突
validateSeckillActivityProductConflicts(null, createReqVO.getProducts());
// 校验秒杀时段是否存在
seckillTimeService.validateSeckillTimeExists(createReqVO.getTimeIds());
// 插入秒杀活动
SeckillActivityDO seckillActivity = SeckillActivityConvert.INSTANCE.convert(createReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(createReqVO.getStartTime(), createReqVO.getEndTime()));
seckillActivityMapper.insert(seckillActivity);
// 插入商品
List<SeckillProductDO> productDOS = SeckillActivityConvert.INSTANCE.convertList(createReqVO.getProducts(), seckillActivity);
seckillProductMapper.insertBatch(productDOS);
// 更新秒杀时段的秒杀活动数量
seckillTimeService.sekillActivityCountIncr(createReqVO.getTimeIds());
return seckillActivity.getId();
}
@Override
public void updateSeckillActivity(SeckillActivityUpdateReqVO updateReqVO) {
// 校验存在
SeckillActivityDO seckillActivity = validateSeckillActivityExists(updateReqVO.getId());
if (PromotionActivityStatusEnum.CLOSE.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
}
// 校验商品是否冲突
validateSeckillActivityProductConflicts(updateReqVO.getId(), updateReqVO.getProducts());
// 更新活动
SeckillActivityDO updateObj = SeckillActivityConvert.INSTANCE.convert(updateReqVO)
.setStatus(PromotionUtils.calculateActivityStatus(updateReqVO.getStartTime(), updateReqVO.getEndTime()));
seckillActivityMapper.updateById(updateObj);
// 更新商品
updateSeckillProduct(updateReqVO);
// 更新秒杀时段的秒杀活动数量
updateSeckillTimeActivityCount(seckillActivity, updateReqVO.getTimeIds());
}
/**
* 更新秒杀时段的秒杀活动数量
*
* @param seckillActivity 查询出的秒杀活动
* @param updateTimeIds 更新后的秒杀时段id列表
*/
private void updateSeckillTimeActivityCount(SeckillActivityDO seckillActivity, List<Long> updateTimeIds) {
// 查询出 timeIds
List<Long> existsTimeIds = seckillActivity.getTimeIds();
// 需要减少的时间段
Collection<Long> reduceIds = CollUtil.filterNew(existsTimeIds, existsTimeId -> !updateTimeIds.contains(existsTimeId));
// 需要添加的时间段
updateTimeIds.removeIf(existsTimeIds::contains);
// 更新减少时间段和增加时间段
if (CollUtil.isNotEmpty(updateTimeIds)) {
seckillTimeService.sekillActivityCountIncr(updateTimeIds);
}
if (CollUtil.isNotEmpty(reduceIds)) {
seckillTimeService.sekillActivityCountDecr(reduceIds);
}
}
/**
* 更新秒杀商品
* 后台查出的数据和前台查出的数据进行遍历,
* 1. 对前台数据进行遍历:如果不存在于后台的 sku 中需要新增
* 2. 对后台数据进行遍历:如果不存在于前台的 sku 中需要删除
* 3. 最后对当前活动商品全部更新更新秒杀时段id列表
*
* @param updateReqVO 更新的请求VO
*/
private void updateSeckillProduct(SeckillActivityUpdateReqVO updateReqVO) {
List<SeckillProductDO> seckillProductDOS = seckillProductMapper.selectListByActivityId(updateReqVO.getId());
List<SeckillActivityBaseVO.Product> products = updateReqVO.getProducts();
// 计算需要删除的数据
List<Long> deleteIds = CollectionUtils.convertList(seckillProductDOS, SeckillProductDO::getId,
seckillProductDO -> products.stream()
.noneMatch(product -> SeckillActivityConvert.INSTANCE.isEquals(seckillProductDO, product)));
if (CollUtil.isNotEmpty(deleteIds)) {
seckillProductMapper.deleteBatchIds(deleteIds);
}
// 计算需要新增的数据
List<SeckillProductDO> newSeckillProductDOs = CollectionUtils.convertList(products,
product -> SeckillActivityConvert.INSTANCE.convert(product).setActivityId(updateReqVO.getId()));
newSeckillProductDOs.removeIf(product -> seckillProductDOS.stream()
.anyMatch(seckillProduct -> SeckillActivityConvert.INSTANCE.isEquals(seckillProduct, product)));
if (CollUtil.isNotEmpty(newSeckillProductDOs)) {
seckillProductMapper.insertBatch(newSeckillProductDOs);
}
//全量更新当前活动商品的秒杀时段id列表timeIds
seckillProductMapper.updateTimeIdsByActivityId(updateReqVO.getId(), updateReqVO.getTimeIds());
}
/**
* 校验商品是否冲突
*
* @param id 秒杀活动编号
* @param products 商品列表
*/
private void validateSeckillActivityProductConflicts(Long id, List<SeckillActivityBaseVO.Product> products) {
if (CollUtil.isEmpty(products)) {
return;
}
List<SeckillProductDO> seckillProductDOS = seckillProductMapper
.selectListBySkuIds(CollectionUtils.convertSet(products, SeckillActivityBaseVO.Product::getSkuId));
if (CollUtil.isEmpty(seckillProductDOS)) {
return;
}
List<SeckillActivityDO> seckillActivityDOS = seckillActivityMapper
.selectBatchIds(CollectionUtils.convertSet(seckillProductDOS, SeckillProductDO::getActivityId));
if (id != null) { // 排除自己这个活动
seckillActivityDOS.removeIf(item -> id.equals(item.getId()));
}
// 排除不满足 status 的活动
List<Integer> statuses = asList(PromotionActivityStatusEnum.WAIT.getStatus(), PromotionActivityStatusEnum.RUN.getStatus());
seckillActivityDOS.removeIf(item -> !statuses.contains(item.getStatus()));
// 如果非空,则说明冲突
if (CollUtil.isNotEmpty(seckillActivityDOS)) {
throw exception(SECKILL_ACTIVITY_SPU_CONFLICTS);
}
}
@Override
public void closeSeckillActivity(Long id) {
// 校验存在
SeckillActivityDO seckillActivity = this.validateSeckillActivityExists(id);
if (PromotionActivityStatusEnum.CLOSE.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
}
if (PromotionActivityStatusEnum.END.getStatus().equals(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_END);
}
// 更新
SeckillActivityDO updateObj = new SeckillActivityDO().setId(id).setStatus(PromotionActivityStatusEnum.CLOSE.getStatus());
seckillActivityMapper.updateById(updateObj);
}
@Override
public void deleteSeckillActivity(Long id) {
// 校验存在
SeckillActivityDO seckillActivity = this.validateSeckillActivityExists(id);
List<Integer> statuses = asList(PromotionActivityStatusEnum.CLOSE.getStatus(), PromotionActivityStatusEnum.END.getStatus());
if (!statuses.contains(seckillActivity.getStatus())) {
throw exception(SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
}
// 更新秒杀时段的秒杀活动数量
seckillTimeService.sekillActivityCountDecr(seckillActivity.getTimeIds());
// 删除
seckillActivityMapper.deleteById(id);
}
private SeckillActivityDO validateSeckillActivityExists(Long id) {
SeckillActivityDO seckillActivity = seckillActivityMapper.selectById(id);
if (seckillActivity == null) {
throw exception(SECKILL_ACTIVITY_NOT_EXISTS);
}
return seckillActivity;
}
@Override
public SeckillActivityDO getSeckillActivity(Long id) {
return seckillActivityMapper.selectById(id);
}
@Override
public List<SeckillActivityDO> getSeckillActivityList(Collection<Long> ids) {
return seckillActivityMapper.selectBatchIds(ids);
}
@Override
public PageResult<SeckillActivityDO> getSeckillActivityPage(SeckillActivityPageReqVO pageReqVO) {
return seckillActivityMapper.selectPage(pageReqVO);
}
@Override
public List<SeckillProductDO> getSeckillProductListByActivityId(Long id) {
return seckillProductMapper.selectListByActivityId(id);
}
}

Some files were not shown because too many files have changed in this diff Show More