进行商品添加的迁移

This commit is contained in:
YunaiV
2020-07-27 20:07:23 +08:00
parent be4b34c884
commit e107b42f53
49 changed files with 553 additions and 1042 deletions

View File

@@ -18,15 +18,15 @@ public interface ProductErrorCodeConstants {
ErrorCode PRODUCT_CATEGORY_PARENT_CAN_NOT_BE_LEVEL2 = new ErrorCode(1002001005, "父分类必须是一级分类");
// ========== PRODUCT SPU + SKU 模块 ==========
ErrorCode PRODUCT_SKU_ATTR_CANT_NOT_DUPLICATE = new ErrorCode(1003002000, "一个 Sku 下,不能有重复的规格");
ErrorCode PRODUCT_SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1003002001, "一个 Spu 下的每个 Sku ,其规格数必须一致");
ErrorCode PRODUCT_SPU_SKU__NOT_DUPLICATE = new ErrorCode(1003002002, "一个 Spu 下的每个 Sku ,必须不重复");
ErrorCode PRODUCT_SPU_NOT_EXISTS = new ErrorCode(1003002003, "Spu 不存在");
ErrorCode PRODUCT_SPU_CATEGORY_MUST_BE_LEVEL2 = new ErrorCode(1003002003, "Spu 只能添加在二级分类下");
ErrorCode PRODUCT_SKU_ATTR_CANT_NOT_DUPLICATE = new ErrorCode(1003002000, "一个 SKU 下,不能有重复的规格");
ErrorCode PRODUCT_SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1003002001, "一个 Spu 下的每个 SKU ,其规格数必须一致");
ErrorCode PRODUCT_SPU_SKU_NOT_DUPLICATE = new ErrorCode(1003002002, "一个 SPU 下的每个 SKU ,必须不重复");
ErrorCode PRODUCT_SPU_NOT_EXISTS = new ErrorCode(1003002003, "SPU 不存在");
ErrorCode PRODUCT_SPU_CATEGORY_MUST_BE_LEVEL2 = new ErrorCode(1003002003, "SPU 只能添加在二级分类下");
// ========== PRODUCT ATTR + ATTR_VALUE 模块 ==========
ErrorCode PRODUCT_ATTR_VALUE_NOT_EXIST = new ErrorCode(1003003000, "商品属性值不存在");
ErrorCode PRODUCT_ATTR_NOT_EXIST = new ErrorCode(1003003001, "商品属性值不存在");
ErrorCode PRODUCT_ATTR_VALUE_NOT_EXIST = new ErrorCode(1003003000, "商品属性值不存在");
ErrorCode PRODUCT_ATTR_KEY_NOT_EXIST = new ErrorCode(1003003001, "商品属性值不存在");
ErrorCode PRODUCT_ATTR_EXISTS = new ErrorCode(1003003002, "商品规格已经存在");
ErrorCode PRODUCT_ATTR_STATUS_EQUALS = new ErrorCode(1003003003, "商品规格已经是该状态");
ErrorCode PRODUCT_ATTR_VALUE_EXISTS = new ErrorCode(1003003004, "商品规格值已经存在");

View File

