进行商品修改的迁移

This commit is contained in:
YunaiV 2020-07-28 20:10:03 +08:00
parent e107b42f53
commit 75876682fb
28 changed files with 337 additions and 759 deletions

View File

@ -18,6 +18,34 @@ POST {{baseUrl}}/product-spu/create
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{accessToken}}
name=测试商品标题&description=测试商品描述&cid=637&sellPoint=丑&picUrls=1,2,3&visible=true&skus[0].price=1&skus[0].quantity=100&skus[0].attrValueIds=1,3
name=测试商品标题&description=测试商品描述&cid=637&sellPoint=丑&picUrls=1,2,3&visible=true
&skus[0].price=1&skus[0].quantity=100&skus[0].attrValueIds=1,3
&skus[1].price=2&skus[1].quantity=50&skus[1].attrValueIds=2,4
### /product-spu/create 失败(规格不存在)
POST {{baseUrl}}/product-spu/create
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{accessToken}}
name=测试商品标题&description=测试商品描述&cid=637&sellPoint=丑&picUrls=1,2,3&visible=true
&skus[0].price=1&skus[0].quantity=100&skus[0].attrValueIds=1,0
### /product-spu/create 失败(规格数量不匹配)
POST {{baseUrl}}/product-spu/create
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{accessToken}}
name=测试商品标题&description=测试商品描述&cid=637&sellPoint=丑&picUrls=1,2,3&visible=true
&skus[0].price=1&skus[0].quantity=100&skus[0].attrValueIds=1,3
&skus[1].price=2&skus[1].quantity=50&skus[1].attrValueIds=1,2,4
### /product-spu/update 成功
POST {{baseUrl}}/product-spu/update
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{accessToken}}
id=59&name=测试商品标题&description=测试商品描述&cid=637&sellPoint=丑&picUrls=1,2,3&visible=true
&skus[0].price=1&skus[0].quantity=100&skus[0].attrValueIds=1,3
&skus[1].price=2&skus[1].quantity=50&skus[1].attrValueIds=2,4
###

View File

