Kaynağa Gözat

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

puhui999 1 yıl önce
ebeveyn
işleme
57f0ea04f7
10 değiştirilmiş dosya ile 104 ekleme ve 121 silme
  1. 2 2
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApi.java
  2. 12 24
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityUpdateStockReqDTO.java
  3. 2 19
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApiImpl.java
  4. 1 60
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
  5. 8 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java
  6. 19 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
  7. 7 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
  8. 35 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
  9. 3 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java
  10. 15 11
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

+ 2 - 2
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 {
     /**
      * 更新砍价活动库存
      *
-     * @param activityId 砍价活动编号
+     * @param id 砍价活动编号
      * @param count      购买数量
      */
-    void updateBargainActivityStock(Long activityId, Integer count);
+    void updateBargainActivityStock(Long id, Integer count);
 
 }

+ 12 - 24
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;
 
 import lombok.Data;
 
-import java.util.List;
+import javax.validation.constraints.NotNull;
 
 /**
  * 更新秒杀库存 request DTO
@@ -12,37 +12,25 @@ import java.util.List;
 @Data
 public class SeckillActivityUpdateStockReqDTO {
 
-    // TODO @puhui999:参数校验
-
-    // TODO @puhui999:秒杀的话,一次只能购买一种商品哈;不能多个哈;
-
-    /**
-     * 活动编号
-     */
+    @NotNull(message = "活动编号不能为空")
     private Long activityId;
-    /**
-     * 总购买数量
-     */
+
+    @NotNull(message = "购买数量不能为空")
     private Integer count;
-    /**
-     * 活动商品
-     */
-    private List<Item> items;
+
+    @NotNull(message = "活动商品不能为空")
+    private Item item;
 
     @Data
     public static class Item {
 
-        /**
-         * SPU 编号
-         */
+        @NotNull(message = "SPU 编号不能为空")
         private Long spuId;
-        /**
-         * SKU 编号
-         */
+
+        @NotNull(message = "SKU 编号活动商品不能为空")
         private Long skuId;
-        /**
-         * 购买数量
-         */
+
+        @NotNull(message = "购买数量不能为空")
         private Integer count;
 
     }

+ 2 - 19
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApiImpl.java

@@ -1,15 +1,10 @@
 package cn.iocoder.yudao.module.promotion.api.bargain;
 
-import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.BargainActivityUpdateReqVO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
 import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.BARGAIN_ACTIVITY_NOT_EXISTS;
