Commit 57f0ea04f72fb4ebe68d4bc1d7a3bee38e3aca09

Authored by puhui999
1 parent c023209a

mall-order: 完善活动商品库存扣减逻辑(并发更新库存下一提交实现)

yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java
... ... @@ -10,9 +10,9 @@ public interface BargainActivityApi {
10 10 /**
11 11 * 更新砍价活动库存
12 12 *
13   - * @param activityId 砍价活动编号
  13 + * @param id 砍价活动编号
14 14 * @param count 购买数量
15 15 */
16   - void updateBargainActivityStock(Long activityId, Integer count);
  16 + void updateBargainActivityStock(Long id, Integer count);
17 17  
18 18 }
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityUpdateStockReqDTO.java
... ... @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.promotion.api.seckill.dto;
2 2  
3 3 import lombok.Data;
4 4  
5   -import java.util.List;
  5 +import javax.validation.constraints.NotNull;
6 6  
7 7 /**
8 8 * 更新秒杀库存 request DTO
... ... @@ -12,37 +12,25 @@ import java.util.List;
12 12 @Data
13 13 public class SeckillActivityUpdateStockReqDTO {
14 14  
15   - // TODO @puhui999:参数校验
16   -
17   - // TODO @puhui999:秒杀的话,一次只能购买一种商品哈;不能多个哈;
18   -
19   - /**
20   - * 活动编号
21   - */
  15 + @NotNull(message = "活动编号不能为空")
22 16 private Long activityId;
23   - /**
24   - * 总购买数量
25   - */
  17 +
  18 + @NotNull(message = "购买数量不能为空")
26 19 private Integer count;
27   - /**
28   - * 活动商品
29   - */
30   - private List<Item> items;
  20 +
  21 + @NotNull(message = "活动商品不能为空")
  22 + private Item item;