@ -46,14 +46,14 @@ public class ProductSpuController {
@GetMapping("/get")
@ApiOperation("获得商品 SPU")
@ApiImplicitParam(name = "productSpuId", value = "商品 SPU编号", required = true)
@ApiImplicitParam(name = "productSpuId", value = "商品 SPU 编号", required = true)
public CommonResult<ProductSpuRespVO> getProductSpu(@RequestParam("productSpuId") Integer productSpuId) {
return success(productSpuManager.getProductSpu(productSpuId));
}
@GetMapping("/list")
@ApiOperation("获得商品 SPU 列表")
@ApiImplicitParam(name = "productSpuIds", value = "商品 SPU编号列表", required = true)
@ApiImplicitParam(name = "productSpuIds", value = "商品 SPU 编号列表", required = true)
public CommonResult<List<ProductSpuRespVO>> listProductSpus(@RequestParam("productSpuIds") List<Integer> productSpuIds) {
return success(productSpuManager.listProductSpus(productSpuIds));
}

View File

@ -42,22 +42,22 @@ public class ProductSpuCreateReqVO {
}
@ApiModelProperty(value = "SPU 名字", required = true)
@ApiModelProperty(value = "SPU 名字", required = true, example = "芋艿")
@NotEmpty(message = "SPU 名字不能为空")
private String name;
@ApiModelProperty(value = "卖点", required = true)
@ApiModelProperty(value = "卖点", required = true, example = "好吃好玩")
@NotEmpty(message = "卖点不能为空")
private String sellPoint;
@ApiModelProperty(value = "描述", required = true)
@ApiModelProperty(value = "描述", required = true, example = "我是哈哈哈")
@NotEmpty(message = "描述不能为空")
private String description;
@ApiModelProperty(value = "分类编号", required = true)
@ApiModelProperty(value = "分类编号", required = true, example = "1")
@NotNull(message = "分类编号不能为空")
private Integer cid;
@ApiModelProperty(value = "商品主图地址", required = true)
@ApiModelProperty(value = "商品主图地址", required = true, example = "http://www.iocoder.cn/xxx.jpg", notes = "多个之间,使用逗号分隔")
@NotEmpty(message = "商品主图地址不能为空")
private List<String> picUrls;
@ApiModelProperty(value = "是否上架商品", required = true)
@ApiModelProperty(value = "是否上架商品", required = true, example = "true")
@NotNull(message = "是否上架商品不能为空")
private Boolean visible;

View File

@ -10,27 +10,25 @@ public class ProductSpuRespVO {
@ApiModelProperty(value = "SPU 编号", required = true)
private Integer id;
@ApiModelProperty(value = "SPU 名字", required = true)
@ApiModelProperty(value = "SPU 名字", required = true, example = "芋艿")
private String name;
@ApiModelProperty(value = "卖点", required = true)
@ApiModelProperty(value = "卖点", required = true, example = "好吃好玩")
private String sellPoint;
@ApiModelProperty(value = "描述", required = true)
@ApiModelProperty(value = "描述", required = true, example = "我是哈哈哈")
private String description;
@ApiModelProperty(value = "分类编号", required = true)
@ApiModelProperty(value = "分类编号", required = true, example = "1")
private Integer cid;
@ApiModelProperty(value = "商品主图地址", required = true)
@ApiModelProperty(value = "商品主图地址", required = true, example = "http://www.iocoder.cn/xxx.jpg", notes = "多个之间,使用逗号分隔")
private List<String> picUrls;
@ApiModelProperty(value = "是否上架商品", required = true)
@ApiModelProperty(value = "是否上架商品", required = true, example = "true")
private Boolean visible;
@ApiModelProperty(value = "排序字段", required = true)
@ApiModelProperty(value = "排序字段", required = true, example = "1024")
private Integer sort;
@ApiModelProperty(value = "价格", required = true)
@ApiModelProperty(value = "价格", required = true, example = "233", notes = "单位:分")
private Integer price;
@ApiModelProperty(value = "库存数量", required = true)
@ApiModelProperty(value = "库存数量", required = true, example = "1024")
private Integer quantity;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
@ApiModelProperty(value = "最后更新时间", required = true)
private Date updateTime;
}

View File

@ -3,44 +3,75 @@ package cn.iocoder.mall.managementweb.controller.product.vo.spu;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
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.util.List;
@ApiModel("商品 SPU更新 Request VO")
@ApiModel("商品 SPU 更新 Request VO")
@Data
public class ProductSpuUpdateReqVO {
/**
* SKU 信息
*/
@Data
@Accessors(chain = true)
public static class Sku {
/**
* 规格值数组
*/
@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;
}
@ApiModelProperty(value = "SPU 编号", required = true)
@NotNull(message = "SPU 编号不能为空")
private Integer id;
@ApiModelProperty(value = "SPU 名字", required = true)
@ApiModelProperty(value = "SPU 名字", required = true, example = "芋艿")
@NotEmpty(message = "SPU 名字不能为空")
private String name;
@ApiModelProperty(value = "卖点", required = true)
@ApiModelProperty(value = "卖点", required = true, example = "好吃好玩")
@NotEmpty(message = "卖点不能为空")
private String sellPoint;
@ApiModelProperty(value = "描述", required = true)
@ApiModelProperty(value = "描述", required = true, example = "我是哈哈哈")
@NotEmpty(message = "描述不能为空")
private String description;
@ApiModelProperty(value = "分类编号", required = true)
@ApiModelProperty(value = "分类编号", required = true, example = "1")
@NotNull(message = "分类编号不能为空")
private Integer cid;
@ApiModelProperty(value = "商品主图地址", required = true)
@ApiModelProperty(value = "商品主图地址", required = true, example = "http://www.iocoder.cn/xxx.jpg", notes = "多个之间,使用逗号分隔")
@NotEmpty(message = "商品主图地址不能为空")
private List<String> picUrls;
@ApiModelProperty(value = "是否上架商品", required = true)
@ApiModelProperty(value = "是否上架商品", required = true, example = "true")
@NotNull(message = "是否上架商品不能为空")
private Boolean visible;
@ApiModelProperty(value = "排序字段", required = true)
@NotNull(message = "排序字段不能为空")
private Integer sort;
@ApiModelProperty(value = "价格", required = true)
@NotNull(message = "价格不能为空")
private Integer price;
@ApiModelProperty(value = "库存数量", required = true)
@NotNull(message = "库存数量不能为空")
private Integer quantity;
// ========== SKU =========
/**
* SKU 数组
*/
@NotNull(message = "SKU 不能为空")
@Valid
private List<ProductSpuCreateReqVO.Sku> skus;
}

View File

@ -6,9 +6,9 @@ import cn.iocoder.mall.managementweb.controller.product.vo.spu.ProductSpuPageReq
import cn.iocoder.mall.managementweb.controller.product.vo.spu.ProductSpuRespVO;
import cn.iocoder.mall.managementweb.controller.product.vo.spu.ProductSpuUpdateReqVO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuUpdateReqDTO;
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 org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@ -21,7 +21,7 @@ public interface ProductSpuConvert {
ProductSpuAndSkuCreateReqDTO convert(ProductSpuCreateReqVO bean);
ProductSpuUpdateReqDTO convert(ProductSpuUpdateReqVO bean);
ProductSpuAndSkuUpdateReqDTO convert(ProductSpuUpdateReqVO bean);
ProductSpuRespVO convert(ProductSpuRespDTO bean);

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>order</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-application</artifactId>
<dependencies>
<!-- Mall 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>order-rest</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>order-rpc</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -1,51 +0,0 @@
package cn.iocoder.mall.order.application;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
/**
* @author xiaofeng
*/
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.order"})
public class OrderApplication {
/**
* 设置需要读取的配置文件的名字 基于 {@link org.springframework.boot.context.config.ConfigFileApplicationListener#CONFIG_NAME_PROPERTY}
* 实现
*/
private static final String CONFIG_NAME_VALUE = "biz,rest,rpc,application";
public static void main(String[] args) {
// 设置环境变量
System.setProperty(ConfigFileApplicationListener.CONFIG_NAME_PROPERTY, CONFIG_NAME_VALUE);
SpringApplication.run(OrderApplication.class, args);
}
/**
* 解决异常信息 java.lang.IllegalArgumentException: Invalid character found in the request target.
* The valid characters are defined in RFC 7230 and RFC 3986
*
* @return
*/
@Bean
// TODO FROM 芋艿 to 芋艿这个不太了解可能后续要放到 starter 统一处理
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
connector.setProperty("relaxedQueryChars", "|{}[]");
}
});
return factory;
}
}

View File

@ -1,7 +0,0 @@
spring:
# Application 的配置项
application:
name: order-application
# Profile 的配置项
profiles:
active: local

View File

@ -3,9 +3,9 @@ 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.ProductSpuAndSkuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuUpdateReqDTO;
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 java.util.List;
@ -27,7 +27,7 @@ public interface ProductSpuRpc {
*
* @param updateDTO 更新商品 SPU DTO
*/
CommonResult<Boolean> updateProductSpu(ProductSpuUpdateReqDTO updateDTO);
CommonResult<Boolean> updateProductSpu(ProductSpuAndSkuUpdateReqDTO updateDTO);
/**
* 获得商品 SPU

View File

@ -0,0 +1,96 @@
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 ProductSpuAndSkuUpdateReqDTO 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 编号
*/
@NotNull(message = "SPU 编号不能为空")
private Integer id;
// ========== 基本信息 =========
/**
* 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,69 +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 ProductSpuUpdateReqDTO implements Serializable {
/**
* 商品 SPU 编号
*/
@NotNull(message = "商品 SPU 编号不能为空")
private Integer id;
/**
* 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

@ -18,7 +18,7 @@ public interface ProductSkuConvert {
List<ProductSkuDO> convertList(List<ProductSkuCreateOrUpdateBO> list);
@Mapping(source = "attrValueIds", target = "attrs", qualifiedByName = "translatePicUrlsFromStringList")
ProductSkuDO convertList(ProductSkuCreateOrUpdateBO bean);
ProductSkuDO convert(ProductSkuCreateOrUpdateBO skuUpdateDTO);
@Named("translateAttrValueIdsFromString")
default List<String> translateAttrValueIdsFromString(String attrValueIdsStar) {

View File

@ -3,11 +3,11 @@ 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.service.sku.bo.ProductSkuCreateOrUpdateBO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuUpdateReqDTO;
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.sku.bo.ProductSkuCreateOrUpdateBO;
import cn.iocoder.mall.productservice.service.spu.bo.ProductSpuBO;
import cn.iocoder.mall.productservice.service.spu.bo.ProductSpuCreateBO;
import cn.iocoder.mall.productservice.service.spu.bo.ProductSpuPageBO;
@ -41,7 +41,7 @@ public interface ProductSpuConvert {
ProductSpuCreateBO convert(ProductSpuAndSkuCreateReqDTO bean);
ProductSpuUpdateBO convert(ProductSpuUpdateReqDTO bean);
ProductSpuUpdateBO convert(ProductSpuAndSkuUpdateReqDTO bean);
ProductSpuRespDTO convert(ProductSpuBO bean);
@ -51,6 +51,9 @@ public interface ProductSpuConvert {
PageResult<ProductSpuRespDTO> convertPage(PageResult<ProductSpuBO> page);
List<ProductSkuCreateOrUpdateBO> convert(List<ProductSpuAndSkuCreateReqDTO.Sku> list);
List<ProductSkuCreateOrUpdateBO> convert02(List<ProductSpuAndSkuUpdateReqDTO.Sku> list);
@Named("translatePicUrlsFromString")
default List<String> translatePicUrlsFromList(String picUrls) {
return StringUtils.split(picUrls, ",");
@ -61,6 +64,5 @@ public interface ProductSpuConvert {
return StringUtils.join(picUrls, ",");
}
List<ProductSkuCreateOrUpdateBO> convert(List<ProductSpuAndSkuCreateReqDTO.Sku> list);
}

View File

@ -5,9 +5,9 @@ import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.productservice.convert.spu.ProductSpuConvert;
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.ProductSpuAndSkuUpdateReqDTO;
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;
@ -17,17 +17,15 @@ 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 cn.iocoder.mall.productservice.service.spu.bo.ProductSpuUpdateBO;
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 java.util.*;
import java.util.stream.Collectors;
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;
import static cn.iocoder.mall.productservice.enums.ProductErrorCodeConstants.*;
/**
* 商品 SPU Manager
@ -52,30 +50,18 @@ public class ProductSpuManager {
*/
@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);
// 校验商品分类是否合法
this.checkProductCategory(createDTO.getCid());
// 创建商品 SKU 对象并进行校验
List<ProductSkuCreateOrUpdateBO> skus = ProductSpuConvert.INSTANCE.convert(createDTO.getSkus());
productSkuService.validProductSku(skus, attrKeyValueBOs);
List<ProductSkuCreateOrUpdateBO> skuBOs = ProductSpuConvert.INSTANCE.convert(createDTO.getSkus());
this.checkProductAttr(skuBOs);
// 插入商品 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()); // 求库存之和
spuCreateBO.setPrice(skuBOs.stream().min(Comparator.comparing(ProductSkuCreateOrUpdateBO::getPrice)).get().getPrice()); // 求最小价格
spuCreateBO.setQuantity(skuBOs.stream().mapToInt(ProductSkuCreateOrUpdateBO::getQuantity).sum()); // 求库存之和
ProductSpuBO spuBO = productSpuService.createProductSpu(spuCreateBO);
// 插入商品 SKU 记录
productSkuService.createProductSkus(spuBO.getId(), skus);
// 插入商品 SKU 记录
productSkuService.createProductSkus(spuBO.getId(), skuBOs);
return spuBO.getId();
}
@ -84,8 +70,19 @@ public class ProductSpuManager {
*
* @param updateDTO 更新商品 SPU DTO
*/
public void updateProductSpu(ProductSpuUpdateReqDTO updateDTO) {
productSpuService.updateProductSpu(ProductSpuConvert.INSTANCE.convert(updateDTO));
public void updateProductSpu(ProductSpuAndSkuUpdateReqDTO updateDTO) {
// 校验商品分类是否合法
this.checkProductCategory(updateDTO.getCid());
// 创建商品 SKU 对象并进行校验
List<ProductSkuCreateOrUpdateBO> skuBOs = ProductSpuConvert.INSTANCE.convert02(updateDTO.getSkus());
this.checkProductAttr(skuBOs);
// 更新商品 SPU 记录
ProductSpuUpdateBO spuUpdateBO = ProductSpuConvert.INSTANCE.convert(updateDTO);
spuUpdateBO.setPrice(skuBOs.stream().min(Comparator.comparing(ProductSkuCreateOrUpdateBO::getPrice)).get().getPrice()); // 求最小价格
spuUpdateBO.setQuantity(skuBOs.stream().mapToInt(ProductSkuCreateOrUpdateBO::getQuantity).sum()); // 求库存之和
productSpuService.updateProductSpu(spuUpdateBO);
// 更新商品 SKU 记录
productSkuService.updateProductSkus(updateDTO.getId(), skuBOs);
}
/**
@ -121,4 +118,57 @@ public class ProductSpuManager {
return ProductSpuConvert.INSTANCE.convertPage(pageResultBO);
}
/**
* 添加或修改商品 SPU 校验商品分类是否合法
*
* @param cid 商品分类编号
* @return 商品分类
*/
private ProductCategoryBO checkProductCategory(Integer cid) {
ProductCategoryBO categoryBO = productCategoryService.getProductCategory(cid);
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);
}
return categoryBO;
}
private List<ProductAttrKeyValueBO> checkProductAttr(List<ProductSkuCreateOrUpdateBO> skuBOs) {
// 第一步校验 SKU 使用到的规格是否存在
Set<Integer> attrValueIds = new HashSet<>();
skuBOs.forEach(sku -> attrValueIds.addAll(sku.getAttrValueIds()));
List<ProductAttrKeyValueBO> attrKeyValueBOs = productAttrService.validProductAttr(attrValueIds, true);
// 第二步校验 SKU 设置的规格是否合法例如说数量是否一致是否重复等等
// 创建 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);
}
}
return attrKeyValueBOs;
}
}

