rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);
package com.saodai.saodaimall.ware.listener; import com.rabbitmq.client.Channel; import com.saodai.common.to.OrderTo; import com.saodai.common.to.mq.StockLockedTo; import com.saodai.saodaimall.ware.service.WareSkuService; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; /** * RabbitMQ的监听器 * 这里有两个监听方法,这两个监听识别的依据是看传入的是StockLockedTo还是OrderTo * 一个是监听的库存自动解锁 * 一个是监听订单取消后库存解锁 */ @Slf4j @RabbitListener(queues = "stock.release.stock.queue") @Service public class StockReleaseListener { @Autowired private WareSkuService wareSkuService; /** ** 监听库存自动解锁 */ @RabbitHandler public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException { log.info("******收到解锁库存的信息******"); try { System.out.println("******收到解锁库存的信息******"); //当前消息是否被第二次及以后(重新)派发过来了 // Boolean redelivered = message.getMessageProperties().getRedelivered(); //解锁库存 wareSkuService.unlockStock(to); // 手动删除消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { // 解锁失败 将消息重新放回队列,让别人消费 channel.basicReject(message.getMessageProperties().getDeliveryTag(),true); } } /** * * 防止订单服务卡顿,导致订单状态消息一直改不了,库存优先到期,查订单状态新建,什么都不处理 *导致卡顿的订单,永远都不能解锁库存 * 订单释放直接和库存释放进行绑定 * @param orderTo * @param message * @param channel * @throws IOException */ @RabbitHandler public void handleOrderCloseRelease(OrderTo orderTo, Message message, Channel channel) throws IOException { log.info("******收到订单关闭,准备解锁库存的信息******"); try { wareSkuService.unlockStock(orderTo); // 手动删除消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { // 解锁失败 将消息重新放回队列,让别人消费 channel.basicReject(message.getMessageProperties().getDeliveryTag(),true); } } }
package com.saodai.common.to.mq; import lombok.Data; /** * 发送到mq消息队列的to **/ @Data public class StockLockedTo { /** 库存工作单的id **/ private Long id; /** 工作单详情的所有信息 StockDetailTo对象内容就是上面的WareOrderTaskDetailEntity **/ private StockDetailTo detailTo; }
解锁库存的思路
首先查询数据库的库存详细工作单表看看有没有成功锁定库存(如果成功锁库存了会有对应的一条记录),如果没有那就说明库存没有锁成功,那自然就不需要解锁了
库存详细工作单表有这条记录那就证明库存锁定成功了
具体需不需要解库存还要先看订单状态
先查询有没有这个订单,没有这个订单必须解锁库存(可能出现因为有异常造成的数据回滚导致订单不存在的情况,但是库存锁成功了)
有这个订单,不一定解锁库存,要根据订单的状态来决定是否解库存
订单状态是已取消状态,说明是用户没有支付订单过期了,那就必须解锁库存
订单状态是已支付状态,说明是用户支付成功了,那就不能解锁库存
除了判断上面的情况,还有考虑当前库存详细工作单的状态,只有满足订单状态是已取消状态并且是已锁定的状态那才可以解库存
已锁定:解锁库存
已解锁 :不能再解锁
/** * (这个方法是由StockReleaseListener监听器调用的) * 锁库存失败后的自动解锁(也就是回溯) * @param to */ @Override public void unlockStock(StockLockedTo to) { //获取库存详细工作单类 StockDetailTo detail = to.getDetailTo(); //库存详细工作单的id Long detailId = detail.getId(); //WareOrderTaskDetailEntity是库存详细工作单类 WareOrderTaskDetailEntity taskDetailInfo = wareOrderTaskDetailService.getById(detailId); if (taskDetailInfo != null) { //查出wms_ware_order_task工作单的信息 Long id = to.getId(); //订单锁库存工作单(获取哪个订单要锁库存) WareOrderTaskEntity orderTaskInfo = wareOrderTaskService.getById(id); //获取订单号查询订单状态 String orderSn = orderTaskInfo.getOrderSn(); //远程查询订单信息 R orderData = orderFeignService.getOrderStatus(orderSn); if (orderData.getCode() == 0) { //订单数据返回成功 OrderVo orderInfo = orderData.getData("data", new TypeReference<OrderVo>() {}); /** * CREATE_NEW(0,"待付款"), * PAYED(1,"已付款"), * SENDED(2,"已发货"), * RECIEVED(3,"已完成"), * CANCLED(4,"已取消"), * SERVICING(5,"售后中"), * SERVICED(6,"售后完成"); */ //订单不存在(因为有异常造成的数据回滚导致订单不存在)或者订单状态是取消状态(orderInfo.getStatus() == 4)才可解库存 if (orderInfo == null || orderInfo.getStatus() == 4) { //当前库存工作单详情状态1,已锁定,只有当前库存工作单详情状态未解锁才可以解锁 if (taskDetailInfo.getLockStatus() == 1) { //调用真正接库存的方法unLockStock unLockStock(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detailId); } } } else { //消息拒绝以后重新放在队列里面,让别人继续消费解锁 //远程调用服务失败 throw new RuntimeException("远程调用服务失败"); } } else { //无需解锁 } } /** * 真正解锁库存的方法(自动解库存) * @param skuId 需要解锁库存的商品id * @param wareId 需要解锁库存的库存仓库id * @param num 需要解锁库存的商品数量 * @param taskDetailId 库存工作单详情id */ public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁(其实就是修改wms_ware_sku表中的stock_locked的值,之前锁库存锁了多少个就减去多少个) wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //setLockStatus(2)表示变为已解锁(1表示已锁定,2表示已解锁,3表示减扣) taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); } /** * 订单取消了就立马解库存 * 防止订单服务卡顿,导致订单状态消息一直改不了,库存优先到期,查订单状态新建,什么都不处理 * 导致卡顿的订单,永远都不能解锁库存 * @param orderTo */ @Transactional(rollbackFor = Exception.class) @Override public void unlockStock(OrderTo orderTo) { String orderSn = orderTo.getOrderSn(); //查一下最新的库存解锁状态,防止重复解锁库存 WareOrderTaskEntity orderTaskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn); //按照工作单的id找到所有 没有解锁的库存,进行解锁(lock_status=1表示已锁定库存) Long id = orderTaskEntity.getId(); List<WareOrderTaskDetailEntity> list = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>() .eq("task_id", id).eq("lock_status", 1)); for (WareOrderTaskDetailEntity taskDetailEntity : list) { //解锁库存 unLockStock(taskDetailEntity.getSkuId(), taskDetailEntity.getWareId(), taskDetailEntity.getSkuNum(), taskDetailEntity.getId()); } }
自动解库存
/** * (这个方法是由StockReleaseListener监听器调用的) * 锁库存失败后的自动解锁(也就是回溯) * @param to */ @Override public void unlockStock(StockLockedTo to) { //获取库存详细工作单类 StockDetailTo detail = to.getDetailTo(); //库存详细工作单的id Long detailId = detail.getId(); //WareOrderTaskDetailEntity是库存详细工作单类 WareOrderTaskDetailEntity taskDetailInfo = wareOrderTaskDetailService.getById(detailId); if (taskDetailInfo != null) { //查出wms_ware_order_task工作单的信息 Long id = to.getId(); //订单锁库存工作单(获取哪个订单要锁库存) WareOrderTaskEntity orderTaskInfo = wareOrderTaskService.getById(id); //获取订单号查询订单状态 String orderSn = orderTaskInfo.getOrderSn(); //远程查询订单信息 R orderData = orderFeignService.getOrderStatus(orderSn); if (orderData.getCode() == 0) { //订单数据返回成功 OrderVo orderInfo = orderData.getData("data", new TypeReference<OrderVo>() {}); /** * CREATE_NEW(0,"待付款"), * PAYED(1,"已付款"), * SENDED(2,"已发货"), * RECIEVED(3,"已完成"), * CANCLED(4,"已取消"), * SERVICING(5,"售后中"), * SERVICED(6,"售后完成"); */ //订单不存在(因为有异常造成的数据回滚导致订单不存在)或者订单状态是取消状态(orderInfo.getStatus() == 4)才可解库存 if (orderInfo == null || orderInfo.getStatus() == 4) { //当前库存工作单详情状态1,已锁定,只有当前库存工作单详情状态未解锁才可以解锁 if (taskDetailInfo.getLockStatus() == 1) { //调用真正接库存的方法unLockStock unLockStock(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detailId); } } } else { //消息拒绝以后重新放在队列里面,让别人继续消费解锁 //远程调用服务失败 throw new RuntimeException("远程调用服务失败"); } } else { //无需解锁 } } /** * 真正解锁库存的方法(自动解库存) * @param skuId 需要解锁库存的商品id * @param wareId 需要解锁库存的库存仓库id * @param num 需要解锁库存的商品数量 * @param taskDetailId 库存工作单详情id */ public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁(其实就是修改wms_ware_sku表中的stock_locked的值,之前锁库存锁了多少个就减去多少个) wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //setLockStatus(2)表示变为已解锁(1表示已锁定,2表示已解锁,3表示减扣) taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); }
- 自动解库存的具体实现流程
- 获取库存详细工作单的id
package com.saodai.common.to.mq; import lombok.Data; /** * 发送到mq消息队列的to **/ @Data public class StockLockedTo { /** 库存工作单的id **/ private Long id; /** 工作单详情的所有信息 **/ private StockDetailTo detailTo; } package com.saodai.common.to.mq; import lombok.Data; /** * 其实就是库存工作单详情实体类(具体给订单的哪个商品锁库存) **/ @Data public class StockDetailTo { private Long id; /** * sku_id */ private Long skuId; /** * sku_name */ private String skuName; /** * 购买个数 */ private Integer skuNum; /** * 工作单id */ private Long taskId; /** * 仓库id */ private Long wareId; /** * 锁定状态 */ private Integer lockStatus; }
- 查询数据库有没有这个库存详细工作单类
package com.saodai.saodaimall.ware.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * 库存工作单详情(具体给订单的哪个商品锁库存) */ @NoArgsConstructor @AllArgsConstructor @Builder @Data @TableName("wms_ware_order_task_detail") public class WareOrderTaskDetailEntity implements Serializable { private static final long serialVersionUID = 1L; /** * id */ @TableId private Long id; /** * sku_id */ private Long skuId; /** * sku_name */ private String skuName; /** * 购买个数 */ private Integer skuNum; /** * 工作单id */ private Long taskId; /** * 仓库id */ private Long wareId; /** * 锁定状态 */ private Integer lockStatus; }
- 查询订单锁库存工作单(获取哪个订单要锁库存)
package com.saodai.saodaimall.ware.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * 订单锁库存工作单(表示我准备要给哪个订单锁库存了) */ @Data @TableName("wms_ware_order_task") public class WareOrderTaskEntity implements Serializable { private static final long serialVersionUID = 1L; /** * id */ @TableId private Long id; /** * order_id */ private Long orderId; /** * order_sn */ private String orderSn; /** * 收货人 */ private String consignee; /** * 收货人电话 */ private String consigneeTel; /** * 配送地址 */ private String deliveryAddress; /** * 订单备注 */ private String orderComment; /** * 付款方式【 1:在线付款 2:货到付款】 */ private Integer paymentWay; /** * 任务状态 */ private Integer taskStatus; /** * 订单描述 */ private String orderBody; /** * 物流单号 */ private String trackingNo; /** * create_time */ private Date createTime; /** * 仓库id */ private Long wareId; /** * 工作单备注 */ private String taskComment; }
- 根据订单号远程查询订单
package com.saodai.saodaimall.ware.vo; import lombok.Data; import java.math.BigDecimal; import java.util.Date; @Data public class OrderVo { private Long id; /** * member_id */ private Long memberId; /** * 订单号 */ private String orderSn; /** * 使用的优惠券 */ private Long couponId; /** * create_time */ private Date createTime; /** * 用户名 */ private String memberUsername; /** * 订单总额 */ private BigDecimal totalAmount; /** * 应付总额 */ private BigDecimal payAmount; /** * 运费金额 */ private BigDecimal freightAmount; /** * 促销优化金额(促销价、满减、阶梯价) */ private BigDecimal promotionAmount; /** * 积分抵扣金额 */ private BigDecimal integrationAmount; /** * 优惠券抵扣金额 */ private BigDecimal couponAmount; /** * 后台调整订单使用的折扣金额 */ private BigDecimal discountAmount; /** * 支付方式【1->支付宝;2->微信;3->银联; 4->货到付款;】 */ private Integer payType; /** * 订单来源[0->PC订单;1->app订单] */ private Integer sourceType; /** * 订单状态【0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单】 */ private Integer status; /** * 物流公司(配送方式) */ private String deliveryCompany; /** * 物流单号 */ private String deliverySn; /** * 自动确认时间(天) */ private Integer autoConfirmDay; /** * 可以获得的积分 */ private Integer integration; /** * 可以获得的成长值 */ private Integer growth; /** * 发票类型[0->不开发票;1->电子发票;2->纸质发票] */ private Integer billType; /** * 发票抬头 */ private String billHeader; /** * 发票内容 */ private String billContent; /** * 收票人电话 */ private String billReceiverPhone; /** * 收票人邮箱 */ private String billReceiverEmail; /** * 收货人姓名 */ private String receiverName; /** * 收货人电话 */ private String receiverPhone; /** * 收货人邮编 */ private String receiverPostCode; /** * 省份/直辖市 */ private String receiverProvince; /** * 城市 */ private String receiverCity; /** * 区 */ private String receiverRegion; /** * 详细地址 */ private String receiverDetailAddress; /** * 订单备注 */ private String note; /** * 确认收货状态[0->未确认;1->已确认] */ private Integer confirmStatus; /** * 删除状态【0->未删除;1->已删除】 */ private Integer deleteStatus; /** * 下单时使用的积分 */ private Integer useIntegration; /** * 支付时间 */ private Date paymentTime; /** * 发货时间 */ private Date deliveryTime; /** * 确认收货时间 */ private Date receiveTime; /** * 评价时间 */ private Date commentTime; /** * 修改时间 */ private Date modifyTime; }
进行双重判断
先判断订单不存在(因为有异常造成的数据回滚导致订单不存在)或者订单状态是取消状态
在判断当前库存工作单详情状态是不是1,1表示已锁定,只有当前库存工作单详情状态未解锁才可以解锁
调用unLockStock方法实现真正的解库存(自动解库存)
更新库存的数量(还原)
更新工作单的状态为已解锁
/** * 真正解锁库存的方法(自动解库存) * @param skuId 需要解锁库存的商品id * @param wareId 需要解锁库存的库存仓库id * @param num 需要解锁库存的商品数量 * @param taskDetailId 库存工作单详情id */ public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁(其实就是修改wms_ware_sku表中的stock_locked的值,之前锁库存锁了多少个就减去多少个) wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //setLockStatus(2)表示变为已解锁(1表示已锁定,2表示已解锁,3表示减扣) taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); }
<!-- 解锁库存--> <update id="unLockStock"> UPDATE wms_ware_sku SET stock_locked = stock_locked - #{num} WHERE sku_id = ${skuId} AND ware_id = #{wareId} </update>
手动解库存
- 订单服务的订单取消后立马解库存的具体逻辑
- 首先通过订单号查询订单锁库存工作单
- 通过订单锁库存工作单的id去库存详细工作单去找对应的锁库存的记录,看有没有记录并且锁库存的状态是已锁定的状态,防止多次重复解库存(其中库存详细工作单中的工作id的值就是订单锁库存工作单的id的值)
- 最后调用真正的解库存方法来解库存
/** * 真正解锁库存的方法(自动解库存) * @param skuId 需要解锁库存的商品id * @param wareId 需要解锁库存的库存仓库id * @param num 需要解锁库存的商品数量 * @param taskDetailId 库存工作单详情id */ public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁(其实就是修改wms_ware_sku表中的stock_locked的值,之前锁库存锁了多少个就减去多少个) wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //setLockStatus(2)表示变为已解锁(1表示已锁定,2表示已解锁,3表示减扣) taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); } /** * 订单取消了就立马解库存 * 防止订单服务卡顿,导致订单状态消息一直改不了,库存优先到期,查订单状态新建,什么都不处理 * 导致卡顿的订单,永远都不能解锁库存 * @param orderTo */ @Transactional(rollbackFor = Exception.class) @Override public void unlockStock(OrderTo orderTo) { String orderSn = orderTo.getOrderSn(); //查一下最新的库存解锁状态,防止重复解锁库存 WareOrderTaskEntity orderTaskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn); //按照工作单的id找到所有 没有解锁的库存,进行解锁(lock_status=1表示已锁定库存) Long id = orderTaskEntity.getId(); List<WareOrderTaskDetailEntity> list = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>() .eq("task_id", id).eq("lock_status", 1)); for (WareOrderTaskDetailEntity taskDetailEntity : list) { //解锁库存 unLockStock(taskDetailEntity.getSkuId(), taskDetailEntity.getWareId(), taskDetailEntity.getSkuNum(), taskDetailEntity.getId()); } }