31 23  
32 24 @Data
33 25 public static class Item {
34 26  
35   - /**
36   - * SPU 编号
37   - */
  27 + @NotNull(message = "SPU 编号不能为空")
38 28 private Long spuId;
39   - /**
40   - * SKU 编号
41   - */
  29 +
  30 + @NotNull(message = "SKU 编号活动商品不能为空")
42 31 private Long skuId;
43   - /**
44   - * 购买数量
45   - */
  32 +
  33 + @NotNull(message = "购买数量不能为空")
46 34 private Integer count;
47 35  
48 36 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApiImpl.java
1 1 package cn.iocoder.yudao.module.promotion.api.bargain;
2 2  
3   -import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
4   -import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
5 3 import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
6 4 import org.springframework.stereotype.Service;
7 5  
8 6 import javax.annotation.Resource;
9 7  
10   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
11   -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.BARGAIN_ACTIVITY_NOT_EXISTS;
12   -
13 8 /**
14 9 * 砍价活动 Api 接口实现类
15 10 *
... ... @@ -22,20 +17,8 @@ public class BargainActivityApiImpl implements BargainActivityApi {
22 17 private BargainActivityService bargainActivityService;
23 18  
24 19 @Override
25   - public void updateBargainActivityStock(Long activityId, Integer count) {
26   - // TODO @puhui999:可以整个实现到 bargainActivityService 中
27   - // 查询砍价活动
28   - BargainActivityDO activity = bargainActivityService.getBargainActivity(activityId);
29   - if (activity == null) {
30   - throw exception(BARGAIN_ACTIVITY_NOT_EXISTS);
31   - }
32   -
33   - // 更新砍价库存
34   - // TODO @puhui999:考虑下并发更新问题
35   - BargainActivityUpdateReqVO reqVO = new BargainActivityUpdateReqVO();
36   - reqVO.setId(activityId);
37   - reqVO.setStock(activity.getStock() - count);
38   - bargainActivityService.updateBargainActivity(reqVO);
  20 + public void updateBargainActivityStock(Long id, Integer count) {
  21 + bargainActivityService.updateBargainActivityStock(id, count);
39 22 }
40 23  
41 24 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
1 1 package cn.iocoder.yudao.module.promotion.api.seckill;
2 2  
3   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 3 import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
5   -import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
6   -import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
7 4 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
8 5 import org.springframework.stereotype.Service;
9 6  
10 7 import javax.annotation.Resource;
11   -import java.util.ArrayList;
12   -import java.util.HashMap;
13   -import java.util.List;
14   -import java.util.Map;
15   -
16   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
17   -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_ACTIVITY_UPDATE_STOCK_FAIL;
18 8  
19 9 /**
20 10 * 秒杀活动接口 Api 接口实现类
... ... @@ -27,58 +17,9 @@ public class SeckillActivityApiImpl implements SeckillActivityApi {
27 17 @Resource
28 18 private SeckillActivityService activityService;
29 19  
30   - // TODO @puhui:建议这块弄到 activityService 实现哈;
31   - // TODO @puhui:这个方法,要考虑事务性
32 20 @Override
33 21 public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
34   - // TODO @puhui999:长方法,最好有 1.1 1.2 2.1 这种步骤哈;
35   - SeckillActivityDO seckillActivity = activityService.getSeckillActivity(updateStockReqDTO.getActivityId());
36   - if (seckillActivity.getStock() < updateStockReqDTO.getCount()) {
37   - throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
38   - }
39   - // 获取活动商品
40   - // TODO @puhui999:在一个方法里,dos 和 dolist 最好保持一致,要么用 s,要么用 list 哈;
41   - List<SeckillProductDO> productDOs = activityService.getSeckillProductListByActivityId(updateStockReqDTO.getActivityId());
42   - // TODO @puhui999:这个是不是搞成 CollectionUtils.convertMultiMap()
43   - List<SeckillActivityUpdateStockReqDTO.Item> items = updateStockReqDTO.getItems();
44   - Map<Long, List<Long>> map = new HashMap<>();
45   - items.forEach(item -> {
46   - if (map.containsKey(item.getSpuId())) {
47   - List<Long> skuIds = map.get(item.getSpuId());
48   - skuIds.add(item.getSkuId());
49   - map.put(item.getSpuId(), skuIds);
50   - } else {
51   - List<Long> list = new ArrayList<>();
52   - list.add(item.getSkuId());
53   - map.put(item.getSpuId(), list);
54   - }
55   - });
56   - // 过滤出购买的商品
57   - // TODO @puhui999:productDOList 可以简化成 productList;一般来说,do 之类不用带着哈,在变量里;
58   - List<SeckillProductDO> productDOList = CollectionUtils.filterList(productDOs, item -> map.get(item.getSpuId()).contains(item.getSkuId()));
59   - Map<Long, SeckillActivityUpdateStockReqDTO.Item> productDOMap = CollectionUtils.convertMap(items, SeckillActivityUpdateStockReqDTO.Item::getSkuId, p -> p);
60   - // 检查活动商品库存是否充足
61   - // TODO @puhui999:避免 b 这种无业务含义的变量;
62   - boolean b = CollectionUtils.anyMatch(productDOList, item -> {
63   - SeckillActivityUpdateStockReqDTO.Item item1 = productDOMap.get(item.getSkuId());
64   - return (item.getStock() < item1.getCount()) || (item.getStock() - item1.getCount()) < 0;
65   - });
66   - if (b) {
67   - throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
68   - }
69   - // TODO @puhui999:类似 doList,应该和下面的 update 逻辑粘的更紧密一点;so 在空行的时候,应该挪到 74 之后里去;甚至更合理,应该是 79 之后;说白了,逻辑要分块,每个模块涉及的代码要紧密在一起;
70   - List<SeckillProductDO> doList = CollectionUtils.convertList(productDOList, item -> {
71   - item.setStock(item.getStock() - productDOMap.get(item.getSkuId()).getCount());
72   - return item;
73   - });
74   -
75   - // 更新活动库存
76   - // TODO @puhui999:考虑下并发更新
77   - seckillActivity.setStock(seckillActivity.getStock() + updateStockReqDTO.getCount());
78   - seckillActivity.setTotalStock(seckillActivity.getTotalStock() - updateStockReqDTO.getCount());
79   - activityService.updateSeckillActivity(seckillActivity);
80   - // 更新活动商品库存
81   - activityService.updateSeckillActivityProductList(doList);
  22 + activityService.updateSeckillStock(updateStockReqDTO);
82 23 }
83 24  
84 25 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
... ... @@ -31,6 +31,14 @@ public interface BargainActivityService {
31 31 void updateBargainActivity(@Valid BargainActivityUpdateReqVO updateReqVO);
32 32  
33 33 /**
  34 + * 更新砍价活动库存
  35 + *
  36 + * @param id 砍价活动编号
  37 + * @param count 购买数量
  38 + */
  39 + void updateBargainActivityStock(Long id, Integer count);
  40 +
  41 + /**
34 42 * 删除砍价活动
35 43 *
36 44 * @param id 编号
... ... @@ -53,5 +61,4 @@ public interface BargainActivityService {
53 61 */
54 62 PageResult<BargainActivityDO> getBargainActivityPage(BargainActivityPageReqVO pageReqVO);
55 63  
56   -
57 64 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
... ... @@ -39,6 +39,7 @@ public class BargainActivityServiceImpl implements BargainActivityService {
39 39 private ProductSkuApi productSkuApi;
40 40  
41 41 @Override
  42 + @Transactional(rollbackFor = Exception.class)
42 43 public Long createBargainActivity(BargainActivityCreateReqVO createReqVO) {
43 44 // 校验商品 SPU 是否存在是否参加的别的活动
44 45 validateBargainConflict(createReqVO.getSpuId(), null);
... ... @@ -53,6 +54,7 @@ public class BargainActivityServiceImpl implements BargainActivityService {
53 54 }
54 55  
55 56 @Override
  57 + @Transactional(rollbackFor = Exception.class)
56 58 public void updateBargainActivity(BargainActivityUpdateReqVO updateReqVO) {
57 59 // 校验存在
58 60 BargainActivityDO activityDO = validateBargainActivityExists(updateReqVO.getId());
... ... @@ -70,6 +72,23 @@ public class BargainActivityServiceImpl implements BargainActivityService {
70 72 bargainActivityMapper.updateById(updateObj);
71 73 }
72 74  
  75 + @Override
  76 + @Transactional(rollbackFor = Exception.class)
  77 + public void updateBargainActivityStock(Long id, Integer count) {
  78 + // 查询砍价活动
  79 + BargainActivityDO activity = getBargainActivity(id);
  80 + if (activity == null) {
  81 + throw exception(BARGAIN_ACTIVITY_NOT_EXISTS);
  82 + }
  83 +
  84 + // 更新砍价库存
  85 + // TODO @puhui999:考虑下并发更新问题
  86 + BargainActivityUpdateReqVO reqVO = new BargainActivityUpdateReqVO();
  87 + reqVO.setId(id);
  88 + reqVO.setStock(activity.getStock() - count);
  89 + //bargainActivityService.updateBargainActivity(reqVO);
  90 + }
  91 +
73 92 private void validateBargainConflict(Long spuId, Long activityId) {
74 93 // 查询所有开启的砍价活动
75 94 List<BargainActivityDO> activityList = bargainActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
1 1 package cn.iocoder.yudao.module.promotion.service.seckill;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
4 5 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
5 6 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
6 7 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
... ... @@ -41,6 +42,12 @@ public interface SeckillActivityService {
41 42 void updateSeckillActivity(SeckillActivityDO activityDO);
42 43  
43 44 /**
  45 + * 更新秒杀库存
  46 + *
  47 + * @param updateStockReqDTO 更新信息
  48 + */
  49 + void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO);
  50 + /**
44 51 * 更新秒杀活动商品
45 52 *
46 53 * @param productList 活动商品列表
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
... ... @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
7 7 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
8 8 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
9 9 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
  10 +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
10 11 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
11 12 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
12 13 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
... ... @@ -150,6 +151,40 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
150 151 }
151 152  
152 153 @Override
  154 + @Transactional(rollbackFor = Exception.class)
  155 + public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
  156 + // 1、校验秒杀活动是否存在
  157 + SeckillActivityDO seckillActivity = getSeckillActivity(updateStockReqDTO.getActivityId());
  158 + // 1.1、校验库存是否充足
  159 + if (seckillActivity.getStock() < updateStockReqDTO.getCount()) {
  160 + throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
  161 + }
  162 +
  163 + // 2、更新活动库存
  164 + // TODO @puhui999:考虑下并发更新
  165 + seckillActivity.setStock(seckillActivity.getStock() + updateStockReqDTO.getCount());
  166 + seckillActivity.setTotalStock(seckillActivity.getTotalStock() - updateStockReqDTO.getCount());
  167 + updateSeckillActivity(seckillActivity);
  168 +
  169 + // 3、获取活动商品
  170 + List<SeckillProductDO> products = getSeckillProductListByActivityId(updateStockReqDTO.getActivityId());
  171 + // 3.1、过滤出购买的商品
  172 + SeckillProductDO product = findFirst(products, item -> ObjectUtil.equal(updateStockReqDTO.getItem().getSkuId(), item.getSkuId()));
  173 + // 3.2、检查活动商品库存是否充足
  174 + boolean isSufficient = product == null || (product.getStock() == 0 || (product.getStock() < updateStockReqDTO.getItem().getCount()) || (product.getStock() - updateStockReqDTO.getItem().getCount()) < 0);
  175 + if (isSufficient) {
  176 + throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
  177 + }
  178 +
  179 + // 4、更新活动商品库存
  180 + SeckillProductDO updateProduct = new SeckillProductDO();
  181 + updateProduct.setId(product.getId());
  182 + updateProduct.setStock(product.getStock() - updateStockReqDTO.getItem().getCount());
  183 + // TODO @puhui999:考虑下并发更新
  184 +
  185 + }
  186 +
  187 + @Override
153 188 public void updateSeckillActivityProductList(List<SeckillProductDO> productList) {
154 189 seckillProductMapper.updateBatch(productList);
155 190 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
... ... @@ -81,10 +81,9 @@ public class AppTradeOrderController {
81 81 public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) {
82 82 // 查询订单
83 83 TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id);
84   - // TODO @puhui999:这里建议改成,如果为 null,直接返回 success null;主要查询操作,尽量不要有非空的提示哈;交给前端处理;
85   -// if (order == null) {
86   -// return success(null, ORDER_NOT_FOUND.getMsg());
87   -// }
  84 + if (order == null) {
  85 + return success(null);
  86 + }
88 87  
89 88 // 查询订单项
90 89 List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId());
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -313,17 +313,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
313 313 Integer count = getSumValue(orderItems, TradeOrderItemDO::getCount, Integer::sum);
314 314 // 1)如果是秒杀商品:额外扣减秒杀的库存;
315 315 if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), tradeOrderDO.getType())) {
316   - SeckillActivityUpdateStockReqDTO updateStockReqDTO = new SeckillActivityUpdateStockReqDTO();
317   - updateStockReqDTO.setActivityId(createReqVO.getSeckillActivityId());
318   - updateStockReqDTO.setCount(count);
319   - updateStockReqDTO.setItems(CollectionUtils.convertList(orderItems, item -> {
320   - SeckillActivityUpdateStockReqDTO.Item item1 = new SeckillActivityUpdateStockReqDTO.Item();
321   - item1.setSpuId(item.getSpuId());
322   - item1.setSkuId(item.getSkuId());
323   - item1.setCount(item.getCount());
324   - return item1;
325   - }));
326   - seckillActivityApi.updateSeckillStock(updateStockReqDTO);
  316 + seckillActivityApi.updateSeckillStock(getSeckillActivityUpdateStockReqDTO(createReqVO, orderItems, count));
327 317 }
328 318 // 2)如果是砍价活动:额外扣减砍价的库存;
329 319 bargainActivityApi.updateBargainActivityStock(createReqVO.getBargainActivityId(), count);
... ... @@ -351,6 +341,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
351 341 // 增加订单日志 TODO 芋艿:待实现
352 342 }
353 343  
  344 + private SeckillActivityUpdateStockReqDTO getSeckillActivityUpdateStockReqDTO(AppTradeOrderCreateReqVO createReqVO, List<TradeOrderItemDO> orderItems, Integer count) {
  345 + SeckillActivityUpdateStockReqDTO updateStockReqDTO = new SeckillActivityUpdateStockReqDTO();
  346 + updateStockReqDTO.setActivityId(createReqVO.getSeckillActivityId());
  347 + updateStockReqDTO.setCount(count);
  348 + // 秒杀活动只能选择一个商品
  349 + TradeOrderItemDO item = orderItems.get(0);
  350 + SeckillActivityUpdateStockReqDTO.Item item1 = new SeckillActivityUpdateStockReqDTO.Item();
  351 + item1.setSpuId(item.getSpuId());
  352 + item1.setSkuId(item.getSkuId());
  353 + item1.setCount(item.getCount());
  354 + updateStockReqDTO.setItem(item1);
  355 + return updateStockReqDTO;
  356 + }
  357 +
354 358 private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) {
355 359 // 创建支付单,用于后续的支付
356 360 PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(
... ...