-
 /**
  * 砍价活动 Api 接口实现类
  *
@@ -22,20 +17,8 @@ public class BargainActivityApiImpl implements BargainActivityApi {
     private BargainActivityService bargainActivityService;
 
     @Override
-    public void updateBargainActivityStock(Long activityId, Integer count) {
-        // TODO @puhui999:可以整个实现到 bargainActivityService 中
-        // 查询砍价活动
-        BargainActivityDO activity = bargainActivityService.getBargainActivity(activityId);
-        if (activity == null) {
-            throw exception(BARGAIN_ACTIVITY_NOT_EXISTS);
-        }
-
-        // 更新砍价库存
-        // TODO @puhui999:考虑下并发更新问题
-        BargainActivityUpdateReqVO reqVO = new BargainActivityUpdateReqVO();
-        reqVO.setId(activityId);
-        reqVO.setStock(activity.getStock() - count);
-        bargainActivityService.updateBargainActivity(reqVO);
+    public void updateBargainActivityStock(Long id, Integer count) {
+        bargainActivityService.updateBargainActivityStock(id, count);
     }
 
 }

+ 1 - 60
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java

@@ -1,20 +1,10 @@
 package cn.iocoder.yudao.module.promotion.api.seckill;
 
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
-import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillProductDO;
 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.SECKILL_ACTIVITY_UPDATE_STOCK_FAIL;
 
 /**
  * 秒杀活动接口 Api 接口实现类
@@ -27,58 +17,9 @@ public class SeckillActivityApiImpl implements SeckillActivityApi {
     @Resource
     private SeckillActivityService activityService;
 
-    // TODO @puhui:建议这块弄到 activityService 实现哈;
-    // TODO @puhui:这个方法,要考虑事务性
     @Override
     public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
-        // TODO @puhui999:长方法,最好有 1.1 1.2 2.1 这种步骤哈;
-        SeckillActivityDO seckillActivity = activityService.getSeckillActivity(updateStockReqDTO.getActivityId());
-        if (seckillActivity.getStock() < updateStockReqDTO.getCount()) {
-            throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
-        }
-        // 获取活动商品
-        // TODO @puhui999:在一个方法里,dos 和 dolist 最好保持一致,要么用 s,要么用 list 哈;
-        List<SeckillProductDO> productDOs = activityService.getSeckillProductListByActivityId(updateStockReqDTO.getActivityId());
-        // TODO @puhui999:这个是不是搞成  CollectionUtils.convertMultiMap()
-        List<SeckillActivityUpdateStockReqDTO.Item> items = updateStockReqDTO.getItems();
-        Map<Long, List<Long>> map = new HashMap<>();
-        items.forEach(item -> {
-            if (map.containsKey(item.getSpuId())) {
-                List<Long> skuIds = map.get(item.getSpuId());
-                skuIds.add(item.getSkuId());
-                map.put(item.getSpuId(), skuIds);
-            } else {
-                List<Long> list = new ArrayList<>();
-                list.add(item.getSkuId());
-                map.put(item.getSpuId(), list);
-            }
-        });
-        // 过滤出购买的商品
-        // TODO @puhui999:productDOList 可以简化成 productList;一般来说,do 之类不用带着哈,在变量里;
-        List<SeckillProductDO> productDOList = CollectionUtils.filterList(productDOs, item -> map.get(item.getSpuId()).contains(item.getSkuId()));
-        Map<Long, SeckillActivityUpdateStockReqDTO.Item> productDOMap = CollectionUtils.convertMap(items, SeckillActivityUpdateStockReqDTO.Item::getSkuId, p -> p);
-        // 检查活动商品库存是否充足
-        // TODO @puhui999:避免 b 这种无业务含义的变量;
-        boolean b = CollectionUtils.anyMatch(productDOList, item -> {
-            SeckillActivityUpdateStockReqDTO.Item item1 = productDOMap.get(item.getSkuId());
-            return (item.getStock() < item1.getCount()) || (item.getStock() - item1.getCount()) < 0;
-        });
-        if (b) {
-            throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
-        }
-        // TODO @puhui999:类似 doList,应该和下面的 update 逻辑粘的更紧密一点;so 在空行的时候,应该挪到 74 之后里去;甚至更合理,应该是 79 之后;说白了,逻辑要分块,每个模块涉及的代码要紧密在一起;
-        List<SeckillProductDO> doList = CollectionUtils.convertList(productDOList, item -> {
-            item.setStock(item.getStock() - productDOMap.get(item.getSkuId()).getCount());
-            return item;
-        });
-
-        // 更新活动库存
-        // TODO @puhui999:考虑下并发更新
-        seckillActivity.setStock(seckillActivity.getStock() + updateStockReqDTO.getCount());
-        seckillActivity.setTotalStock(seckillActivity.getTotalStock() - updateStockReqDTO.getCount());
-        activityService.updateSeckillActivity(seckillActivity);
-        // 更新活动商品库存
-        activityService.updateSeckillActivityProductList(doList);
+        activityService.updateSeckillStock(updateStockReqDTO);
     }
 
 }

+ 8 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityService.java

@@ -30,6 +30,14 @@ public interface BargainActivityService {
      */
     void updateBargainActivity(@Valid BargainActivityUpdateReqVO updateReqVO);
 
+    /**
+     * 更新砍价活动库存
+     *
+     * @param id    砍价活动编号
+     * @param count 购买数量
+     */
+    void updateBargainActivityStock(Long id, Integer count);
+
     /**
      * 删除砍价活动
      *
@@ -53,5 +61,4 @@ public interface BargainActivityService {
      */
     PageResult<BargainActivityDO> getBargainActivityPage(BargainActivityPageReqVO pageReqVO);
 
-
 }