@@ -2,7 +2,7 @@ package cn.iocoder.mall.productservice.rpc.spu;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuPageReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuRespDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuUpdateReqDTO;
@@ -20,7 +20,7 @@ public interface ProductSpuRpc {
* @param createDTO 创建商品 SPU DTO
* @return 商品 SPU编号
*/
CommonResult<Integer> createProductSpu(ProductSpuCreateReqDTO createDTO);
CommonResult<Integer> createProductSpu(ProductSpuAndSkuCreateReqDTO createDTO);
/**
* 更新商品 SPU

View File

@@ -0,0 +1,90 @@
package cn.iocoder.mall.productservice.rpc.spu.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* 商品 SPU 和 SKU 创建 Request DTO
*/
@Data
@Accessors(chain = true)
public class ProductSpuAndSkuCreateReqDTO implements Serializable {
/**
* SKU 信息
*/
@Data
@Accessors(chain = true)
public static class Sku implements Serializable {
/**
* 规格值数组
*/
@NotNull(message = "规格值数组不能为空")
private List<Integer> attrValueIds;
/**
* 价格,单位:分
*/
@NotNull(message = "价格不能为空")
@Min(value = 1L, message = "最小价格为 1")
private Integer price;
/**
* 库存数量
*/
@NotNull(message = "库存数量不能为空")
@Min(value = 1L, message = "最小库存为 1")
private Integer quantity;
}
// ========== 基本信息 =========
/**
* SPU 名字
*/
@NotEmpty(message = "SPU 名字不能为空")
private String name;
/**
* 卖点
*/
@NotEmpty(message = "卖点不能为空")
private String sellPoint;
/**
* 描述
*/
@NotEmpty(message = "描述不能为空")
private String description;
/**
* 分类编号
*/
@NotNull(message = "分类编号不能为空")
private Integer cid;
/**
* 商品主图地址
*/
@NotEmpty(message = "商品主图地址不能为空")
private List<String> picUrls;
// ========== 其他信息 =========
/**
* 是否上架商品
*/
@NotNull(message = "是否上架商品不能为空")
private Boolean visible;
// ========== SKU =========
/**
* SKU 数组
*/
@NotNull(message = "SKU 不能为空")
@Valid
private List<Sku> skus;
}

View File

@@ -1,64 +0,0 @@
package cn.iocoder.mall.productservice.rpc.spu.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* 商品 SPU 创建 Request DTO
*/
@Data
@Accessors(chain = true)
public class ProductSpuCreateReqDTO implements Serializable {
/**
* SPU 名字
*/
@NotEmpty(message = "SPU 名字不能为空")
private String name;
/**
* 卖点
*/
@NotEmpty(message = "卖点不能为空")
private String sellPoint;
/**
* 描述
*/
@NotEmpty(message = "描述不能为空")
private String description;
/**
* 分类编号
*/
@NotNull(message = "分类编号不能为空")
private Integer cid;
/**
* 商品主图地址
*/
@NotEmpty(message = "商品主图地址不能为空")
private List<String> picUrls;
/**
* 是否上架商品
*/
@NotNull(message = "是否上架商品不能为空")
private Boolean visible;
/**
* 排序字段
*/
@NotNull(message = "排序字段不能为空")
private Integer sort;
/**
* 价格
*/
@NotNull(message = "价格不能为空")
private Integer price;
/**
* 库存数量
*/
@NotNull(message = "库存数量不能为空")
private Integer quantity;
}

View File

@@ -17,6 +17,14 @@ public class ProductSpuPageReqDTO extends PageParam {
* SPU 名字
*/
private String name;
/**
* 分类编号
*/
private Integer cid;
/**
* 是否可见
*/
private Boolean visible;
/**
* 是否有库存
*/

View File

@@ -0,0 +1,33 @@
package cn.iocoder.mall.productservice.convert.sku;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.spu.ProductSkuDO;
import cn.iocoder.mall.productservice.service.sku.bo.ProductSkuCreateOrUpdateBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface ProductSkuConvert {
ProductSkuConvert INSTANCE = Mappers.getMapper(ProductSkuConvert.class);
List<ProductSkuDO> convertList(List<ProductSkuCreateOrUpdateBO> list);
@Mapping(source = "attrValueIds", target = "attrs", qualifiedByName = "translatePicUrlsFromStringList")
ProductSkuDO convertList(ProductSkuCreateOrUpdateBO bean);
@Named("translateAttrValueIdsFromString")
default List<String> translateAttrValueIdsFromString(String attrValueIdsStar) {
return StringUtils.split(attrValueIdsStar, ",");
}
@Named("translateAttrValueIdsFromList")
default String translateAttrValueIdsFromList(List<Integer> attrValueIds) {
return StringUtils.join(attrValueIds, ",");
}
}

View File

@@ -3,7 +3,8 @@ package cn.iocoder.mall.productservice.convert.spu;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.spu.ProductSpuDO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuCreateReqDTO;
import cn.iocoder.mall.productservice.service.sku.bo.ProductSkuCreateOrUpdateBO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuPageReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuRespDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuUpdateReqDTO;
@@ -38,7 +39,7 @@ public interface ProductSpuConvert {
@Mapping(source = "records", target = "list")
PageResult<ProductSpuBO> convertPage(IPage<ProductSpuDO> page);
ProductSpuCreateBO convert(ProductSpuCreateReqDTO bean);
ProductSpuCreateBO convert(ProductSpuAndSkuCreateReqDTO bean);
ProductSpuUpdateBO convert(ProductSpuUpdateReqDTO bean);
@@ -51,13 +52,15 @@ public interface ProductSpuConvert {
PageResult<ProductSpuRespDTO> convertPage(PageResult<ProductSpuBO> page);
@Named("translatePicUrlsFromString")
default List<String> translatePicUrlsFromString(String picUrls) {
default List<String> translatePicUrlsFromList(String picUrls) {
return StringUtils.split(picUrls, ",");
}
@Named("translatePicUrlsFromStringList")
default String translatePicUrlsFromString(List<String> picUrls) {
default String translatePicUrlsFromList(List<String> picUrls) {
return StringUtils.join(picUrls, ",");
}
List<ProductSkuCreateOrUpdateBO> convert(List<ProductSpuAndSkuCreateReqDTO.Sku> list);
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.mall.productservice.dal.mysql.dataobject.attr;
import cn.iocoder.common.framework.enums.CommonStatusEnum;
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* Product 规格键 DO
*/
@TableName("product_attr_key")
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class ProductAttrKeyDO extends DeletableDO {
/**
* 规格编号
*/
private Integer id;
/**
* 名称
*/
private String name;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
}

View File

@@ -0,0 +1,38 @@
package cn.iocoder.mall.productservice.dal.mysql.dataobject.attr;
import cn.iocoder.common.framework.enums.CommonStatusEnum;
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 商品规格值 DO
*/
@TableName("product_attr_value")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class ProductAttrValueDO extends DeletableDO {
/**
* 规格值编号
*/
private Integer id;
/**
* 规格编号
*/
private Integer attrId;
/**
* 规格值
*/
private String name;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
}

View File

@@ -1,6 +1,8 @@
package cn.iocoder.mall.productservice.dal.mysql.dataobject.spu;
import cn.iocoder.common.framework.enums.CommonStatusEnum;
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.attr.ProductAttrValueDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -29,8 +31,7 @@ public class ProductSkuDO extends DeletableDO {
/**
* 状态
*
* 1-正常
* 2-禁用
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
@@ -38,7 +39,7 @@ public class ProductSkuDO extends DeletableDO {
*/
private String picUrl;
/**
* 规格值({@link ProductAttrDO})数组
* 规格值({@link ProductAttrValueDO#getId()})数组
*
* 数组,以逗号分隔
*/

View File

@@ -0,0 +1,10 @@
package cn.iocoder.mall.productservice.dal.mysql.mapper.attr;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.attr.ProductAttrKeyDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductAttrKeyMapper extends BaseMapper<ProductAttrKeyDO> {
}

View File

@@ -0,0 +1,11 @@
package cn.iocoder.mall.productservice.dal.mysql.mapper.attr;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.attr.ProductAttrValueDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductAttrValueMapper extends BaseMapper<ProductAttrValueDO> {
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.mall.productservice.dal.mysql.mapper.sku;
import cn.iocoder.mall.mybatis.core.query.QueryWrapperX;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.spu.ProductSkuDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductSkuMapper extends BaseMapper<ProductSkuDO> {
default List<ProductSkuDO> selectListBySpuIdAndStatus(Integer spuId, Integer status) {
return selectList(new QueryWrapperX<ProductSkuDO>().eq("spu_id", spuId)
.eq("status", status));
}
void insertList(@Param("productSkuDOs") List<ProductSkuDO> productSkuDOs);
}

View File

@@ -12,7 +12,8 @@ import org.springframework.stereotype.Repository;
public interface ProductSpuMapper extends BaseMapper<ProductSpuDO> {
default IPage<ProductSpuDO> selectPage(ProductSpuPageBO pageBO) {
QueryWrapperX<ProductSpuDO> query = new QueryWrapperX<ProductSpuDO>().likeIfPresent("name", pageBO.getName());
QueryWrapperX<ProductSpuDO> query = new QueryWrapperX<ProductSpuDO>().likeIfPresent("name", pageBO.getName())
.eqIfPresent("cid", pageBO.getCid()).eqIfPresent("visible", pageBO.getVisible());
// 库存过滤
if (pageBO.getHasQuantity() != null) {
if (pageBO.getHasQuantity()) {

View File

@@ -1,17 +1,33 @@
package cn.iocoder.mall.productservice.manager.spu;
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.productservice.convert.spu.ProductSpuConvert;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuCreateReqDTO;
import cn.iocoder.mall.productservice.enums.category.ProductCategoryIdEnum;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuPageReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuRespDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuUpdateReqDTO;
import cn.iocoder.mall.productservice.service.attr.ProductAttrService;
import cn.iocoder.mall.productservice.service.attr.bo.ProductAttrKeyValueBO;
import cn.iocoder.mall.productservice.service.category.ProductCategoryService;
import cn.iocoder.mall.productservice.service.category.bo.ProductCategoryBO;
import cn.iocoder.mall.productservice.service.sku.ProductSkuService;
import cn.iocoder.mall.productservice.service.sku.bo.ProductSkuCreateOrUpdateBO;
import cn.iocoder.mall.productservice.service.spu.ProductSpuService;
import cn.iocoder.mall.productservice.service.spu.bo.ProductSpuBO;
import cn.iocoder.mall.productservice.service.spu.bo.ProductSpuCreateBO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static cn.iocoder.mall.productservice.enums.ProductErrorCodeConstants.PRODUCT_CATEGORY_NOT_EXISTS;
import static cn.iocoder.mall.productservice.enums.ProductErrorCodeConstants.PRODUCT_SPU_CATEGORY_MUST_BE_LEVEL2;
/**
* 商品 SPU Manager
@@ -21,16 +37,46 @@ public class ProductSpuManager {
@Autowired
private ProductSpuService productSpuService;
@Autowired
private ProductSkuService productSkuService;
@Autowired
private ProductCategoryService productCategoryService;
@Autowired
private ProductAttrService productAttrService;
/**
* 创建商品 SPU
* 创建商品 SPU 和 SKU
*
* @param createDTO 创建商品 SPU DTO
* @param createDTO 创建商品 SPU 和 SKU DTO
* @return 商品 SPU
*/
public Integer createProductSpu(ProductSpuCreateReqDTO createDTO) {
ProductSpuBO productSpuBO = productSpuService.createProductSpu(ProductSpuConvert.INSTANCE.convert(createDTO));
return productSpuBO.getId();
@Transactional
public Integer createProductSpu(ProductSpuAndSkuCreateReqDTO createDTO) {
// 校验商品分类分类存在
ProductCategoryBO categoryBO = productCategoryService.getProductCategory(createDTO.getCid());
if (categoryBO == null) {
// 不存在
throw ServiceExceptionUtil.exception(PRODUCT_CATEGORY_NOT_EXISTS);
}
if (ProductCategoryIdEnum.ROOT.getId().equals(categoryBO.getPid())) {
// 商品只能添加到二级分类下
throw ServiceExceptionUtil.exception(PRODUCT_SPU_CATEGORY_MUST_BE_LEVEL2);
}
// 校验规格是否存在
Set<Integer> attrValueIds = new HashSet<>();
createDTO.getSkus().forEach(productSkuAddDTO -> attrValueIds.addAll(productSkuAddDTO.getAttrValueIds()));
List<ProductAttrKeyValueBO> attrKeyValueBOs = productAttrService.validProductAttr(attrValueIds, true);
// 创建商品 SKU 对象,并进行校验
List<ProductSkuCreateOrUpdateBO> skus = ProductSpuConvert.INSTANCE.convert(createDTO.getSkus());
productSkuService.validProductSku(skus, attrKeyValueBOs);
// 插入商品 SPU 记录
ProductSpuCreateBO spuCreateBO = ProductSpuConvert.INSTANCE.convert(createDTO).setSort(0);
spuCreateBO.setPrice(skus.stream().min(Comparator.comparing(ProductSkuCreateOrUpdateBO::getPrice)).get().getPrice()); // 求最小价格
spuCreateBO.setQuantity(skus.stream().mapToInt(ProductSkuCreateOrUpdateBO::getQuantity).sum()); // 求库存之和
ProductSpuBO spuBO = productSpuService.createProductSpu(spuCreateBO);
// 从插入商品 SKU 记录
productSkuService.createProductSkus(spuBO.getId(), skus);
return spuBO.getId();
}
/**
@@ -71,7 +117,8 @@ public class ProductSpuManager {
* @return 商品 SPU分页结果
*/
public PageResult<ProductSpuRespDTO> pageProductSpu(ProductSpuPageReqDTO pageDTO) {
PageResult<ProductSpuBO> pageResultBO = productSpuService.pageProductSpu(ProductSpuConvert.INSTANCE.convert(pageDTO));
PageResult<ProductSpuBO> pageResultBO = productSpuService.pageProductSpu(ProductSpuConvert.INSTANCE.convert(pageDTO));
return ProductSpuConvert.INSTANCE.convertPage(pageResultBO);
}
}

View File

@@ -3,7 +3,7 @@ package cn.iocoder.mall.productservice.rpc.spu;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.productservice.manager.spu.ProductSpuManager;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuPageReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuRespDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuUpdateReqDTO;
@@ -24,7 +24,7 @@ public class ProductSpuRpcImpl implements ProductSpuRpc {
private ProductSpuManager productSpuManager;
@Override
public CommonResult<Integer> createProductSpu(ProductSpuCreateReqDTO createDTO) {
public CommonResult<Integer> createProductSpu(ProductSpuAndSkuCreateReqDTO createDTO) {
return success(productSpuManager.createProductSpu(createDTO));
}

View File

@@ -0,0 +1,64 @@
package cn.iocoder.mall.productservice.service.attr;
import cn.iocoder.common.framework.enums.CommonStatusEnum;
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.util.CollectionUtils;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.attr.ProductAttrKeyDO;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.attr.ProductAttrValueDO;
import cn.iocoder.mall.productservice.dal.mysql.mapper.attr.ProductAttrKeyMapper;
import cn.iocoder.mall.productservice.dal.mysql.mapper.attr.ProductAttrValueMapper;
import cn.iocoder.mall.productservice.service.attr.bo.ProductAttrKeyValueBO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.mall.productservice.enums.ProductErrorCodeConstants.PRODUCT_ATTR_KEY_NOT_EXIST;
import static cn.iocoder.mall.productservice.enums.ProductErrorCodeConstants.PRODUCT_ATTR_VALUE_NOT_EXIST;
@Service
public class ProductAttrService {
@Autowired
private ProductAttrKeyMapper productAttrKeyMapper;
@Autowired
private ProductAttrValueMapper productAttrValueMapper;
public List<ProductAttrKeyValueBO> validProductAttr(Set<Integer> productAttrValueIds, boolean validStatus) {
// 首先,校验规格 Value
List<ProductAttrValueDO> attrValues = productAttrValueMapper.selectBatchIds(productAttrValueIds);
if (attrValues.size() != productAttrValueIds.size()) {
throw ServiceExceptionUtil.exception(PRODUCT_ATTR_VALUE_NOT_EXIST);
}
if (validStatus) {
for (ProductAttrValueDO attrValue : attrValues) { // 同时,校验下状态
if (CommonStatusEnum.DISABLE.getValue().equals(attrValue.getStatus())) {
throw ServiceExceptionUtil.exception(PRODUCT_ATTR_VALUE_NOT_EXIST);
}
}
}
// 然后,校验规 Key
Set<Integer> attrKeyIds = CollectionUtils.convertSet(attrValues, ProductAttrValueDO::getAttrId);
List<ProductAttrKeyDO> attrKeys = productAttrKeyMapper.selectBatchIds(attrKeyIds);
if (attrKeys.size() != attrKeyIds.size()) {
throw ServiceExceptionUtil.exception(PRODUCT_ATTR_KEY_NOT_EXIST);
}
if (validStatus) {
for (ProductAttrKeyDO attrKey : attrKeys) { // 同时,校验下状态
if (CommonStatusEnum.DISABLE.getValue().equals(attrKey.getStatus())) {
throw ServiceExceptionUtil.exception(PRODUCT_ATTR_KEY_NOT_EXIST);
}
}
}
// 返回成功
Map<Integer, ProductAttrKeyDO> attrKeyMap = CollectionUtils.convertMap(attrKeys, ProductAttrKeyDO::getId, attrKeyDO -> attrKeyDO); // ProductAttrDO 的映射,方便查找。
return attrValues.stream().map(attrValueDO -> new ProductAttrKeyValueBO()
.setAttrKeyId(attrValueDO.getAttrId()).setAttrKeyName(attrKeyMap.get(attrValueDO.getAttrId()).getName())
.setAttrValueId(attrValueDO.getId()).setAttrValueName(attrValueDO.getName()))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.mall.productservice.service.attr.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 商品规格键值对 BO
*/
@Data
@Accessors(chain = true)
public class ProductAttrKeyValueBO implements Serializable {
/**
* 规格 Key 编号
*/
private Integer attrKeyId;
/**
* 规格 Key 名字
*/
private String attrKeyName;
/**
* 规格 Value 编号
*/
private Integer attrValueId;
/**
* 规格 Value 名字
*/
private String attrValueName;
}

View File

@@ -0,0 +1,74 @@
package cn.iocoder.mall.productservice.service.sku;
import cn.iocoder.common.framework.enums.CommonStatusEnum;
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
import cn.iocoder.mall.productservice.convert.sku.ProductSkuConvert;
import cn.iocoder.mall.productservice.dal.mysql.dataobject.spu.ProductSkuDO;
import cn.iocoder.mall.productservice.dal.mysql.mapper.sku.ProductSkuMapper;
import cn.iocoder.mall.productservice.service.attr.bo.ProductAttrKeyValueBO;
import cn.iocoder.mall.productservice.service.sku.bo.ProductSkuCreateOrUpdateBO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.mall.productservice.enums.ProductErrorCodeConstants.*;
@Service
public class ProductSkuService {
@Autowired
private ProductSkuMapper productSkuMapper;
public void createProductSkus(Integer spuId, List<ProductSkuCreateOrUpdateBO> createBOs) {
List<ProductSkuDO> skus = ProductSkuConvert.INSTANCE.convertList(createBOs);
skus.forEach(sku -> {
sku.setStatus(CommonStatusEnum.ENABLE.getValue());
sku.setSpuId(spuId);
});
productSkuMapper.insertList(skus);
}
public void updateProductSkus(Integer spuId, List<ProductSkuCreateOrUpdateBO> createBOs) {
}
/**
* 校验 sku 是否合法
*
* @param skuBOs 商品 SKU 添加信息
* @param attrKeyValueBOs 商品规格明细数组
*/
public void validProductSku(List<ProductSkuCreateOrUpdateBO> skuBOs, List<ProductAttrKeyValueBO> attrKeyValueBOs) {
// 创建 ProductAttrDetailBO 的映射。其中KEY 为 ProductAttrDetailBO.attrValueId ,即规格值的编号
Map<Integer, ProductAttrKeyValueBO> productAttrDetailBOMap = attrKeyValueBOs.stream().collect(
Collectors.toMap(ProductAttrKeyValueBO::getAttrValueId, productAttrDetailBO -> productAttrDetailBO));
// 1. 先校验,一个 Sku 下,没有重复的规格。校验方式是,遍历每个 Sku ,看看是否有重复的规格 attrId
for (ProductSkuCreateOrUpdateBO sku : skuBOs) {
Set<Integer> attrIds = sku.getAttrValueIds().stream().map(attrValueId -> productAttrDetailBOMap.get(attrValueId).getAttrKeyId())
.collect(Collectors.toSet());
if (attrIds.size() != sku.getAttrValueIds().size()) {
throw ServiceExceptionUtil.exception(PRODUCT_SKU_ATTR_CANT_NOT_DUPLICATE);
}
}
// 2. 再校验,每个 Sku 的规格值的数量,是一致的。
int attrValueIdsSize = skuBOs.get(0).getAttrValueIds().size();
for (int i = 1; i < skuBOs.size(); i++) {
if (attrValueIdsSize != skuBOs.get(i).getAttrValueIds().size()) {
throw ServiceExceptionUtil.exception(PRODUCT_SPU_ATTR_NUMBERS_MUST_BE_EQUALS);
}
}
// 3. 最后校验,每个 Sku 之间不是重复的
Set<Set<Integer>> skuAttrValues = new HashSet<>(); // 每个元素,都是一个 Sku 的 attrValueId 集合。这样,通过最外层的 Set ,判断是否有重复的.
for (ProductSkuCreateOrUpdateBO sku : skuBOs) {
if (!skuAttrValues.add(new HashSet<>(sku.getAttrValueIds()))) { // 添加失败,说明重复
throw ServiceExceptionUtil.exception(PRODUCT_SPU_SKU_NOT_DUPLICATE);
}
}
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.mall.productservice.service.sku.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 商品 SKU 创建或者修改 BO
*
* 注意,目前该对象是
*/
@Data
@Accessors(chain = true)
public class ProductSkuCreateOrUpdateBO {
/**
* 规格值数组
*/
@NotNull(message = "规格值数组不能为空")
private List<Integer> attrValueIds;
/**
* 价格,单位:分
*/
@NotNull(message = "价格不能为空")
@Min(value = 1L, message = "最小价格为 1")
private Integer price;
/**
* 库存数量
*/
@NotNull(message = "库存数量不能为空")
@Min(value = 1L, message = "最小库存为 1")
private Integer quantity;
}

View File

@@ -17,6 +17,14 @@ public class ProductSpuPageBO extends PageParam {
* SPU 名字
*/
private String name;
/**
* 分类编号
*/
private Integer cid;
/**
* 是否可见
*/
private Boolean visible;
/**
* 是否有库存
*/

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.productservice.dal.mysql.mapper.sku.ProductSkuMapper">
<insert id="insertList" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO product_sku (
spu_id, status, pic_url, attrs, price,
quantity
) VALUES
<foreach collection="productSkuDOs" item="productSkuDO" separator=",">
(#{productSkuDO.spuId}, #{productSkuDO.status}, #{productSkuDO.picUrl}, #{productSkuDO.attrs}, #{productSkuDO.price},
#{productSkuDO.quantity}
)
</foreach>
</insert>
</mapper>