diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index 6b83bb3d6..d57fb21b9 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java @@ -173,6 +173,23 @@ public class CollectionUtils { return valueFunc.apply(t); } + public static > V getMinValue(List from, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + T t = from.stream().min(Comparator.comparing(valueFunc)).get(); + return valueFunc.apply(t); + } + + public static > V getSumValue(List from, Function valueFunc, BinaryOperator accumulator) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + return from.stream().map(valueFunc).reduce(accumulator).get(); + } + public static void addIfNotNull(Collection coll, T item) { if (item == null) { return; diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java index 0c47d9406..454314e78 100644 --- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java @@ -15,6 +15,7 @@ public interface ErrorCodeConstants { ErrorCode PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, "父分类不能是二级分类"); ErrorCode PRODUCT_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, "存在子分类,无法删除"); ErrorCode PRODUCT_CATEGORY_DISABLED = new ErrorCode(1008001004, "商品分类({})已禁用,无法使用"); + ErrorCode PRODUCT_CATEGORY_LEVEL = new ErrorCode(1008001005, "商品需挂在三级分类下"); // ========== 品牌相关编号 1008002000 ========== ErrorCode PRODUCT_BRAND_NOT_EXISTS = new ErrorCode(1008002000, "品牌不存在"); @@ -31,4 +32,8 @@ public interface ErrorCodeConstants { // ========== 商品sku 1008006000 ========== ErrorCode SKU_NOT_EXISTS = new ErrorCode(1008006000, "商品sku不存在"); ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品sku的属性组合存在重复"); + + ErrorCode PRODUCT_SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 Spu 下的每个 SKU ,其规格数必须一致"); + + ErrorCode PRODUCT_SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU ,必须不重复"); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java index 95364f75a..7f1da8553 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/ProductCategoryServiceImpl.java @@ -93,6 +93,14 @@ public class ProductCategoryServiceImpl implements ProductCategoryService { @Override public void validateProductCategory(Long id) { + Integer level = categoryLevel(id, 1); + if(level < 3){ + throw exception(PRODUCT_CATEGORY_LEVEL); + } + } + + // 校验分类级别 + private Integer categoryLevel(Long id, int level){ ProductCategoryDO category = productCategoryMapper.selectById(id); if (category == null) { throw exception(PRODUCT_CATEGORY_NOT_EXISTS); @@ -100,6 +108,10 @@ public class ProductCategoryServiceImpl implements ProductCategoryService { if (ObjectUtil.notEqual(category.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { throw exception(PRODUCT_CATEGORY_DISABLED); } + if(category.getParentId() == 0) { + return level; + } + return categoryLevel(category.getParentId(), ++level); } @Override diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java index 20c7680bf..59e8a04d7 100755 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java @@ -68,14 +68,14 @@ public interface ProductSkuService { * * @param list sku组合的集合 */ - void validateProductSkus(List list); + void validateProductSkus(List list, Integer specType); /** * 批量创建 SKU * * @param list SKU 对象集合 */ - void createProductSkus(List list); + void createProductSkus(List list, Long spuId); /** * 根据 SPU 编号,批量更新它的 SKU 信息 diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java index 50408bcb3..18a9ec7df 100755 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.product.service.sku; 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.ProductPropertyRespVO; import cn.iocoder.yudao.module.product.controller.admin.propertyvalue.vo.ProductPropertyValueRespVO; import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO; @@ -11,6 +12,7 @@ import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert; import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper; import cn.iocoder.yudao.module.product.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum; import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -86,45 +88,52 @@ public class ProductSkuServiceImpl implements ProductSkuService { return productSkuMapper.selectPage(pageReqVO); } - // TODO luowenfeng:参考下 yudao-cloud 的 checkProductAttr 方法,重构下 @Override - public void validateProductSkus(List list) { - List skuPropertyList = list.stream().flatMap(p -> Optional.of(p.getProperties()).orElse(new ArrayList<>()).stream()).collect(Collectors.toList()); - // 校验规格属性存在 - // TODO @luowenfeng:使用 CollectionUtils.convert - List propertyIds = skuPropertyList.stream().map(ProductSkuBaseVO.Property::getPropertyId).collect(Collectors.toList()); - List propertyAndValueList = productPropertyService.selectByIds(propertyIds); - // TODO @luowenfeng:校验数量一致; - if (propertyAndValueList.isEmpty()) { - throw exception(PROPERTY_NOT_EXISTS); - } - // 校验规格属性值存在 - // TODO @luowenfeng:使用 CollectionUtils.convert - Map propertyMap = propertyAndValueList.stream().collect(Collectors.toMap(ProductPropertyRespVO::getId, p -> p)); - skuPropertyList.forEach(p -> { - ProductPropertyRespVO productPropertyRespVO = propertyMap.get(p.getPropertyId()); - // 如果对应的属性名不存在或属性名下的属性值集合为空,给出提示 - if (null == productPropertyRespVO || productPropertyRespVO.getPropertyValueList().isEmpty()) { + public void validateProductSkus(List list, Integer specType) { + // 多规格才需校验 + if(specType.equals(ProductSpuSpecTypeEnum.DISABLE.getType())){ + List skuPropertyList = list.stream().flatMap(p -> Optional.of(p.getProperties()).orElse(new ArrayList<>()).stream()).collect(Collectors.toList()); + // 1、校验规格属性存在 + List propertyIds = CollectionUtils.convertList(skuPropertyList, ProductSkuBaseVO.Property::getPropertyId); + List propertyAndValueList = productPropertyService.selectByIds(propertyIds); + if (propertyAndValueList.size() == propertyIds.size()){ throw exception(PROPERTY_NOT_EXISTS); } - // 判断改属性名对应的属性值是否存在,不存在,给出提示 - if (!productPropertyRespVO.getPropertyValueList().stream().map(ProductPropertyValueRespVO::getId).collect(Collectors.toSet()).contains(p.getValueId())) { - throw exception(ErrorCodeConstants.PROPERTY_VALUE_NOT_EXISTS); + // 2. 校验,一个 Sku 下,没有重复的规格。校验方式是,遍历每个 Sku ,看看是否有重复的规格 attrId + List collect = propertyAndValueList.stream() + .flatMap(v -> Optional.of(v.getPropertyValueList()) + .orElse(new ArrayList<>()).stream()).collect(Collectors.toList()); + Map propertyValueRespVOMap = CollectionUtils.convertMap(collect, ProductPropertyValueRespVO::getId); + list.forEach(v->{ + Set keys = v.getProperties().stream().map(k -> propertyValueRespVOMap.get(k.getValueId()).getPropertyId()).collect(Collectors.toSet()); + if(keys.size() != v.getProperties().size()){ + throw exception(ErrorCodeConstants.SKU_PROPERTIES_DUPLICATED); + } + }); + + // 3. 再校验,每个 Sku 的规格值的数量,是一致的。 + int attrValueIdsSize = list.get(0).getProperties().size(); + for (int i = 1; i < list.size(); i++) { + if (attrValueIdsSize != list.get(i).getProperties().size()) { + throw exception(ErrorCodeConstants.PRODUCT_SPU_ATTR_NUMBERS_MUST_BE_EQUALS); + } } - }); - // 校验是否有重复的sku组合 - List> skuProperties = list.stream().map(ProductSkuBaseVO::getProperties).collect(Collectors.toList()); - Set skuPropertiesConvertSet = new HashSet<>(); - skuProperties.forEach(p -> { - // 组合属性值id为 1~2~3.... 形式的字符串,通过set的特性判断是否有重复的组合 - if (!skuPropertiesConvertSet.add(p.stream().map(pr -> String.valueOf(pr.getValueId())).sorted().collect(Collectors.joining("~")))) { - throw exception(ErrorCodeConstants.SKU_PROPERTIES_DUPLICATED); + + // 4. 最后校验,每个 Sku 之间不是重复的 + Set> skuAttrValues = new HashSet<>(); // 每个元素,都是一个 Sku 的 attrValueId 集合。这样,通过最外层的 Set ,判断是否有重复的. + for (ProductSkuCreateOrUpdateReqVO sku : list) { + if (!skuAttrValues.add(sku.getProperties().stream().map(ProductSkuBaseVO.Property::getValueId).collect(Collectors.toSet()))) { // 添加失败,说明重复 + throw exception(ErrorCodeConstants.PRODUCT_SPU_SKU_NOT_DUPLICATE); + } } - }); + } } @Override - public void createProductSkus(List skuDOList) { + public void createProductSkus(List skuCreateReqList, Long spuId) { + // 批量插入 SKU + List skuDOList = ProductSkuConvert.INSTANCE.convertSkuDOList(skuCreateReqList); + skuDOList.forEach(v->v.setSpuId(spuId)); productSkuMapper.insertBatch(skuDOList); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java index 26c564f34..dec092856 100755 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.product.service.spu; 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.ProductPropertyRespVO; import cn.iocoder.yudao.module.product.controller.admin.property.vo.ProductPropertyViewRespVO; import cn.iocoder.yudao.module.product.controller.admin.propertyvalue.vo.ProductPropertyValueRespVO; @@ -58,31 +59,23 @@ public class ProductSpuServiceImpl implements ProductSpuService { @Transactional public Long createProductSpu(ProductSpuCreateReqVO createReqVO) { // 校验分类 - // TODO @luowenfeng:可以在这个类里加个方法,校验分类;商品必须挂在三级分类下; categoryService.validateProductCategory(createReqVO.getCategoryId()); // TODO @luowenfeng:校验品牌 + // 校验SKU List skuCreateReqList = createReqVO.getSkus(); - // 多规格才需校验 - // TODO @luowenfeng:可以把 type 传递到 productSkuService 里,通过它统一判断处理 - if(Objects.equals(createReqVO.getSpecType(), ProductSpuSpecTypeEnum.DISABLE.getType())) { - productSkuService.validateProductSkus(skuCreateReqList); - } + productSkuService.validateProductSkus(skuCreateReqList, createReqVO.getSpecType()); // 插入 SPU ProductSpuDO spu = ProductSpuConvert.INSTANCE.convert(createReqVO); - // TODO @luowenfeng:可以在 CollectionUtils 增加 getMaxValue 方法,增加一个 defaultValue 方法,如果为空,则返回 defaultValue - spu.setMarketPrice(skuCreateReqList.stream().map(ProductSkuCreateOrUpdateReqVO::getMarketPrice).max(Integer::compare).orElse(0)); - spu.setMaxPrice(skuCreateReqList.stream().map(ProductSkuCreateOrUpdateReqVO::getPrice).max(Integer::compare).orElse(0)); - spu.setMinPrice(skuCreateReqList.stream().map(ProductSkuCreateOrUpdateReqVO::getPrice).min(Integer::compare).orElse(0)); - // TODO @luowenfeng:库存求和 + 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)); ProductSpuMapper.insert(spu); - // 批量插入 SKU - // TODO @luowenfeng:convert 逻辑,交给 createProductSkus 一起处理 - List skuDOList = ProductSkuConvert.INSTANCE.convertSkuDOList(skuCreateReqList); - skuDOList.forEach(v->v.setSpuId(spu.getId())); - productSkuService.createProductSkus(skuDOList); + // 插入 SKU + productSkuService.createProductSkus(skuCreateReqList, spu.getId()); // 返回 return spu.getId(); } @@ -98,10 +91,7 @@ public class ProductSpuServiceImpl implements ProductSpuService { // 校验SKU List skuCreateReqList = updateReqVO.getSkus(); // 多规格才需校验 - // TODO @luowenfeng:可以把 type 传递到 productSkuService 里,通过它统一判断处理 - if(updateReqVO.getSpecType().equals(ProductSpuSpecTypeEnum.DISABLE.getType())) { - productSkuService.validateProductSkus(skuCreateReqList); - } + productSkuService.validateProductSkus(skuCreateReqList, updateReqVO.getSpecType()); // 更新 SPU ProductSpuDO updateObj = ProductSpuConvert.INSTANCE.convert(updateReqVO);