+ 19 - 0
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 {
     private ProductSkuApi productSkuApi;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createBargainActivity(BargainActivityCreateReqVO createReqVO) {
         // 校验商品 SPU 是否存在是否参加的别的活动
         validateBargainConflict(createReqVO.getSpuId(), null);
@@ -53,6 +54,7 @@ public class BargainActivityServiceImpl implements BargainActivityService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateBargainActivity(BargainActivityUpdateReqVO updateReqVO) {
         // 校验存在
         BargainActivityDO activityDO = validateBargainActivityExists(updateReqVO.getId());
@@ -70,6 +72,23 @@ public class BargainActivityServiceImpl implements BargainActivityService {
         bargainActivityMapper.updateById(updateObj);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateBargainActivityStock(Long id, Integer count) {
+        // 查询砍价活动
+        BargainActivityDO activity = getBargainActivity(id);
+        if (activity == null) {
+            throw exception(BARGAIN_ACTIVITY_NOT_EXISTS);
+        }
+
+        // 更新砍价库存
+        // TODO @puhui999:考虑下并发更新问题
+        BargainActivityUpdateReqVO reqVO = new BargainActivityUpdateReqVO();
+        reqVO.setId(id);
+        reqVO.setStock(activity.getStock() - count);
+        //bargainActivityService.updateBargainActivity(reqVO);
+    }
+
     private void validateBargainConflict(Long spuId, Long activityId) {
         // 查询所有开启的砍价活动
         List<BargainActivityDO> activityList = bargainActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());

+ 7 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.promotion.service.seckill;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
@@ -40,6 +41,12 @@ public interface SeckillActivityService {
      */
     void updateSeckillActivity(SeckillActivityDO activityDO);
 
+    /**
+     * 更新秒杀库存
+     *
+     * @param updateStockReqDTO 更新信息
+     */
+    void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO);
     /**
      * 更新秒杀活动商品
      *

+ 35 - 0
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;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityUpdateStockReqDTO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO;
@@ -149,6 +150,40 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
         seckillActivityMapper.updateById(activityDO);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateSeckillStock(SeckillActivityUpdateStockReqDTO updateStockReqDTO) {
+        // 1、校验秒杀活动是否存在
+        SeckillActivityDO seckillActivity = getSeckillActivity(updateStockReqDTO.getActivityId());
+        // 1.1、校验库存是否充足
+        if (seckillActivity.getStock() < updateStockReqDTO.getCount()) {
+            throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
+        }
+
+        // 2、更新活动库存
+        // TODO @puhui999:考虑下并发更新
+        seckillActivity.setStock(seckillActivity.getStock() + updateStockReqDTO.getCount());
+        seckillActivity.setTotalStock(seckillActivity.getTotalStock() - updateStockReqDTO.getCount());
+        updateSeckillActivity(seckillActivity);
+
+        // 3、获取活动商品
+        List<SeckillProductDO> products = getSeckillProductListByActivityId(updateStockReqDTO.getActivityId());
+        // 3.1、过滤出购买的商品
+        SeckillProductDO product = findFirst(products, item -> ObjectUtil.equal(updateStockReqDTO.getItem().getSkuId(), item.getSkuId()));
+        // 3.2、检查活动商品库存是否充足
+        boolean isSufficient = product == null || (product.getStock() == 0 || (product.getStock() < updateStockReqDTO.getItem().getCount()) || (product.getStock() - updateStockReqDTO.getItem().getCount()) < 0);
+        if (isSufficient) {
+            throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
+        }
+
+        // 4、更新活动商品库存
+        SeckillProductDO updateProduct = new SeckillProductDO();
+        updateProduct.setId(product.getId());
+        updateProduct.setStock(product.getStock() - updateStockReqDTO.getItem().getCount());
+        // TODO @puhui999:考虑下并发更新
+
+    }
+
     @Override
     public void updateSeckillActivityProductList(List<SeckillProductDO> productList) {
         seckillProductMapper.updateBatch(productList);

+ 3 - 4
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 {
     public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) {
         // 查询订单
         TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id);
-        // TODO @puhui999:这里建议改成,如果为 null,直接返回 success null;主要查询操作,尽量不要有非空的提示哈;交给前端处理;
-//        if (order == null) {
-//            return success(null, ORDER_NOT_FOUND.getMsg());
-//        }
+        if (order == null) {
+            return success(null);
+        }
 
         // 查询订单项
         List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId());

+ 15 - 11
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 {
         Integer count = getSumValue(orderItems, TradeOrderItemDO::getCount, Integer::sum);
         // 1)如果是秒杀商品:额外扣减秒杀的库存;
         if (Objects.equals(TradeOrderTypeEnum.SECKILL.getType(), tradeOrderDO.getType())) {
-            SeckillActivityUpdateStockReqDTO updateStockReqDTO = new SeckillActivityUpdateStockReqDTO();
-            updateStockReqDTO.setActivityId(createReqVO.getSeckillActivityId());
-            updateStockReqDTO.setCount(count);
-            updateStockReqDTO.setItems(CollectionUtils.convertList(orderItems, item -> {
-                SeckillActivityUpdateStockReqDTO.Item item1 = new SeckillActivityUpdateStockReqDTO.Item();
-                item1.setSpuId(item.getSpuId());
-                item1.setSkuId(item.getSkuId());
-                item1.setCount(item.getCount());
-                return item1;
-            }));
-            seckillActivityApi.updateSeckillStock(updateStockReqDTO);
+            seckillActivityApi.updateSeckillStock(getSeckillActivityUpdateStockReqDTO(createReqVO, orderItems, count));
         }
         // 2)如果是砍价活动:额外扣减砍价的库存;
         bargainActivityApi.updateBargainActivityStock(createReqVO.getBargainActivityId(), count);
@@ -351,6 +341,20 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         // 增加订单日志 TODO 芋艿:待实现
     }
 
+    private SeckillActivityUpdateStockReqDTO getSeckillActivityUpdateStockReqDTO(AppTradeOrderCreateReqVO createReqVO, List<TradeOrderItemDO> orderItems, Integer count) {
+        SeckillActivityUpdateStockReqDTO updateStockReqDTO = new SeckillActivityUpdateStockReqDTO();
+        updateStockReqDTO.setActivityId(createReqVO.getSeckillActivityId());
+        updateStockReqDTO.setCount(count);
+        // 秒杀活动只能选择一个商品
+        TradeOrderItemDO item = orderItems.get(0);
+        SeckillActivityUpdateStockReqDTO.Item item1 = new SeckillActivityUpdateStockReqDTO.Item();
+        item1.setSpuId(item.getSpuId());
+        item1.setSkuId(item.getSkuId());
+        item1.setCount(item.getCount());
+        updateStockReqDTO.setItem(item1);
+        return updateStockReqDTO;
+    }
+
     private void createPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems, TradePriceCalculateRespBO calculateRespBO) {
         // 创建支付单,用于后续的支付
         PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert(