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

This commit is contained in:
puhui999 2023-09-10 11:06:39 +08:00
parent c023209aa5
commit 57f0ea04f7
10 changed files with 104 additions and 121 deletions

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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 @puhui999productDOList 可以简化成 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);
}
}

View File

@ -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);
}

View File

@ -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());

View File

@ -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);
/**
* 更新秒杀活动商品
*

View File

@ -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);

View File

@ -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());

View File

@ -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(