✅P282_商城业务-订单服务-锁定库存
锁库存流程
锁定库存流程如下图所示
编写VO
锁定库存VO
cfmall-order/src/main/java/com/gyz/cfmall/order/vo/WareSkuLockVo.java
@Data
public class WareSkuLockVo {
/**
* 订单编号
*/
private String orderSn;
/**
* 需要锁住的所有库存信息
*/
private List<OrderItemVo> locks;
}
将订单服务cfmall-order
中的WareSkuLockVo与OrderItemVo复制到库存服务cfmall-ware
中
锁定库存结果VO
cfmall-ware/src/main/java/com/gyz/cfmall/ware/vo/LockStockResultVo.java
@Data
public class LockStockResultVo {
private Long skuId;
private Integer num;
/**
* 是否锁定成功
**/
private Boolean locked;
}
库存异常类与状态码
cfmall-common/src/main/java/com/gyz/common/exception/NoStockException.java
public class NoStockException extends RuntimeException {
@Getter
@Setter
private Long skuId;
public NoStockException(Long skuId) {
super("商品id:" + skuId + "库存不足!");
}
public NoStockException(String msg) {
super(msg);
}
}
cfmall-common/src/main/java/com/gyz/common/exception/BizCodeEnum.java
public enum BizCodeEnum {
/**
* 状态码枚举
*/
NO_STOCK_EXCEPTION(21000,"商品库存不足"),
//省略其它代码
}
锁库存字段设置默认值:0 -> 表:**wms_ware_sku**
,字段**stock_locked**
锁定库存
cfmall-ware/src/main/java/com/gyz/cfmall/ware/controller/WareSkuController.java
@Resource
private WareSkuService wareSkuService;
/**
* 锁定库存
*
* @param vo 库存解锁的场景
* 1)、下订单成功,订单过期没有支付被系统自动取消或者被用户手动取消,都要解锁库存
* 2)、下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁
* 3)、
*/
@PostMapping(value = "/lock/order")
public R orderLockStock(@RequestBody WareSkuLockVo vo) {
try {
boolean lockStock = wareSkuService.orderLockStock(vo);
return R.ok().setData(lockStock);
} catch (NoStockException e) {
return R.error(NO_STOCK_EXCEPTION.getCode(), NO_STOCK_EXCEPTION.getMessage());
}
}
cfmall-ware/src/main/java/com/gyz/cfmall/ware/service/impl/WareSkuServiceImpl.java
@Transactional(rollbackFor = NoStockException.class)
@Override
public boolean orderLockStock(WareSkuLockVo vo) {
//1、按照下单的收货地址,找到一个就近仓库,锁定库存
//2、找到每个商品在哪个仓库都有库存
List<OrderItemVo> locks = vo.getLocks();
List<SkuWareHasStock> collect = locks.stream().map((item) -> {
SkuWareHasStock stock = new SkuWareHasStock();
Long skuId = item.getSkuId();
stock.setSkuId(skuId);
stock.setNum(item.getCount());
//查询这个商品在哪个仓库有库存
List<Long> wareIdList = wareSkuDao.listWareIdHasSkuStock(skuId);
stock.setWareId(wareIdList);
return stock;
}).collect(Collectors.toList());
//2、锁定库存
for (SkuWareHasStock hasStock : collect) {
boolean skuStocked = false;
Long skuId = hasStock.getSkuId();
List<Long> wareIds = hasStock.getWareId();
if (org.springframework.util.StringUtils.isEmpty(wareIds)) {
//没有任何仓库有这个商品的库存
throw new NoStockException(skuId);
}
//1、如果每一个商品都锁定成功,将当前商品锁定了几件的工作单记录发给MQ
//2、锁定失败。前面保存的工作单信息都回滚了。发送出去的消息,即使要解锁库存,由于在数据库查不到指定的id,所有就不用解锁
for (Long wareId : wareIds) {
//锁定成功就返回1,失败就返回0
Long count = wareSkuDao.lockSkuStock(skuId, wareId, hasStock.getNum());
if (count == 1) {
skuStocked = true;
break;
}
}
if (!skuStocked) {
//当前商品所有仓库都没有锁住
throw new NoStockException(skuId);
}
}
//3、肯定全部都是锁定成功的
return true;
}
SkuWareHasStock内部类
@Data
static class SkuWareHasStock {
private Long skuId;
private Integer num;
private List<Long> wareId;
}
cfmall-ware/src/main/resources/mapper.ware/WareSkuDao.xml
<select id="listWareIdHasSkuStock" resultType="java.lang.Long">
SELECT ware_id
FROM wms_ware_sku
WHERE sku_id = #{skuId}
AND stock - stock_locked > 0
</select>
<update id="lockSkuStock">
UPDATE wms_ware_sku
SET stock_locked = stock_locked + #{num}
WHERE sku_id = #{skuId}
AND ware_id = #{wareId}
AND stock - stock_locked > #{num}
</update>
提交订单业务
提交订单调用锁库存操作
Feign
cfmall-order/src/main/java/com/gyz/cfmall/order/feign/WmsFeignService.java
@FeignClient("cfmall-ware")
public interface WmsFeignService {
/**
* 锁定库存
*
* @param vo 锁定库存VO
* @return true/false
*/
@PostMapping(value = "/ware/waresku/lock/order")
R orderLockStock(@RequestBody WareSkuLockVo vo);
}
实现逻辑
提交订单完整方法:
@Transactional(rollbackFor = Exception.class)
@Override
public SubmitOrderResponseVo submitOrder(OrderSubmitVo orderSubmitVo) {
SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();
responseVo.setCode(0);
MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
String token = orderSubmitVo.getOrderToken();
//脚本执行的返回结果: 0-代表令牌校验失败, 1-代表令牌成功删除即成功
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//Arg1:用DefaultRedisScript的构造器封装脚本和返回值类型; Arg2:用于存放Redis中token的key;Arg3:用于比较的token,即浏览器存储的token
//令牌的验证和删除必须保证原子性
Long response = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(OrderConstant.USER_ORDER_TOKEN_PREFIX.toString() + memberResponseVo.getId()),
token);
if (response != null && response == 0) {
responseVo.setCode(1);
return responseVo;
} else {
//令牌校验成功,去创建订单,校验价格,所库存
submitVoThreadLocal.set(orderSubmitVo);
//1、创建订单、订单项等信息
OrderCreateTo order = this.createOrder();
//2、验证价格
BigDecimal payAmount = order.getOrder().getPayAmount();
double substractValue = 0.01;
if (Math.abs(orderSubmitVo.getPayPrice().subtract(payAmount).doubleValue()) < substractValue) {
//3、保存订单
this.saveOrder(order);
//4、库存锁定,只要有异常,回滚订单数据
//订单号、所有订单项信息(skuId,skuNum,skuName)
WareSkuLockVo lockVo = new WareSkuLockVo();
lockVo.setOrderSn(order.getOrder().getOrderSn());
//获取出要锁定的商品数据信息
List<OrderItemVo> orderItemVos = order.getOrderItems().stream().map((item) -> {
OrderItemVo orderItemVo = new OrderItemVo();
orderItemVo.setSkuId(item.getSkuId());
orderItemVo.setCount(item.getSkuQuantity());
orderItemVo.setTitle(item.getSkuName());
return orderItemVo;
}).collect(Collectors.toList());
lockVo.setLocks(orderItemVos);
R r = wmsFeignService.orderLockStock(lockVo);
if (r.getCode() == 0) {
//锁定成功
responseVo.setOrder(order.getOrder());
} else {
//库存锁定失败
responseVo.setCode(3);
}
return responseVo;
} else {
responseVo.setCode(2);
return responseVo;
}
}
}
向前端页面返回订单提交的数据响应
cfmall-order/src/main/java/com/gyz/cfmall/order/web/OrderWebController.java
@Resource
private OrderService orderService;
@PostMapping("/submitOrder")
public String submitOrder(OrderSubmitVo orderSubmitVo, Model model) {
SubmitOrderResponseVo responseVo = orderService.submitOrder(orderSubmitVo);
if (responseVo.getCode() == 0) {
model.addAttribute("submitOrderResp", responseVo);
//下单成功,支付
return "pay";
}
//下单失败,重新下单
return "redirect:http://order.cfmall.com/toTrade";
}