Commit 16f5d0f5a4f3d2d0cc6da028271ecceb85b1af7f

Authored by YunaiV
1 parent 734236df

trade:增加优惠劵使用、商品库存的扣减

Showing 20 changed files with 424 additions and 129 deletions
yudao-module-mall/yudao-module-product-api/pom.xml
... ... @@ -22,6 +22,13 @@
22 22 <groupId>cn.iocoder.boot</groupId>
23 23 <artifactId>yudao-common</artifactId>
24 24 </dependency>
  25 +
  26 + <!-- 参数校验 -->
  27 + <dependency>
  28 + <groupId>org.springframework.boot</groupId>
  29 + <artifactId>spring-boot-starter-validation</artifactId>
  30 + <optional>true</optional>
  31 + </dependency>
25 32 </dependencies>
26 33  
27   -</project>
28 34 \ No newline at end of file
  35 +</project>
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
1 1 package cn.iocoder.yudao.module.product.api.sku;
2 2  
3   -import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
  3 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
4 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
5 5  
6 6 import java.util.Collection;
... ... @@ -31,11 +31,10 @@ public interface ProductSkuApi {
31 31 List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
32 32  
33 33 /**
34   - * 批量扣减 SKU 库存
  34 + * 更新 SKU 库存
35 35 *
36   - * @param batchReqDTO sku库存信息列表
  36 + * @param updateStockReqDTO 更新请求
37 37 */
38   - // TODO @LeeYan9: decrementSkuStockBatch? 啊哈, 动名词;
39   - void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO);
  38 + void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
40 39  
41 40 }
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/SkuDecrementStockBatchReqDTO.java renamed to yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuUpdateStockReqDTO.java
... ... @@ -4,45 +4,44 @@ import lombok.AllArgsConstructor;
4 4 import lombok.Data;
5 5 import lombok.NoArgsConstructor;
6 6  
  7 +import javax.validation.constraints.NotNull;