View File

@ -4,9 +4,9 @@ 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.ProductSpuAndSkuCreateReqDTO;
import cn.iocoder.mall.productservice.rpc.spu.dto.ProductSpuAndSkuUpdateReqDTO;
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 org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
@ -29,7 +29,7 @@ public class ProductSpuRpcImpl implements ProductSpuRpc {
}
@Override
public CommonResult<Boolean> updateProductSpu(ProductSpuUpdateReqDTO updateDTO) {
public CommonResult<Boolean> updateProductSpu(ProductSpuAndSkuUpdateReqDTO updateDTO) {
productSpuManager.updateProductSpu(updateDTO);
return success(true);
}

View File

@ -1,31 +1,26 @@
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.common.framework.util.CollectionUtils;
import cn.iocoder.common.framework.util.StringUtils;
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.*;
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);
public void createProductSkus(Integer spuId, List<ProductSkuCreateOrUpdateBO> createSkuBOs) {
List<ProductSkuDO> skus = ProductSkuConvert.INSTANCE.convertList(createSkuBOs);
skus.forEach(sku -> {
sku.setStatus(CommonStatusEnum.ENABLE.getValue());
sku.setSpuId(spuId);
@ -33,42 +28,64 @@ public class ProductSkuService {
productSkuMapper.insertList(skus);
}
public void updateProductSkus(Integer spuId, List<ProductSkuCreateOrUpdateBO> createBOs) {
public void updateProductSkus(Integer spuId, List<ProductSkuCreateOrUpdateBO> skuUpdateBOs) {
List<ProductSkuDO> existsSkus = productSkuMapper.selectListBySpuIdAndStatus(spuId,
CommonStatusEnum.ENABLE.getValue());
List<ProductSkuDO> insertSkus = new ArrayList<>(); // 1找不到进行插入
List<Integer> deleteSkus = new ArrayList<>(); // 2多余的删除
List<ProductSkuDO> updateSkus = new ArrayList<>(); // 3找的到进行更新
for (ProductSkuCreateOrUpdateBO skuUpdateDTO : skuUpdateBOs) {
ProductSkuDO existsSku = findProductSku(skuUpdateDTO.getAttrValueIds(), existsSkus);
// 3找的到进行更新
if (existsSku != null) {
// 移除
existsSkus.remove(existsSku);
// 创建 ProductSkuDO
updateSkus.add(ProductSkuConvert.INSTANCE.convert(skuUpdateDTO).setId(existsSku.getId()));
continue;
}
// 1找不到进行插入
ProductSkuDO insertSku = ProductSkuConvert.INSTANCE.convert(skuUpdateDTO)
.setSpuId(spuId).setStatus(CommonStatusEnum.ENABLE.getValue());
insertSkus.add(insertSku);
}
// 2多余的删除
if (!existsSkus.isEmpty()) {
deleteSkus.addAll(existsSkus.stream().map(ProductSkuDO::getId).collect(Collectors.toList()));
}
// 执行修改 Sku
if (!insertSkus.isEmpty()) {
productSkuMapper.insertList(insertSkus);
}
if (!updateSkus.isEmpty()) {
updateSkus.forEach(productSkuDO -> productSkuMapper.updateById(productSkuDO));
}
if (!deleteSkus.isEmpty()) {
productSkuMapper.deleteBatchIds(deleteSkus);
}
}
/**
* 校验 sku 是否合法
* 获得 sku 数组中指定规格的 sku
*
* @param skuBOs 商品 SKU 添加信息
* @param attrKeyValueBOs 商品规格明细数组
* @param attrValueIds 指定规格 Value 的编号数组
* @param skus sku 数组
* @return 符合条件的 sku
*/
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);
private ProductSkuDO findProductSku(Collection<Integer> attrValueIds, List<ProductSkuDO> skus) {
if (CollectionUtils.isEmpty(skus)) {
return null;
}
// 创建成 Set 方便后面比较
attrValueIds = new HashSet<>(attrValueIds);
for (ProductSkuDO sku : skus) {
Set<Integer> skuAttrValueIds = StringUtils.split(sku.getAttrs(), ",")
.stream().map(Integer::parseInt).collect(Collectors.toSet());
if (attrValueIds.equals(skuAttrValueIds)) {
return sku;
}
}
return null;
}
}

View File

@ -52,7 +52,6 @@ public class ProductSpuUpdateBO {
/**
* 排序字段
*/
@NotNull(message = "排序字段不能为空")
private Integer sort;
/**
* 价格

View File

@ -1,42 +0,0 @@
package cn.iocoder.mall.product.biz.bo.brand;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 商品品牌 VO
*/
@Data
@Accessors(chain = true)
public class ProductBrandBO implements Serializable {
/**
* 规格编号
*/
private Integer id;
/**
* 名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 图片地址
*/
private String picUrl;
/**
* 状态
*
* 1-开启
* 2-禁用
*/
private Integer status;
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.mall.product.biz.dao.sku;
import cn.iocoder.mall.product.biz.dataobject.spu.ProductSkuDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
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(Wrappers.<ProductSkuDO>query().lambda()
.eq(ProductSkuDO::getSpuId, spuId)
.eq(ProductSkuDO::getStatus, status)
.eq(ProductSkuDO::getDeleted, false));
}
void insertList(@Param("productSkuDOs") List<ProductSkuDO> productSkuDOs);
}

View File

@ -1,5 +0,0 @@
/**
* author: sin
* time: 2020/5/3 8:29 下午
*/
package cn.iocoder.mall.product.biz.dto;

View File

@ -1,41 +0,0 @@
package cn.iocoder.mall.product.biz.dto.product;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* 商品 Spu 分页 DTO
*/
@Data
@Accessors(chain = true)
public class ProductSpuPageDTO {
/**
* 商品名
*
* 模糊匹配
*/
private String name;
/**
* 分类编号
*/
private Integer cid;
/**
* 是否可见
*/
private Boolean visible;
/**
* 是否有库存
*
* 允许为空空时不进行筛选
*/
private Boolean hasQuantity;
@NotNull(message = "页码不能为空")
private Integer pageNo;
@NotNull(message = "每页条数不能为空")
private Integer pageSize;
}

View File

@ -1,68 +0,0 @@
package cn.iocoder.mall.product.biz.dto.product;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 商品 SPU + SKU 更新 DTO
*/
@Data
@Accessors(chain = true)
public class ProductSpuUpdateDTO {
/**
* Spu 编号
*/
@NotNull(message = "SPU 编号不能为空")
private Integer id;
// ========== 基本信息 =========
/**
* SPU 名字
*/
@NotEmpty(message = "SPU 名字不能为空")
private String name;
/**
* 卖点
*/
@NotEmpty(message = "卖点不能为空")
private String sellPoint;
/**
* 描述
*/
@NotEmpty(message = "描述不能为空")
private String description;
/**
* 分类编号
*/
@NotNull(message = "分类不能为空")
private Integer cid;
/**
* 商品主图地址
*/
@NotNull(message = "商品主图不能为空")
private List<String> picUrls;
// ========== 其他信息 =========
/**
* 是否上架商品是否可见
*
* true 为已上架
* false 为已下架
*/
@NotNull(message = "是否上架不能为空")
private Boolean visible;
// ========== SKU =========
/**
* SKU 数组
*/
@NotNull(message = "SKU 不能为空")
private List<ProductSkuAddOrUpdateDTO> skus;
}

View File

@ -1,35 +0,0 @@
package cn.iocoder.mall.product.biz.dto.product;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* 更新商品收藏参数
* @author xiaofeng
* @date 2019/07/01 20:38
* @version 1.0
*/
@Data
@Accessors(chain = true)
public class UserProductSpuCollectionsUpdateDTO implements Serializable {
/**
* id自增长
*/
private Integer id;
/**
* 更新时间
*/
private Date updateTime;
/**
* 删除状态
*/
private Integer deleted;
}

View File

@ -1,93 +0,0 @@
package cn.iocoder.mall.product.rest.controller.brand;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.common.framework.vo.PageResult;
import cn.iocoder.mall.product.biz.bo.brand.ProductBrandBO;
import cn.iocoder.mall.product.biz.dto.brand.ProductBrandAddDTO;
import cn.iocoder.mall.product.biz.dto.brand.ProductBrandPageDTO;
import cn.iocoder.mall.product.biz.dto.brand.ProductBrandUpdateDTO;
import cn.iocoder.mall.product.biz.service.brand.ProductBrandService;
import cn.iocoder.mall.product.rest.convert.brand.ProductBrandConvert;
import cn.iocoder.mall.product.rest.request.brand.ProductBrandAddRequest;
import cn.iocoder.mall.product.rest.request.brand.ProductBrandPageRequest;
import cn.iocoder.mall.product.rest.request.brand.ProductBrandUpdateRequest;
import cn.iocoder.mall.product.rest.response.brand.AdminsProductBrandResponse;
import cn.iocoder.mall.security.core.context.AdminSecurityContextHolder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.common.framework.vo.CommonResult.success;
@RestController
@RequestMapping("admins/brand")
@Api("管理员 - 商品品牌 API")
@AllArgsConstructor
public class AdminsProductBrandController {
private final ProductBrandService productBrandService;
@PostMapping("/add")
@ApiOperation("创建品牌")
public CommonResult<AdminsProductBrandResponse> add(@Validated ProductBrandAddRequest addRequest) {
// 创建 ProductBrandAddDTO 对象
ProductBrandAddDTO productBrandAddDTO = ProductBrandConvert.INSTANCE.convertAdd(addRequest);
// 保存品牌
ProductBrandBO result = productBrandService.addProductBrand(AdminSecurityContextHolder.getContext().getAdminId(), productBrandAddDTO);
// 返回结果
return success(ProductBrandConvert.INSTANCE.convert(result));
}
@PostMapping("/update")
@ApiOperation("更新商品")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "品牌主键", required = true, example = "1"),
@ApiImplicitParam(name = "name", value = "品牌名称", required = true, example = "安踏"),
@ApiImplicitParam(name = "description", value = "品牌描述", required = true, example = "安踏拖鞋"),
@ApiImplicitParam(name = "picUrl", value = "品牌图片", required = true, example = "http://www.iocoder.cn"),
@ApiImplicitParam(name = "status", value = "状态 1开启 2禁用", required = true, example = "1")
})
// TODO FROM 芋艿 to q2118cs只要改成了 bean 接收就不用在写 @ApiImplicitParam 注解啦直接在 bean 里写就 ok
public CommonResult<Boolean> update(@Validated ProductBrandUpdateRequest updateRequest) {
// 创建 productBrandUpdateDTO 对象
ProductBrandUpdateDTO productBrandUpdateDTO = ProductBrandConvert.INSTANCE.convertUpdate(updateRequest);
// 更新商品
return success(productBrandService.updateProductBrand(AdminSecurityContextHolder.getContext().getAdminId(), productBrandUpdateDTO));
}
@GetMapping("/get")
@ApiOperation("获取品牌")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "品牌主键", required = true, example = "1")
})
public CommonResult<AdminsProductBrandResponse> add(@RequestParam("id") Integer id) {
// 保存商品
ProductBrandBO result = productBrandService.getProductBrand(id);
// 返回结果
return success(ProductBrandConvert.INSTANCE.convert(result));
}
@GetMapping("/page")
@ApiOperation("获得品牌分页")
@ApiImplicitParams({
@ApiImplicitParam(name = "name", value = "品牌名称", required = true, example = "安踏"),
@ApiImplicitParam(name = "description", value = "品牌描述", required = true, example = "安踏拖鞋"),
@ApiImplicitParam(name = "status", value = "状态 1开启 2禁用", required = true, example = "1"),
@ApiImplicitParam(name = "pageNo", value = "页码", required = true, example = "1"),
@ApiImplicitParam(name = "pageSize", value = "页面大小", required = true, example = "10")
})
public CommonResult<PageResult<AdminsProductBrandResponse>> attrPage(ProductBrandPageRequest pageRequest) {
// 创建 ProductBrandPageDTO 对象
ProductBrandPageDTO productBrandPageDTO = ProductBrandConvert.INSTANCE.convertPageRequest(pageRequest);
// 查询分页
PageResult<ProductBrandBO> productBrandPage = productBrandService.getProductBrandPage(productBrandPageDTO);
PageResult<AdminsProductBrandResponse> adminPageResponse = ProductBrandConvert.INSTANCE.convertPage(productBrandPage);
return CommonResult.success(adminPageResponse);
}
}

View File

@ -30,10 +30,6 @@ public interface ProductSpuService {
List<ProductSkuDetailBO> getProductSkuDetailList(Collection<Integer> ids);
ProductSpuDetailBO addProductSpu(Integer adminId, ProductSpuAddDTO productSpuAddDTO);
void updateProductSpu(Integer adminId, ProductSpuUpdateDTO productSpuUpdateDTO);
Boolean updateProductSpuSort(Integer adminId, Integer spuId, Integer sort);
}

View File

@ -1,68 +0,0 @@
package cn.iocoder.mall.product.api.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 商品 SPU + SKU 更新 DTO
*/
@Data
@Accessors(chain = true)
public class ProductSpuUpdateDTO {
/**
* Spu 编号
*/
@NotNull(message = "SPU 编号不能为空")
private Integer id;
// ========== 基本信息 =========
/**
* SPU 名字
*/
@NotEmpty(message = "SPU 名字不能为空")
private String name;
/**
* 卖点
*/
@NotEmpty(message = "卖点不能为空")
private String sellPoint;
/**
* 描述
*/
@NotEmpty(message = "描述不能为空")
private String description;
/**
* 分类编号
*/
@NotNull(message = "分类不能为空")
private Integer cid;
/**
* 商品主图地址
*/
@NotNull(message = "商品主图不能为空")
private List<String> picUrls;
// ========== 其他信息 =========
/**
* 是否上架商品是否可见
*
* true 为已上架
* false 为已下架
*/
@NotNull(message = "是否上架不能为空")
private Boolean visible;
// ========== SKU =========
/**
* SKU 数组
*/
@NotNull(message = "SKU 不能为空")
private List<ProductSkuAddOrUpdateDTO> skus;
}

View File

@ -88,7 +88,6 @@ public class ProductSpuServiceImpl implements ProductSpuService {
@Override
public ProductSpuDetailBO addProductSpu(Integer adminId, ProductSpuAddDTO productSpuAddDTO) {
ProductSpuDetailBO productSpuDetailBO = addProductSpu0(adminId, productSpuAddDTO);
// 如果新增生成发送创建商品 Topic 消息
// TODO 芋艿先不考虑事务的问题等后面的 fescar 一起搞
sendProductUpdateMessage(productSpuDetailBO.getId());
@ -96,76 +95,6 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return productSpuDetailBO;
}
@Override
public void updateProductSpu(Integer adminId, ProductSpuUpdateDTO productSpuUpdateDTO) {
// 更新商品
updateProductSpu0(adminId, productSpuUpdateDTO);
// TODO 芋艿先不考虑事务的问题等后面的 fescar 一起搞
sendProductUpdateMessage(productSpuUpdateDTO.getId());
}
@SuppressWarnings("Duplicates")
@Transactional
public void updateProductSpu0(Integer adminId, ProductSpuUpdateDTO productSpuUpdateDTO) {
// 校验 Spu 是否存在
if (productSpuMapper.selectById(productSpuUpdateDTO.getId()) == null) {
throw ServiceExceptionUtil.exception(ProductErrorCodeEnum.PRODUCT_SPU_NOT_EXISTS.getCode());
}
// 校验商品分类分类存在
ProductCategoryDO category = productCategoryService.validProductCategory(productSpuUpdateDTO.getCid());
if (ProductCategoryConstants.PID_ROOT.equals(category.getPid())) { // 商品只能添加到二级分类下
throw ServiceExceptionUtil.exception(ProductErrorCodeEnum.PRODUCT_SPU_CATEGORY_MUST_BE_LEVEL2.getCode());
}
// 校验规格是否存在
Set<Integer> productAttrValueIds = new HashSet<>();
productSpuUpdateDTO.getSkus().forEach(productSkuAddDTO -> productAttrValueIds.addAll(productSkuAddDTO.getAttrs()));
List<ProductAttrAndValuePairBO> attrAndValuePairList = productAttrService.validProductAttrAndValue(productAttrValueIds,
true); // 读取规格时需要考虑规格是否被禁用
// 校验 Sku 规格
validProductSku(productSpuUpdateDTO.getSkus(), attrAndValuePairList);
// 更新 Spu
ProductSpuDO updateSpu = ProductSpuConvert.INSTANCE.convert(productSpuUpdateDTO)
.setPicUrls(StringUtil.join(productSpuUpdateDTO.getPicUrls(), ","));
initSpuFromSkus(updateSpu, productSpuUpdateDTO.getSkus()); // 初始化 sku 相关信息到 spu
productSpuMapper.update(updateSpu);
// 修改 Sku
List<ProductSkuDO> existsSkus = productSkuMapper.selectListBySpuIdAndStatus(productSpuUpdateDTO.getId(), ProductSpuConstants.SKU_STATUS_ENABLE);
List<ProductSkuDO> insertSkus = new ArrayList<>(0); // 1找不到进行插入
List<Integer> deleteSkus = new ArrayList<>(0); // 2多余的删除
List<ProductSkuDO> updateSkus = new ArrayList<>(0); // 3找的到进行更新
for (ProductSkuAddOrUpdateDTO skuUpdateDTO : productSpuUpdateDTO.getSkus()) {
ProductSkuDO existsSku = findProductSku(skuUpdateDTO.getAttrs(), existsSkus);
// 3找的到进行更新
if (existsSku != null) {
// 移除
existsSkus.remove(existsSku);
// 创建 ProductSkuDO
updateSkus.add(ProductSpuConvert.INSTANCE.convert(skuUpdateDTO).setId(existsSku.getId()));
continue;
}
// 1找不到进行插入
ProductSkuDO insertSku = ProductSpuConvert.INSTANCE.convert(skuUpdateDTO)
.setSpuId(productSpuUpdateDTO.getId()).setStatus(ProductSpuConstants.SKU_STATUS_ENABLE).setAttrs(StringUtil.join(skuUpdateDTO.getAttrs(), ","));
insertSku.setCreateTime(new Date());
insertSku.setDeleted(DeletedStatusEnum.DELETED_NO.getValue());
insertSkus.add(insertSku);
}
// 2多余的删除
if (!existsSkus.isEmpty()) {
deleteSkus.addAll(existsSkus.stream().map(ProductSkuDO::getId).collect(Collectors.toList()));
}
// 执行修改 Sku
if (!insertSkus.isEmpty()) {
productSkuMapper.insertList(insertSkus);
}
if (!updateSkus.isEmpty()) {
updateSkus.forEach(productSkuDO -> productSkuMapper.update(productSkuDO));
}
if (!deleteSkus.isEmpty()) {
productSkuMapper.updateToDeleted(deleteSkus);
}
}
@Override
public Boolean updateProductSpuSort(Integer adminId, Integer spuId, Integer sort) {
// 校验 Spu 是否存在
@ -181,21 +110,6 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return true;
}
@Override
public ProductSpuPageBO getProductSpuPage(ProductSpuPageDTO productSpuPageDTO) {
ProductSpuPageBO productSpuPage = new ProductSpuPageBO();
// 查询分页数据
int offset = (productSpuPageDTO.getPageNo() - 1) * productSpuPageDTO.getPageSize();
productSpuPage.setList(ProductSpuConvert.INSTANCE.convert(productSpuMapper.selectListByNameLikeOrderBySortAsc(
productSpuPageDTO.getName(), productSpuPageDTO.getCid(), productSpuPageDTO.getHasQuantity(), productSpuPageDTO.getVisible(),
offset, productSpuPageDTO.getPageSize())));
// 查询分页总数
productSpuPage.setTotal(productSpuMapper.selectCountByNameLike(productSpuPageDTO.getName(), productSpuPageDTO.getCid(), productSpuPageDTO.getHasQuantity(),
productSpuPageDTO.getVisible()));
// 返回结果
return productSpuPage;
}
@Override
public List<ProductSpuBO> getProductSpuSearchList(ProductSpuSearchListDTO productSpuSearchListDTO) {
return ProductSpuConvert.INSTANCE.convert(
@ -237,28 +151,6 @@ public class ProductSpuServiceImpl implements ProductSpuService {
return ProductSpuConvert.INSTANCE.convert3(skus, spus, attrAndValuePairList);
}
/**
* 获得 sku 数组中指定规格的 sku
*
* @param attrs 指定规格
* @param skus sku 数组
* @return 符合条件的 sku
*/
private ProductSkuDO findProductSku(Collection<Integer> attrs, List<ProductSkuDO> skus) {
if (CollectionUtil.isEmpty(skus)) {
return null;
}
// 创建成 Set 方便后面比较
attrs = new HashSet<>(attrs);
for (ProductSkuDO sku : skus) {
Set<Integer> skuAttrs = StringUtil.split(sku.getAttrs(), ",").stream().map(Integer::parseInt).collect(Collectors.toSet());
if (attrs.equals(skuAttrs)) {
return sku;
}
}
return null;
}
private boolean sendProductUpdateMessage(Integer id) {
// 创建 Message 对象
ProductUpdateMessage message = new ProductUpdateMessage().setId(id);