Description or Example
# 知识点
## 锁定库存的基本思路
> 1. 首先先获取商品有库存的仓库
> 2. 然后继续判断该仓库是否有足够的库存
## 为什么需要借助异常机制
> 因为并不是所有的商品都具有库存, 有可能前几个有, 后几个没有, 因此, 为了避免最终有些被扣除, 有些没有被扣除, 我们引入异常机制, 来保证整体的ACID特性, 要么全部能扣除, 要么全都不可以
## 为什么需要工作单和工作单详情
> 因为我们采取了最终一致性+可靠消息来保证数据一致性, 因此, 我们需要自己回滚, 为了记录我们干过什么操作, 因此需要工作单和工作单详情
> 换而言之, 我们不能然一个失败的订单扣库存
# 核心代码
```java
@RequestMapping("/lock/sku/stock")
public R lockSkuStock(@RequestBody WareLockTO wareLockTO) {
try {
Boolean isSuccess = wareInfoService.lockSkuStock(wareLockTO);
if (isSuccess) return R.ok();
else throw new StockException("没有足够的库存");
} catch (StockException e) {
// 没有库存
return R.error(WareConstant.StockEnum.STOCK_NOT_ENOUGH.getCode(),
WareConstant.StockEnum.STOCK_NOT_ENOUGH.getMsg());
}
}
```
```java
@Override
@Transactional
public Boolean lockSkuStock(WareLockTO wareLockTO) {
// 0. 首先保存工作单
WareOrderTaskEntity orderTask = new WareOrderTaskEntity().setOrderSn(wareLockTO.getOrderSn());
wareOrderTaskService.save(orderTask);
List<WareOrderTaskDetailEntity> detailEntities = new ArrayList<>();
List<WareLockTO.OrderItemInfo> orderItemInfoList = wareLockTO.getOrderItemInfoList();
if (orderItemInfoList != null && !orderItemInfoList.isEmpty()) { // 有订单需要锁定库存
// 1. 找各个商品有库存的仓库, 并封装对应的对象
List<SkuStockInfo> stockInfos = orderItemInfoList.stream().map(orderItemInfo -> {
SkuStockInfo skuStockInfo = new SkuStockInfo();
Long skuId = orderItemInfo.getSkuId();
List<Long> wareIds = wareSkuService.getWareIdBySkuId(skuId);
return skuStockInfo
.setSkuId(skuId)
.setWareIds(wareIds)
.setNum(orderItemInfo.getNum());
}).collect(Collectors.toList());
for (SkuStockInfo stockInfo : stockInfos) { // 第一次遍历, 遍历每件商品
boolean isSuccess = false; // 布尔标识, 默认当前的商品没有库存
// 首先判断, 该商品是否有库存
if (stockInfo.wareIds == null || stockInfo.wareIds.isEmpty()) {
// 符合条件, 说明当前商品并没有库存
throw new StockException("当前商品没有足够的库存");
}
// 存在库存, 但是不知道是否足够, 继续判断
for (Long wareId : stockInfo.getWareIds()) {
if(wareSkuService.judgeWareIsEnough(stockInfo.skuId, wareId, stockInfo.getNum())) {
// 有足够的库存, 执行到这里, 锁定库存成功, 需要保存对应的工作单详情
detailEntities.add(new WareOrderTaskDetailEntity().setWareId(wareId)
.setSkuId(stockInfo.skuId)
.setSkuNum(stockInfo.num)
.setTaskId(orderTask.getId())
.setLockStatus(WareConstant.LockEnum.LOCKED.getCode()));
isSuccess = true;
break;
}
// 没有足够的库存, 继续尝试去锁定
}
if (!isSuccess) { // 当前商品真的没有库存
throw new StockException("当前商品没有足够的库存");
}
}
}
// 最后保存工作单详情
wareOrderTaskDetailService.saveBatch(detailEntities);
// 然后批量发消息
mqService.sendBatchMessage(detailEntities.stream().map(detailEntity -> {
Long detailId = detailEntity.getId();
Long taskId = orderTask.getId();
return new WareTO().setTaskDetailId(detailId).setTaskId(taskId);
}).collect(Collectors.toList()));
return true;
}
```
```xml
<select id="getHashStockWareBySkuId" resultType="java.lang.Long">
SELECT ware_id FROM wms_ware_sku WHERE sku_id = #{skuId} AND stock - stock_locked > 0
</select>
<update id="judgeWareIsEnough">
UPDATE wms_ware_sku SET stock_locked = stock_locked + #{num}
WHERE stock - stock_locked >= #{num} AND ware_id = #{wareId} AND sku_id = #{skuId}
</update>
```