7 8 import java.util.List;
8 9  
9 10 /**
10   - * TODO @LeeYan9: 1) 类注释; 2) Product 开头哈;
  11 + * 商品 SKU 更新库存 Request DTO
  12 + *
11 13 * @author LeeYan9
12 14 * @since 2022-08-26
13 15 */
14 16 @Data
15 17 @NoArgsConstructor
16 18 @AllArgsConstructor
17   -public class SkuDecrementStockBatchReqDTO {
  19 +public class ProductSkuUpdateStockReqDTO {
18 20  
19   - // TODO @LeeYan9: 参数校验
  21 + /**
  22 + * 商品 SKU
  23 + */
  24 + @NotNull(message = "商品 SKU 不能为空")
20 25 private List<Item> items;
21 26  
22 27 @Data
23 28 public static class Item {
24 29  
25 30 /**
26   - * 商品 SPU 编号,自增
  31 + * 商品 SKU 编号
27 32 */
28   - // TODO @LeeYan9: 是不是不用传递哈
29   - private Long productId;
  33 + @NotNull(message = "商品 SKU 编号不能为空")
  34 + private Long id;
30 35  
31 36 /**
32   - * 商品 SKU 编号,自增
  37 + * 库存变化数量
  38 + *
  39 + * 正数:增加库存
  40 + * 负数:扣减库存
33 41 */
34   - private Long skuId;
  42 + @NotNull(message = "库存变化数量不能为空")
  43 + private Integer incCount;
35 44  
36   - /**
37   - * 数量
38   - */
39   - private Integer count;
40   -
41   - }
42   -
43   - // TODO @LeeYan9: 构造方法, 是不是可以满足啦
44   - public static SkuDecrementStockBatchReqDTO of(List<Item> items) {
45   - return new SkuDecrementStockBatchReqDTO(items);
46 45 }
47 46  
48 47 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
1 1 package cn.iocoder.yudao.module.product.api.sku;
2 2  
3   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  3 +import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
5   -import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
  5 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
6 6 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
7 7 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
8   -import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
  8 +import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
9 9 import org.springframework.stereotype.Service;
10   -import org.springframework.transaction.annotation.Transactional;
11 10 import org.springframework.validation.annotation.Validated;
12 11  
13 12 import javax.annotation.Resource;
... ... @@ -25,7 +24,7 @@ import java.util.List;
25 24 public class ProductSkuApiImpl implements ProductSkuApi {
26 25  
27 26 @Resource
28   - private ProductSkuMapper productSkuMapper;
  27 + private ProductSkuService productSkuService;
29 28  
30 29 @Override
31 30 public ProductSkuRespDTO getSku(Long id) {
... ... @@ -35,18 +34,16 @@ public class ProductSkuApiImpl implements ProductSkuApi {
35 34  
36 35 @Override
37 36 public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) {
38   - // TODO TODO LeeYan9: AllEmpty?
39   - if (CollectionUtils.isAnyEmpty(ids)) {
  37 + if (CollUtil.isEmpty(ids)) {
40 38 return Collections.emptyList();
41 39 }
42   - List<ProductSkuDO> productSkuDOList = productSkuMapper.selectBatchIds(ids);
43   - return ProductSkuConvert.INSTANCE.convertList04(productSkuDOList);
  40 + List<ProductSkuDO> skus = productSkuService.getSkuList(ids);
  41 + return ProductSkuConvert.INSTANCE.convertList04(skus);
44 42 }
45 43  
46 44 @Override
47   - @Transactional(rollbackFor = Exception.class)
48   - public void decrementStockBatch(SkuDecrementStockBatchReqDTO batchReqDTO) {
49   - // TODO @LeeYan9: 最好 Service 去 for 循环;
50   - productSkuMapper.decrementStockBatch(batchReqDTO.getItems());
  45 + public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
  46 + productSkuService.updateSkuStock(updateStockReqDTO);
51 47 }
  48 +
52 49 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/sku/ProductSkuConvert.java
1 1 package cn.iocoder.yudao.module.product.convert.sku;
2 2  
3 3 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  4 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
4 5 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
5 6 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuOptionRespVO;
6 7 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
... ... @@ -9,7 +10,11 @@ import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
9 10 import org.mapstruct.Mapper;
10 11 import org.mapstruct.factory.Mappers;
11 12  
  13 +import java.util.HashMap;
12 14 import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
13 18  
14 19 /**
15 20 * 商品 SKU Convert
... ... @@ -39,4 +44,26 @@ public interface ProductSkuConvert {
39 44  
40 45 List<ProductSkuOptionRespVO> convertList05(List<ProductSkuDO> skus);
41 46  
  47 + /**
  48 + * 获得 SPU 的库存变化 Map
  49 + *
  50 + * @param items SKU 库存变化
  51 + * @param skus SKU 列表
  52 + * @return SPU 的库存变化 Map
  53 + */
  54 + default Map<Long, Integer> convertSpuStockMap(List<ProductSkuUpdateStockReqDTO.Item> items,
  55 + List<ProductSkuDO> skus) {
  56 + Map<Long, Long> skuIdAndSpuIdMap = convertMap(skus, ProductSkuDO::getId, ProductSkuDO::getSpuId); // SKU 与 SKU 编号的 Map 关系
  57 + Map<Long, Integer> spuIdAndStockMap = new HashMap<>(); // SPU 的库存变化 Map 关系
  58 + items.forEach(item -> {
  59 + Long spuId = skuIdAndSpuIdMap.get(item.getId());
  60 + if (spuId == null) {
  61 + return;
  62 + }
  63 + Integer stock = spuIdAndStockMap.getOrDefault(spuId, 0) + item.getIncCount();
  64 + spuIdAndStockMap.put(spuId, stock);
  65 + });
  66 + return spuIdAndStockMap;
  67 + }
  68 +
42 69 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java
1 1 package cn.iocoder.yudao.module.product.dal.mysql.sku;
2 2  
  3 +import cn.hutool.core.lang.Assert;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5   -import cn.iocoder.yudao.module.product.api.sku.dto.SkuDecrementStockBatchReqDTO;
6 6 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
7 7 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
8 8 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
... ... @@ -23,21 +23,37 @@ public interface ProductSkuMapper extends BaseMapperX&lt;ProductSkuDO&gt; {
23 23 }
24 24  
25 25 default void deleteBySpuId(Long spuId) {
26   - delete(new LambdaQueryWrapperX<ProductSkuDO>()
27   - .eqIfPresent(ProductSkuDO::getSpuId, spuId));
  26 + delete(new LambdaQueryWrapperX<ProductSkuDO>().eq(ProductSkuDO::getSpuId, spuId));
28 27 }
29 28  
30   - default void decrementStockBatch(List<SkuDecrementStockBatchReqDTO.Item> items) {
31   - for (SkuDecrementStockBatchReqDTO.Item item : items) {
32   - // 扣减库存 cas 逻辑
33   - LambdaUpdateWrapper<ProductSkuDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()
34   - .setSql(" stock = stock-" + item.getCount())
35   - .eq(ProductSkuDO::getSpuId, item.getProductId())
36   - .eq(ProductSkuDO::getId, item.getSkuId())
37   - .ge(ProductSkuDO::getStock, item.getCount());
38   - // 执行
39   - this.update(null, lambdaUpdateWrapper);
40   - }
  29 + /**
  30 + * 更新 SKU 库存(增加)
  31 + *
  32 + * @param id 编号
  33 + * @param incrCount 增加库存(正数)
  34 + */
  35 + default void updateStockIncr(Long id, Integer incrCount) {
  36 + Assert.isTrue(incrCount > 0);
  37 + LambdaUpdateWrapper<ProductSkuDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()
  38 + .setSql(" stock = stock + " + incrCount)
  39 + .eq(ProductSkuDO::getId, id);
  40 + update(null, lambdaUpdateWrapper);
  41 + }
  42 +
  43 + /**
  44 + * 更新 SKU 库存(减少)
  45 + *
  46 + * @param id 编号
  47 + * @param incrCount 减少库存(负数)
  48 + * @return 更新条数
  49 + */
  50 + default int updateStockDecr(Long id, Integer incrCount) {
  51 + Assert.isTrue(incrCount < 0);
  52 + LambdaUpdateWrapper<ProductSkuDO> updateWrapper = new LambdaUpdateWrapper<ProductSkuDO>()
  53 + .setSql(" stock = stock + " + incrCount) // 负数,所以使用 + 号
  54 + .eq(ProductSkuDO::getId, id)
  55 + .ge(ProductSkuDO::getStock, -incrCount); // cas 逻辑
  56 + return update(null, updateWrapper);
41 57 }
42 58  
43 59 default List<ProductSkuDO> selectListByAlarmStock(){
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
7 7 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 9 import org.apache.ibatis.annotations.Mapper;
9 10  
10 11 import java.util.Set;
... ... @@ -43,4 +44,17 @@ public interface ProductSpuMapper extends BaseMapperX&lt;ProductSpuDO&gt; {
43 44 .orderByDesc(ProductSpuDO::getSort));
44 45 }
45 46  
  47 + /**
  48 + * 更新商品 SPU 库存
  49 + *
  50 + * @param id 商品 SPU 编号
  51 + * @param incrCount 增加的库存数量
  52 + */
  53 + default void updateStock(Long id, Integer incrCount) {
  54 + LambdaUpdateWrapper<ProductSpuDO> updateWrapper = new LambdaUpdateWrapper<ProductSpuDO>()
  55 + .setSql(" total_stock = total_stock +" + incrCount) // 负数,所以使用 + 号
  56 + .eq(ProductSpuDO::getId, id);
  57 + update(null, updateWrapper);
  58 + }
  59 +
46 60 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java
1 1 package cn.iocoder.yudao.module.product.service.sku;
2 2  
  3 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
3 4 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
4 5 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
5 6  
... ... @@ -64,7 +65,16 @@ public interface ProductSkuService {
64 65 * @param spuId SPU 编码
65 66 * @param skus SKU 的集合
66 67 */
67   - void updateProductSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus);
  68 + void updateSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus);
  69 +
  70 + /**
  71 + * 更新 SKU 库存(增量)
  72 + *
  73 + * 如果更新的库存不足,会抛出异常
  74 + *
  75 + * @param updateStockReqDTO 更行请求
  76 + */
  77 + void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
68 78  
69 79 /**
70 80 * 获得商品 sku 集合
... ... @@ -96,4 +106,5 @@ public interface ProductSkuService {
96 106 */
97 107 List<ProductSkuDO> getSkusByAlarmStock();
98 108  
  109 +
99 110 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.service.sku;
2 2  
3 3 import cn.hutool.core.util.ObjectUtil;
4 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  5 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
5 6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
6 7 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
7 8 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
... ... @@ -13,6 +14,8 @@ import cn.iocoder.yudao.module.product.enums.ErrorCodeConstants;
13 14 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
14 15 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
15 16 import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
  17 +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
  18 +import org.springframework.context.annotation.Lazy;
16 19 import org.springframework.stereotype.Service;
17 20 import org.springframework.transaction.annotation.Transactional;
18 21 import org.springframework.validation.annotation.Validated;
... ... @@ -22,6 +25,7 @@ import java.util.*;
22 25 import java.util.stream.Collectors;
23 26  
24 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
25 29 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
26 30  
27 31 /**
... ... @@ -37,15 +41,17 @@ public class ProductSkuServiceImpl implements ProductSkuService {
37 41 private ProductSkuMapper productSkuMapper;
38 42  
39 43 @Resource
  44 + @Lazy // 循环依赖,避免报错
  45 + private ProductSpuService productSpuService;
  46 + @Resource
40 47 private ProductPropertyService productPropertyService;
41   -
42 48 @Resource
43 49 private ProductPropertyValueService productPropertyValueService;
44 50  
45 51 @Override
46 52 public void deleteSku(Long id) {
47 53 // 校验存在
48   - this.validateSkuExists(id);
  54 + validateSkuExists(id);
49 55 // 删除
50 56 productSkuMapper.deleteById(id);
51 57 }
... ... @@ -89,7 +95,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
89 95 // 2. 校验,一个 SKU 下,没有重复的规格。校验方式是,遍历每个 SKU ,看看是否有重复的规格 propertyId
90 96 Map<Long, ProductPropertyValueRespVO> propertyValueMap = CollectionUtils.convertMap(productPropertyValueService.getPropertyValueListByPropertyId(new ArrayList<>(propertyIds)), ProductPropertyValueRespVO::getId);
91 97 skus.forEach(sku -> {
92   - Set<Long> skuPropertyIds = CollectionUtils.convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId());
  98 + Set<Long> skuPropertyIds = convertSet(sku.getProperties(), propertyItem -> propertyValueMap.get(propertyItem.getValueId()).getPropertyId());
93 99 if (skuPropertyIds.size() != sku.getProperties().size()) {
94 100 throw exception(SKU_PROPERTIES_DUPLICATED);
95 101 }
... ... @@ -106,7 +112,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
106 112 // 4. 最后校验,每个 Sku 之间不是重复的
107 113 Set<Set<Long>> skuAttrValues = new HashSet<>(); // 每个元素,都是一个 Sku 的 attrValueId 集合。这样,通过最外层的 Set ,判断是否有重复的.
108 114 for (ProductSkuCreateOrUpdateReqVO sku : skus) {
109   - if (!skuAttrValues.add(CollectionUtils.convertSet(sku.getProperties(), ProductSkuBaseVO.Property::getValueId))) { // 添加失败,说明重复
  115 + if (!skuAttrValues.add(convertSet(sku.getProperties(), ProductSkuBaseVO.Property::getValueId))) { // 添加失败,说明重复
110 116 throw exception(ErrorCodeConstants.SPU_SKU_NOT_DUPLICATE);
111 117 }
112 118 }
... ... @@ -142,7 +148,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
142 148  
143 149 @Override
144 150 @Transactional
145   - public void updateProductSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus) {
  151 + public void updateSkus(Long spuId, List<ProductSkuCreateOrUpdateReqVO> skus) {
146 152 // 查询 SPU 下已经存在的 SKU 的集合
147 153 List<ProductSkuDO> existsSkus = productSkuMapper.selectListBySpuId(spuId);
148 154 // 构建规格与 SKU 的映射关系;
... ... @@ -192,14 +198,27 @@ public class ProductSkuServiceImpl implements ProductSkuService {
192 198 }
193 199 }
194 200  
195   - public static void main(String[] args) {
196   - List<Integer> ids = new ArrayList<>();
197   - ids.add(1);
198   -
199   - List<Integer> ids2 = new ArrayList<>();
200   - ids2.add(2);
  201 + @Override
  202 + @Transactional(rollbackFor = Exception.class)
  203 + public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
  204 + // 更新 SKU 库存
  205 + updateStockReqDTO.getItems().forEach(item -> {
  206 + if (item.getIncCount() > 0) {
  207 + productSkuMapper.updateStockIncr(item.getId(), item.getIncCount());
  208 + } else if (item.getIncCount() < 0) {
  209 + int updateStockIncr = productSkuMapper.updateStockDecr(item.getId(), item.getIncCount());
  210 + if (updateStockIncr == 0) {
  211 + throw exception(SKU_STOCK_NOT_ENOUGH);
  212 + }
  213 + }
  214 + });
201 215  
202   - System.out.println(ids.equals(ids2));
  216 + // 更新 SPU 库存
  217 + List<ProductSkuDO> skus = productSkuMapper.selectBatchIds(
  218 + convertSet(updateStockReqDTO.getItems(), ProductSkuUpdateStockReqDTO.Item::getId));
  219 + Map<Long, Integer> spuStockIncrCounts = ProductSkuConvert.INSTANCE.convertSpuStockMap(
  220 + updateStockReqDTO.getItems(), skus);
  221 + productSpuService.updateSpuStock(spuStockIncrCounts);
203 222 }
204 223  
205 224 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
... ... @@ -99,4 +99,11 @@ public interface ProductSpuService {
99 99 */
100 100 PageResult<AppSpuPageRespVO> getSpuPage(AppSpuPageReqVO pageReqVO);
101 101  
  102 + /**
  103 + * 更新商品 SPU 库存(增量)
  104 + *
  105 + * @param stockIncrCounts SPU 编号与库存变化(增量)的映射
  106 + */
  107 + void updateSpuStock(Map<Long, Integer> stockIncrCounts);
  108 +
102 109 }
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
... ... @@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
24 24 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
25 25 import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
26 26 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
  27 +import org.springframework.context.annotation.Lazy;
27 28 import org.springframework.stereotype.Service;
28 29 import org.springframework.transaction.annotation.Transactional;
29 30 import org.springframework.validation.annotation.Validated;
... ... @@ -51,14 +52,12 @@ public class ProductSpuServiceImpl implements ProductSpuService {
51 52 private ProductCategoryService categoryService;
52 53  
53 54 @Resource
  55 + @Lazy // 循环依赖,避免报错
54 56 private ProductSkuService productSkuService;
55   -
56 57 @Resource
57 58 private ProductPropertyService productPropertyService;
58   -
59 59 @Resource
60 60 private ProductPropertyValueService productPropertyValueService;
61   -
62 61 @Resource
63 62 private ProductBrandService brandService;
64 63  
... ... @@ -106,7 +105,7 @@ public class ProductSpuServiceImpl implements ProductSpuService {
106 105 updateObj.setTotalStock(CollectionUtils.getSumValue(skuCreateReqList, ProductSkuCreateOrUpdateReqVO::getStock, Integer::sum));
107 106 productSpuMapper.updateById(updateObj);
108 107 // 批量更新 SKU
109   - productSkuService.updateProductSkus(updateObj.getId(), updateReqVO.getSkus());
  108 + productSkuService.updateSkus(updateObj.getId(), updateReqVO.getSkus());
110 109 }
111 110  
112 111 @Override
... ... @@ -210,4 +209,10 @@ public class ProductSpuServiceImpl implements ProductSpuService {
210 209 return pageResult;
211 210 }
212 211  
  212 + @Override
  213 + @Transactional(rollbackFor = Exception.class)
  214 + public void updateSpuStock(Map<Long, Integer> stockIncrCounts) {
  215 + stockIncrCounts.forEach((id, incCount) -> productSpuMapper.updateStock(id, incCount));
  216 + }
  217 +
213 218 }
... ...
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.product.service.sku;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  4 +import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
  5 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
  6 +import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
  7 +import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
  8 +import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
  9 +import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
  10 +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
  11 +import org.junit.jupiter.api.Test;
  12 +import org.springframework.boot.test.mock.mockito.MockBean;
  13 +import org.springframework.context.annotation.Import;
  14 +
  15 +import javax.annotation.Resource;
  16 +
  17 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  18 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
  19 +import static java.util.Collections.singletonList;
  20 +import static org.junit.jupiter.api.Assertions.assertEquals;
  21 +import static org.mockito.ArgumentMatchers.argThat;
  22 +import static org.mockito.Mockito.verify;
  23 +
  24 +/**
  25 + * {@link ProductSkuServiceImpl} 的单元测试
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Import(ProductSkuServiceImpl.class)
  30 +public class ProductSkuServiceTest extends BaseDbUnitTest {
  31 +
  32 + @Resource
  33 + private ProductSkuService productSkuService;
  34 +
  35 + @Resource
  36 + private ProductSkuMapper productSkuMapper;
  37 +
  38 + @MockBean
  39 + private ProductSpuService productSpuService;
  40 + @MockBean
  41 + private ProductPropertyService productPropertyService;
  42 + @MockBean
  43 + private ProductPropertyValueService productPropertyValueService;
  44 +
  45 + @Test
  46 + public void testUpdateSkuStock_incrSuccess() {
  47 + // 准备参数
  48 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
  49 + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(10)));
  50 + // mock 数据
  51 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
  52 +
  53 + // 调用
  54 + productSkuService.updateSkuStock(updateStockReqDTO);
  55 + // 断言
  56 + ProductSkuDO sku = productSkuMapper.selectById(1L);
  57 + assertEquals(sku.getStock(), 30);
  58 + verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> {
  59 + assertEquals(spuStockIncrCounts.size(), 1);
  60 + assertEquals(spuStockIncrCounts.get(10L), 10);
  61 + return true;
  62 + }));
  63 + }
  64 +
  65 + @Test
  66 + public void testUpdateSkuStock_decrSuccess() {
  67 + // 准备参数
  68 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
  69 + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(-10)));
  70 + // mock 数据
  71 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
  72 +
  73 + // 调用
  74 + productSkuService.updateSkuStock(updateStockReqDTO);
  75 + // 断言
  76 + ProductSkuDO sku = productSkuMapper.selectById(1L);
  77 + assertEquals(sku.getStock(), 10);
  78 + verify(productSpuService).updateSpuStock(argThat(spuStockIncrCounts -> {
  79 + assertEquals(spuStockIncrCounts.size(), 1);
  80 + assertEquals(spuStockIncrCounts.get(10L), -10);
  81 + return true;
  82 + }));
  83 + }
  84 +
  85 + @Test
  86 + public void testUpdateSkuStock_decrFail() {
  87 + // 准备参数
  88 + ProductSkuUpdateStockReqDTO updateStockReqDTO = new ProductSkuUpdateStockReqDTO()
  89 + .setItems(singletonList(new ProductSkuUpdateStockReqDTO.Item().setId(1L).setIncCount(-30)));
  90 + // mock 数据
  91 + productSkuMapper.insert(randomPojo(ProductSkuDO.class, o -> o.setId(1L).setSpuId(10L).setStock(20)));
  92 +
  93 + // 调用并断言
  94 + AssertUtils.assertServiceException(() -> productSkuService.updateSkuStock(updateStockReqDTO),
  95 + SKU_STOCK_NOT_ENOUGH);
  96 + }
  97 +
  98 +}
... ...
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/sku/SkuServiceImplTest.java
... ... @@ -13,6 +13,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
13 13 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
14 14 import static org.junit.jupiter.api.Assertions.assertNull;
15 15  
  16 +// TODO 芋艿:整合到 {@link ProductSkuServiceTest} 中
16 17 /**
17 18 * {@link ProductSkuServiceImpl} 的单元测试类
18 19 *
... ...
yudao-module-mall/yudao-module-product-biz/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java
1 1 package cn.iocoder.yudao.module.product.service.spu;
2 2  
3 3 import cn.hutool.core.date.DateUtil;
  4 +import cn.hutool.core.map.MapUtil;
4 5 import cn.hutool.core.util.RandomUtil;
5 6 import cn.iocoder.yudao.framework.common.exception.ServiceException;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
... ... @@ -39,6 +40,7 @@ import java.util.stream.Stream;
39 40 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
40 41 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
41 42 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  43 +import static org.junit.jupiter.api.Assertions.assertEquals;
42 44  
43 45 // TODO @芋艿:review 下单元测试
44 46  
... ... @@ -56,19 +58,14 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
56 58 @Resource
57 59 private ProductSpuMapper productSpuMapper;
58 60  
59   -
60 61 @MockBean
61 62 private ProductSkuServiceImpl productSkuService;
62   -
63 63 @MockBean
64 64 private ProductCategoryServiceImpl categoryService;
65   -
66 65 @MockBean
67 66 private ProductBrandServiceImpl brandService;
68   -
69 67 @MockBean
70 68 private ProductPropertyService productPropertyService;
71   -
72 69 @MockBean
73 70 private ProductPropertyValueService productPropertyValueService;
74 71  
... ... @@ -228,7 +225,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
228 225  
229 226 PageResult<Object> result = PageResult.empty();
230 227 Assertions.assertIterableEquals(result.getList(), spuPage.getList());
231   - Assertions.assertEquals(spuPage.getTotal(), result.getTotal());
  228 + assertEquals(spuPage.getTotal(), result.getTotal());
232 229 }
233 230  
234 231 @Test
... ... @@ -277,7 +274,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
277 274  
278 275 PageResult<ProductSpuRespVO> result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO, alarmStockSpuIds));
279 276 Assertions.assertIterableEquals(result.getList(), spuPage.getList());
280   - Assertions.assertEquals(spuPage.getTotal(), result.getTotal());
  277 + assertEquals(spuPage.getTotal(), result.getTotal());
281 278 }
282 279  
283 280 @Test
... ... @@ -328,7 +325,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
328 325 PageResult<ProductSpuRespVO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
329 326  
330 327 PageResult<ProductSpuRespVO> result = ProductSpuConvert.INSTANCE.convertPage(productSpuMapper.selectPage(productSpuPageReqVO, (Set<Long>) null));
331   - Assertions.assertEquals(result, spuPage);
  328 + assertEquals(result, spuPage);
332 329 }
333 330  
334 331 @Test
... ... @@ -354,7 +351,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
354 351 .collect(Collectors.toList());
355 352  
356 353 Assertions.assertIterableEquals(collect, spuPage.getList());
357   - Assertions.assertEquals(spuPage.getTotal(), result.getTotal());
  354 + assertEquals(spuPage.getTotal(), result.getTotal());
358 355 }
359 356  
360 357  
... ... @@ -389,4 +386,19 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
389 386 return res;
390 387 }
391 388  
  389 + @Test
  390 + public void testUpdateSpuStock() {
  391 + // 准备参数
  392 + Map<Long, Integer> stockIncrCounts = MapUtil.builder(1L, 10).put(2L, -20).build();
  393 + // mock 方法(数据)
  394 + productSpuMapper.insert(randomPojo(ProductSpuDO.class, o -> o.setId(1L).setTotalStock(20)));
  395 + productSpuMapper.insert(randomPojo(ProductSpuDO.class, o -> o.setId(2L).setTotalStock(30)));
  396 +
  397 + // 调用
  398 + productSpuService.updateSpuStock(stockIncrCounts);
  399 + // 断言
  400 + assertEquals(productSpuService.getSpu(1L).getTotalStock(), 30);
  401 + assertEquals(productSpuService.getSpu(2L).getTotalStock(), 10);
  402 + }
  403 +
392 404 }
... ...
yudao-module-mall/yudao-module-product-biz/src/test/resources/sql/create_tables.sql
1 1 CREATE TABLE IF NOT EXISTS `product_sku` (
2   -`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
3   -`spu_id` bigint NOT NULL COMMENT 'spu编号',
4   -`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
5   -`name` varchar(128) DEFAULT NULL COMMENT '商品 SKU 名字',
6   -`properties` varchar(128) DEFAULT NULL COMMENT '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
7   -`price` int NOT NULL DEFAULT '-1' COMMENT '销售价格,单位:分',
8   -`market_price` int DEFAULT NULL COMMENT '市场价',
9   -`cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',
10   -`pic_url` varchar(128) NOT NULL COMMENT '图片地址',
11   -`stock` int DEFAULT NULL COMMENT '库存',
12   -`warn_stock` int DEFAULT NULL COMMENT '预警库存',
13   -`volume` double DEFAULT NULL COMMENT '商品体积',
14   -`weight` double DEFAULT NULL COMMENT '商品重量',
15   -`bar_code` varchar(64) DEFAULT NULL COMMENT '条形码',
16   -`status` tinyint DEFAULT NULL COMMENT '状态: 0-正常 1-禁用',
17   -`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
18   -`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
19   -`creator` varchar(64) DEFAULT NULL COMMENT '创建人',
20   -`updater` double DEFAULT NULL COMMENT '更新人',
21   -`deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
22   -PRIMARY KEY (`id`)
  2 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  3 + `spu_id` bigint NOT NULL COMMENT 'spu编号',
  4 + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
  5 + `name` varchar DEFAULT NULL COMMENT '商品 SKU 名字',
  6 + `properties` varchar DEFAULT NULL COMMENT '规格值数组-json格式, [{propertId: , valueId: }, {propertId: , valueId: }]',
  7 + `price` int NOT NULL DEFAULT '-1' COMMENT '销售价格,单位:分',
  8 + `market_price` int DEFAULT NULL COMMENT '市场价',
  9 + `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',
  10 + `pic_url` varchar NOT NULL COMMENT '图片地址',
  11 + `stock` int DEFAULT NULL COMMENT '库存',
  12 + `warn_stock` int DEFAULT NULL COMMENT '预警库存',
  13 + `volume` double DEFAULT NULL COMMENT '商品体积',
  14 + `weight` double DEFAULT NULL COMMENT '商品重量',
  15 + `bar_code` varchar DEFAULT NULL COMMENT '条形码',
  16 + `status` tinyint DEFAULT NULL COMMENT '状态: 0-正常 1-禁用',
  17 + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  18 + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  19 + `creator` varchar DEFAULT NULL COMMENT '创建人',
  20 + `updater` varchar DEFAULT NULL COMMENT '更新人',
  21 + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
  22 + PRIMARY KEY (`id`)
23 23 ) COMMENT '商品sku';
24 24  
25 25  
26 26 CREATE TABLE IF NOT EXISTS `product_spu` (
27   -`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
28   -`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
29   -`brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号',
30   -`category_id` bigint NOT NULL COMMENT '分类id',
31   -`spec_type` int NOT NULL COMMENT '规格类型:0 单规格 1 多规格',
32   -`code` varchar(128) DEFAULT NULL COMMENT '商品编码',
33   -`name` varchar(128) NOT NULL COMMENT '商品名称',
34   -`sell_point` varchar(128) DEFAULT NULL COMMENT '卖点',
35   -`description` text COMMENT '描述',
36   -`pic_urls` varchar(1024) DEFAULT '' COMMENT '商品轮播图地址数组,以逗号分隔最多上传15张',
37   -`video_url` varchar(128) DEFAULT NULL COMMENT '商品视频',
38   -`market_price` int DEFAULT NULL COMMENT '市场价,单位使用:分',
39   -`min_price` int DEFAULT NULL COMMENT '最小价格,单位使用:分',
40   -`max_price` int DEFAULT NULL COMMENT '最大价格,单位使用:分',
41   -`total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',
42   -`show_stock` int DEFAULT '0' COMMENT '是否展示库存',
43   -`sales_count` int DEFAULT '0' COMMENT '商品销量',
44   -`virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
45   -`click_count` int DEFAULT '0' COMMENT '商品点击量',
46   -`status` bit(1) DEFAULT NULL COMMENT '上下架状态: 0 上架(开启) 1 下架(禁用)-1 回收',
47   -`sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
48   -`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
49   -`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
50   -`creator` varchar(64) DEFAULT NULL COMMENT '创建人',
51   -`updater` varchar(64) DEFAULT NULL COMMENT '更新人',
52   -`deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
  27 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  28 + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
  29 + `brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号',
  30 + `category_id` bigint NOT NULL COMMENT '分类id',
  31 + `spec_type` int NOT NULL COMMENT '规格类型:0 单规格 1 多规格',
  32 + `code` varchar(128) DEFAULT NULL COMMENT '商品编码',
  33 + `name` varchar(128) NOT NULL COMMENT '商品名称',
  34 + `sell_point` varchar(128) DEFAULT NULL COMMENT '卖点',
  35 + `description` text COMMENT '描述',
  36 + `pic_urls` varchar(1024) DEFAULT '' COMMENT '商品轮播图地址数组,以逗号分隔最多上传15张',
  37 + `video_url` varchar(128) DEFAULT NULL COMMENT '商品视频',
  38 + `market_price` int DEFAULT NULL COMMENT '市场价,单位使用:分',
  39 + `min_price` int DEFAULT NULL COMMENT '最小价格,单位使用:分',
  40 + `max_price` int DEFAULT NULL COMMENT '最大价格,单位使用:分',
  41 + `total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',
  42 + `show_stock` int DEFAULT '0' COMMENT '是否展示库存',
  43 + `sales_count` int DEFAULT '0' COMMENT '商品销量',
  44 + `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
  45 + `click_count` int DEFAULT '0' COMMENT '商品点击量',
  46 + `status` bit(1) DEFAULT NULL COMMENT '上下架状态: 0 上架(开启) 1 下架(禁用)-1 回收',
  47 + `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
  48 + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  49 + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  50 + `creator` varchar DEFAULT NULL COMMENT '创建人',
  51 + `updater` varchar DEFAULT NULL COMMENT '更新人',
  52 + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
53 53 PRIMARY KEY (`id`)
54 54 ) COMMENT '商品spu';
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.coupon;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
  4 +
  5 +import javax.validation.Valid;
  6 +
  7 +/**
  8 + * 优惠劵 API 接口
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface CouponApi {
  13 +
  14 + /**
  15 + * 使用优惠劵
  16 + *
  17 + * @param useReqDTO 使用请求
  18 + */
  19 + void useCoupon(@Valid CouponUseReqDTO useReqDTO);
  20 +
  21 +}
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/dto/CouponUseReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.coupon.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import javax.validation.constraints.NotNull;
  6 +
  7 +/**
  8 + * 优惠劵使用 Request DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class CouponUseReqDTO {
  14 +
  15 + /**
  16 + * 优惠劵编号
  17 + */
  18 + @NotNull(message = "优惠劵编号不能为空")
  19 + private Long id;
  20 +
  21 + /**
  22 + * 用户编号
  23 + */
  24 + @NotNull(message = "用户编号不能为空")
  25 + private Long userId;
  26 +
  27 + /**
  28 + * 订单编号
  29 + */
  30 + @NotNull(message = "订单编号不能为空")
  31 + private Long orderId;
  32 +
  33 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.coupon;
  2 +
  3 +
  4 +import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
  5 +import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
  6 +import org.springframework.stereotype.Service;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +/**
  11 + * 优惠劵 API 实现类
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Service
  16 +public class CouponApiImpl implements CouponApi {
  17 +
  18 + @Resource
  19 + private CouponService couponService;
  20 +
  21 + @Override
  22 + public void useCoupon(CouponUseReqDTO useReqDTO) {
  23 + couponService.useCoupon(useReqDTO.getId(), useReqDTO.getUserId(),
  24 + useReqDTO.getOrderId());
  25 + }
  26 +
  27 +}
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
... ... @@ -45,10 +45,11 @@ public interface CouponService {
45 45 /**
46 46 * 使用优惠劵
47 47 *
48   - * @param id 优惠劵编号
49   - * @param userId 用户编号
  48 + * @param id 优惠劵编号
  49 + * @param userId 用户编号
  50 + * @param orderId 订单编号
50 51 */
51   - void useCoupon(Long id, Long userId);
  52 + void useCoupon(Long id, Long userId, Long orderId);
52 53  
53 54 /**
54 55 * 回收优惠劵
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
... ... @@ -81,12 +81,13 @@ public class CouponServiceImpl implements CouponService {
81 81 }
82 82  
83 83 @Override
84   - public void useCoupon(Long id, Long userId) {
  84 + public void useCoupon(Long id, Long userId, Long orderId) {
85 85 // 校验优惠劵
86 86 validCoupon(id, userId);
87 87 // 更新状态
88 88 int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
89   - new CouponDO().setStatus(CouponStatusEnum.USED.getStatus()).setUseTime(new Date()));
  89 + new CouponDO().setStatus(CouponStatusEnum.USED.getStatus())
  90 + .setUseOrderId(orderId).setUseTime(new Date()));
90 91 if (updateCount == 0) {
91 92 throw exception(COUPON_STATUS_NOT_UNUSED);
92 93 }
... ...