消息队列流程图

监听库存解锁

下单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁

配置队列和交换机

@Configuration
public class MyRabbitConfig {/*** 使用json序列化机制,将消息转为json格式*/@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}/*** 模拟消费者监听队列*/
//    @RabbitListener(queues = "stock.release.stock.queue")
//    public void listener(Channel channel, Message message){//
//    }//声明交换机@Beanpublic Exchange stockEventExchange(){return new TopicExchange("stock-event-exchange",true,false,null);}//声明队列@Beanpublic Queue stockDelayQueue(){Map<String,Object> arguments = new HashMap<>();arguments.put("x-dead-letter-exchange","stock-event-exchange");arguments.put("x-dead-letter-routing-key","stock.release");arguments.put("x-message-ttl",120000);Queue queue = new Queue("stock.delay.queue",true,false,false,arguments);return queue;}@Beanpublic Queue stockReleaseStockQueue(){Queue queue = new Queue("stock.release.stock.queue",true,false,false,null);return queue;}//声明绑定@Beanpublic Binding stockDelayQueueBinding(){return new Binding("stock.delay.queue",Binding.DestinationType.QUEUE,"stock-event-exchange","stock.locked",null);}@Beanpublic Binding stockReleaseStockQueueBinding(){return new Binding("stock.release.stock.queue",Binding.DestinationType.QUEUE,"stock-event-exchange","stock.release.#",null);}
}

第一步:发送库存锁定成功消息到mq

告诉mq库存锁定成功,并在库存工作单里记录了哪个skuId商品,在哪个仓库wareId,扣除了多少数量num的物品

/*** 为某个订单锁定库存* 一个商品在多个仓库有库存,返回锁定成功还是失败的结果** @Transactional 保证事务可以失败后(抛异常)回滚。默认只要是运行时异常,都会回滚* <p>* 库存解锁的场景* 1、下订单成功,订单过期没有支付、被系统自动或手动取消,都要解锁库存* 2、下订单成功,库存锁定成功,接下来的业务失败,导致订单回滚,之前锁定的库存就要自动解锁*/@Transactional@Overridepublic Boolean orderLockStock(WareSkuLockVo vo) {//获取订单号String orderSn = vo.getOrderSn();//保存库存工作单的详情,声明哪个具体的订单要锁库存,订单号是唯一可识别WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();taskEntity.setOrderSn(orderSn);wareOrderTaskService.save(taskEntity);//TODO 1、找到每个商品在哪些仓库有库存//获取所有要锁库存的商品,订单号下所有的订单List<OrderItemVo> locks = vo.getLocks();//返回 列表(skuId、num、wareId) list(指定商品在不同库存的加购数量)List<SkuWareHasStock> skuWareHasStocks = locks.stream().map((item) -> {SkuWareHasStock stock = new SkuWareHasStock();Long skuId = item.getSkuId();stock.setSkuId(skuId);Integer num = item.getCount();stock.setNum(num);//查询skuId对应的wareId 表wms_ware_skuList<Long> wareIds = wareSkuDao.listWareIdHasSkuStock(skuId);stock.setWareId(wareIds);return stock;}).collect(Collectors.toList());//TODO 2、锁定库存//默认所有的库存都锁定Boolean allLock = true;for (SkuWareHasStock skuWareHasStock : skuWareHasStocks) {//默认skuId商品没锁定库存Boolean skuStocked = false;//获取要锁定库存的商品Long skuId = skuWareHasStock.getSkuId();//查询到此商品所在哪些仓库里List<Long> wareIds = skuWareHasStock.getWareId();if (wareIds == null || wareIds.size() == 0) {//如果指定skuId的商品没有仓库里有库存,就抛出异常throw new NoStockException(skuId);}//TODO 3、如果每一个商品都锁定成功,将当前商品锁定的工作单记录表发送给mq//如果有库存,遍历每一个有此skuId商品的仓库Idfor (Long wareId : wareIds) {//锁住库存,传入 商品id、仓库id、锁库存数量,指明哪个skuId的商品,要在哪个仓库,更新多少库存//返回0行受影响就是没成功,1行受影响就是成功Integer num = skuWareHasStock.getNum();Long count = wareSkuDao.lockSkuStock(skuId, wareId, num);if (count == 1) {//如果当前库存锁定成功skuStocked = true;//TODO 4、告诉mq库存锁定成功,并在库存工作单里记录了哪个skuId商品,在哪个仓库wareId,扣除了多少数量num的物品//封装库存订单详情表,并添加到数据库中,保存库存工作详情单WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity(null, skuId, "", num, taskEntity.getId(), wareId, 1);wareOrderTaskDetailService.save(detailEntity);// StockLockedTo 是 mq专用传输类//设置 【id】 封装工作单的实体类StockLockedTo lockedTo = new StockLockedTo();lockedTo.setId(taskEntity.getId());//设置【detailTo】  封装工作详情单的实体类StockDetailTo stockDetailTo = new StockDetailTo();//因为detailEntity和stockDetailTo 属性相同,可以对拷BeanUtils.copyProperties(detailEntity, stockDetailTo);// lockedTo 封装了 库存订单工作表id和详情表实体类lockedTo.setDetailTo(stockDetailTo);//mq发送,只要有一个库存锁定,就发送给mqrabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", lockedTo);//订单和库存都锁定成功了,就调出for循环break;} else {//如果当前仓库锁失败了,说明此仓库库存不足,就尝试下一个仓库}}//如果当前商品的所有仓库都没有锁住,说明所有仓库都没有此skuId商品的库存了if (skuStocked == false) {//如果指定skuId的商品没有仓库里有库存,就抛出异常throw new NoStockException(skuId);}}//如果上面整个for循环都没有抛出异常,说明是全部skuId的商品都锁定库存了return true;}//内部类@Getter@Setterclass SkuWareHasStock {private Long skuId; //商品private Integer num; //购买数量private List<Long> wareId; //仓库id}

第二步:mq监听队列消息

/*** 监听mq的消息,释放锁库存* 生产者发送消息--交换机--队列(延迟2分钟无人消费)--根据loutingKey和交换机到达--交换机--队列(被监听)**/
@RabbitListener(queues = "stock.release.stock.queue")
@Service
public class StockReleaseListener {@AutowiredWareSkuService wareSkuService;/*** 解锁库存* 当下订单后,订单表锁定库存成功、库存表锁定库存成功,但是30分钟内未支付,消费者监听队列,自动解锁库存* @param to  库存锁定成功后,通知mq库存锁定成功,并在库存工作单里记录了哪个skuId商品,在哪个仓库wareId,扣除了多少数量num的物品* @param message* @param channel* @throws IOException*/@RabbitHandlerpublic void handleStockLockRelease(StockLockedTo to, Message message, Channel channel) throws IOException {System.out.println("2分钟后收到解锁库存的消息,调用解锁方法");try {wareSkuService.unlockStock(to);/*** 手动应答,消费者读取完mq消息后,要给生产者一个答复,执行成功了就回复成功,不是批量回复* 1、消息的标记 tag* 2、是否批量应答 multiple false代表不批量应答信道中的消息  true代表批量*/channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {//有异常就拒绝消费,将消息继续扔到队列里,让别人消费channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}

第三步:库存解锁业务实现

根据mq监听队列得到的信息,用来解锁库存操作

/*** 库存自动解锁:* 下单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁*/@Overridepublic void unlockStock(StockLockedTo to) {//获取到库存详情表vo vo=实体类StockDetailTo detailTo = to.getDetailTo();//获取库存工作详情表的idLong detailId = detailTo.getId();//解锁//1.查询数据库关于这个订单的锁定库存信息// 有:证明锁定成功了//    解锁订单情况//        1.没有这个订单,不必解锁库存//        2.有这个订单。///           订单状态:已取消,解锁库存状态//                    没取消,不能解锁// 没有:库存锁定失败了,库存回滚了,这种情况无需解锁。//根据id查询数据库 库存工作详情表的实体类WareOrderTaskDetailEntity byId = wareOrderTaskDetailService.getById(detailId);//如果从数据库中可以查询出数据,说明库存已经被扣减,就需要解锁库存,释放和恢复仓库的商品数量if (byId != null) {//如果工作详情表有数据,就解锁//库存工作单的id   wms_ware_order_taskLong id = to.getId();//根据工作单id查询到工作表实体类WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(id);//从工作表中查询到订单号String orderSn = taskEntity.getOrderSn();//远程调用订单服务,根据订单号查询订单实体类,再查询状态,如果是取消状态,才可以解锁R r = orderFeignService.getOrderStatus(orderSn);if (r.getCode() == 0) {//订单数据返回成功OrderVo data = r.getData(new TypeReference<OrderVo>() {});//订单已经被取消或被取消状态或没有订单信息,就解锁库存if (data == null || data.getStatus() == 4) {//如果当前订单是库存已锁定,才可以解锁if (byId.getLockStatus() == 1) {Long skuId = detailTo.getSkuId();Long wareId = detailTo.getWareId();Integer skuNum = detailTo.getSkuNum();unLockStock(skuId, wareId, skuNum, detailId);}}} else {throw new RuntimeException("远程服务异常");}}}/*** 解锁库存* 哪个商品、哪个仓库、数量、库存工作详情表id*/public void unLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId) {//库存解锁wareSkuDao.unLockStock(skuId, wareId, num, taskDetailId);//更新库存工作详情表的状态为已解锁库存WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity();detailEntity.setId(taskDetailId);detailEntity.setLockStatus(2); //变为已解锁//根据id更新 lock_status 锁状态为 已解锁wareOrderTaskDetailService.updateById(detailEntity);}

定时关单

流程图分析

1、订单创建成功消息——loutingKey(order-create-order)——交换机(order-event-exchange)

/**
* TODO 订单创建成功给mq发送消息
* 默认订单锁定成功,库存锁定也成功,但后面的业务方法错误,因为有@Transactional本地事务,造成订单未下单回滚了,但是库存已经扣除了,没有回滚需要解锁库存(根据工作表和工作详情表),在库存服务里添加监听器监听队列,自动解锁库存
*/
rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",order.getOrder());

2、交换机(order-event-exchange)—— 延时队列(order-delay-queue)——交换机(order-event-exchange)

交换机将订单创建成功的消息发送到延时队列里,30分钟后自动又将订单创建成功的消息发送到交换机里

3、交换机(order-event-exchange)—— loutingKey(order-release-order)——普通队列(order-release-order-queue)

/*** 监听mq消息,自动关单*/
@RabbitListener(queues = "order.release.order.queue")
@Service
public class OrderCloseListener {@AutowiredOrderService orderService;@RabbitHandlerpublic void listener(OrderEntity entity, Channel channel, Message message) throws Exception{System.out.println("收到过期的订单信息,准备关闭订单"+entity.getOrderSn());try {orderService.closeOrder(entity);/*** 手动应答,消费者读取完mq消息后,要给生产者一个答复* 1、消息的标记 tag* 2、是否批量应答 multiple false代表不批量应答信道中的消息  true代表批量*/channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {//如果失败,信道就拒绝消费消息,将此消息扔到队列中,让其他人消费channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}
}//进入orderService.closeOrder(entity);
@Overridepublic void closeOrder(OrderEntity entity) {//查询当前数据库中订单的最新状态OrderEntity orderEntity = this.getById(entity.getId());//待付款状态需要关闭订单,状态0,待付款if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){//关单:将指定订单号的订单表的状态更改,因为数据库里的订单表和传过来的订单表状态可能不一样,所以要新建订单表OrderEntity update = new OrderEntity();update.setId(entity.getId());//设置订单为已取消订单状态,状态4,已关闭update.setStatus(OrderStatusEnum.CANCELED.getCode());//将订单状态更新到数据库this.updateById(update);

4、释放订单服务消息———— loutingKey(order-release-other)——交换机(order-event-exchange)

@Overridepublic void closeOrder(OrderEntity entity) {//查询当前数据库中订单的最新状态OrderEntity orderEntity = this.getById(entity.getId());//待付款状态需要关闭订单,状态0,待付款if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){//关单:将指定订单号的订单表的状态更改,因为数据库里的订单表和传过来的订单表状态可能不一样,所以要新建订单表OrderEntity update = new OrderEntity();update.setId(entity.getId());//设置订单为已取消订单状态,状态4,已关闭update.setStatus(OrderStatusEnum.CANCELED.getCode());//将订单状态更新到数据库this.updateById(update);/*** 创建 mq 专用传输类 ,等于 OrderEntity* 订单关闭后,也应该主动发一个消息,告诉服务有一个订单释放了* 绑定订单交换机和库存队列,订单释放和库存释放进行绑定* 队列发送给交换机信息,需要手动人工用 rabbitTemplate 工具* 交换机给队列发信息,自动无须人工手动即可发送*/OrderTo orderTo = new OrderTo();BeanUtils.copyProperties(orderEntity,orderTo);//发送给mq一个orderrabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);}}

5、交换机(order-event-exchange)—— loutingKey(order-release-other.#)——库存释放队列(stock-release-stock-queue)

/*** 监听mq的消息,释放锁库存* 生产者发送消息--交换机--队列(延迟2分钟无人消费)--根据loutingKey和交换机到达--交换机--队列(被监听)**/
@RabbitListener(queues = "stock.release.stock.queue")
@Service
public class StockReleaseListener {@AutowiredWareSkuService wareSkuService;/*** 解锁库存* 当下订单后,订单表锁定库存成功、库存表锁定库存成功,但是30分钟内未支付,消费者监听队列,自动解锁库存* @param to  库存锁定成功后,通知mq库存锁定成功,并在库存工作单里记录了哪个skuId商品,在哪个仓库wareId,扣除了多少数量num的物品* @param message* @param channel* @throws IOException*/@RabbitHandlerpublic void handleStockLockRelease(StockLockedTo to, Message message, Channel channel) throws IOException {System.out.println("2分钟后收到解锁库存的消息,调用解锁方法");try {wareSkuService.unlockStock(to);/*** 手动应答,消费者读取完mq消息后,要给生产者一个答复,执行成功了就回复成功,不是批量回复* 1、消息的标记 tag* 2、是否批量应答 multiple false代表不批量应答信道中的消息  true代表批量*/channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {//有异常就拒绝消费,将消息继续扔到队列里,让别人消费channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}/*** 库存服务监听 订单服务的关单信息--------------------关单---------------* @param orderTo  orderEntity* @param message* @param channel* @throws IOException*/@RabbitHandlerpublic void handleOrderCloseRelease(OrderTo orderTo,Message message,Channel channel) throws IOException {try {//监听到订单服务的消息后,库存服务开始解锁库存wareSkuService.unlockStock(orderTo);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}

当下订单后,订单表锁定库存成功、库存表锁定库存成功,但是30分钟内未支付,消费者监听队列,自动解锁库存

配置队列和交换机

/*** mq的配置类* 1、只要rabbitmq控制台已经有了我们要创建的队列、要想修改只能删除重新生成队列*/
@Configuration
public class MyMqConfig {/*** 消费者监听消费队列 order.release.order.queue*/
//    @RabbitListener(queues = "order.release.order.queue")
//    public void listener(OrderEntity entity, Channel channel,Message message) throws Exception{//        System.out.println("收到过期的订单信息,准备关闭订单"+entity.getOrderSn());
//        /**
//         * 手动应答,消费者读取完mq消息后,要给生产者一个答复
//         * 1、消息的标记 tag
//         * 2、是否批量应答 multiple false代表不批量应答信道中的消息  true代表批量
//         */
//        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
//    }//声明延迟队列,指定时间内未消费变为死信@Beanpublic Queue orderDelayQueue (){//此队列携带参数(交换机、loutingKey、过期时间)Map<String,Object> arguments = new HashMap<>();arguments.put("x-dead-letter-exchange","order-event-exchange");arguments.put("x-dead-letter-routing-key","order.release.order");arguments.put("x-message-ttl",60000);//Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)Queue queue = new Queue("order.delay.queue", true, false, false,arguments);return queue;}//普通队列,没有参数@Beanpublic Queue orderReleaseOrderQueue (){Queue queue = new Queue("order.release.order.queue", true, false, false, null);return queue;}//声明普通交换机(没有参数)@Beanpublic Exchange orderEventExchange(){//TopicExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments)return new TopicExchange("order-event-exchange",true,false,null);}//声明延迟队列和交换机绑定@Beanpublic Binding orderCreateOrderBinding(){//Binding(String destination, DestinationType destinationType, String exchange, String routingKey,Map<String, Object> arguments)return new Binding("order.delay.queue",Binding.DestinationType.QUEUE,"order-event-exchange","order.create.order",null);}//声明普通队列和交换机绑定@Beanpublic Binding orderReleaseOrderBinding(){return new Binding("order.release.order.queue",Binding.DestinationType.QUEUE,"order-event-exchange","order.release.order",null);}//声明库存队列和订单交换机绑定 订单释放和库存释放绑定@Beanpublic Binding OrderReleaseOtherBinding(){return new Binding("stock.release.stock.queue",Binding.DestinationType.QUEUE,"order-event-exchange","order.release.other.#",null);}
}

第一步:发送订单成功消息到mq

到了第5步,订单创建成功,发送消息到mq

@Transactional@Overridepublic SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {//将订单提交页面的数据放入此 threadLocal 中,用于共享数据orderSubmitVoThreadLocal.set(vo);//调用 ThreadLocal,获取同一线程的数据,得到当前登录的用户MemberRespVo memberRespVo = LoginUserIntereptor.loginUser.get();//生成提交订单后返回的封装类SubmitOrderResponseVo response = new SubmitOrderResponseVo();//没有抛出异常就设置状态码为0,代表成功response.setCode(0);//1、验证令牌【令牌的对比和删除必须保证原子性】//对比防重删令牌,返回0和1,0代表令牌失败、1代表令牌删除成功String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//从页面获取防重令牌String orderToken = vo.getOrderToken();//redis执行lua脚本,将页面输入令牌和redis中保存的令牌做对比,验证成功返回1,失败返回0  order:token+用户idLong result = redisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);if (result == 0l){//验证失败response.setCode(1);return response;}else {//1、令牌验证成功,解决重复提交订单成功问题,下单、创建订单、验令牌、验价格、锁库存OrderCreateTo order = createOrder();//2、验价,获取到应付金额BigDecimal payAmount = order.getOrder().getPayAmount();//获取页面提交的应付金额BigDecimal payPrice = vo.getPayPrice();//如果数据库订单里的应付金额和页面提交的金额差值小于0.01,就验证价格成功,因为是省略到小数点后两位,如果算相等的话,可能会出现 0.12和0.123456...double value = Math.abs(payAmount.subtract(payPrice).doubleValue());//如果验价成功if (value < 0.01){//TODO 三、保存订单saveOrder(order);// 四、库存锁定,stock_locked 锁定库存数 <= stock 库存数//库存锁定,只要有异常回滚订单数据  (订单号,所有订单项)WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();//封装订单号wareSkuLockVo.setOrderSn(order.getOrder().getOrderSn());List<OrderItemVo> locks = 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());//封装订单项wareSkuLockVo.setLocks(locks);//TODO 4、远程锁库存//远程调用库存服务调用锁定库存方法R r = wareFeignService.orderLockStock(wareSkuLockVo);if (r.getCode() == 0){//如果锁定成功response.setOrder(order.getOrder());//TODO 5、远程扣减积分//模拟异常,订单数据库回滚,但是库存数据库不回滚,本地事务只能对自己的服务有效//int i = 1/0;/*** TODO 订单创建成功给mq发送消息* 默认订单锁定成功,库存锁定也成功,但后面的业务方法错误,因为有@Transactional本地事务,造成订单未下单回滚了,但是库存已经扣除了,没有回滚* 需要解锁库存(根据工作表和工作详情表),在库存服务里添加监听器监听队列,自动解锁库存*/rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",order.getOrder());return response;}else {response.setCode(3);return response;}}else {//如果验价失败response.setCode(2);return response;}}}

第二步:监听队列

/*** 监听mq消息,自动关单*/
@RabbitListener(queues = "order.release.order.queue")
@Service
public class OrderCloseListener {@AutowiredOrderService orderService;@RabbitHandlerpublic void listener(OrderEntity entity, Channel channel, Message message) throws Exception{System.out.println("收到过期的订单信息,准备关闭订单"+entity.getOrderSn());try {orderService.closeOrder(entity);/*** 手动应答,消费者读取完mq消息后,要给生产者一个答复* 1、消息的标记 tag* 2、是否批量应答 multiple false代表不批量应答信道中的消息  true代表批量*/channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {//如果失败,信道就拒绝消费消息,将此消息扔到队列中,让其他人消费channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}}

第三步:关单业务实现

关单:就是到 oms_order 表里将指定订单的状态改变即可

 //OrderServiceImpl类@Overridepublic void closeOrder(OrderEntity entity) {//查询当前订单的最新状态OrderEntity orderEntity = this.getById(entity.getId());//待付款状态需要关闭订单,状态0,待付款if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){//关单:将指定订单号的订单表的状态更改,因为数据库里的订单表和传过来的订单表状态可能不一样,所以要新建订单表OrderEntity update = new OrderEntity();update.setId(entity.getId());//设置订单已取消订单状态 状态4,已取消update.setStatus(OrderStatusEnum.CANCELED.getCode());//将订单状态更新到数据库this.updateById(update);/*** 创建 mq 专用传输类 ,等于 OrderEntity* 订单关闭后,也应该主动发一个消息,告诉服务有一个订单释放了* 绑定订单交换机和库存队列,订单释放和库存释放进行绑定* 队列发送给交换机信息,需要手动人工用 rabbitTemplate 工具* 交换机给队列发信息,自动无须人工手动即可发送*/OrderTo orderTo = new OrderTo();BeanUtils.copyProperties(orderEntity,orderTo);//发送给mq一个orderrabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);}}

第四步:库存服务监听队列

/*** 监听mq的消息,释放锁库存* 生产者发送消息--交换机--队列(延迟2分钟无人消费)--根据loutingKey和交换机到达--交换机--队列(被监听)**/
@RabbitListener(queues = "stock.release.stock.queue")
@Service
public class StockReleaseListener {@AutowiredWareSkuService wareSkuService;/*** 解锁库存* 当下订单后,订单表锁定库存成功、库存表锁定库存成功,但是30分钟内未支付,消费者监听队列,自动解锁库存* @param to  库存锁定成功后,通知mq库存锁定成功,并在库存工作单里记录了哪个skuId商品,在哪个仓库wareId,扣除了多少数量num的物品* @param message* @param channel* @throws IOException*/@RabbitHandlerpublic void handleStockLockRelease(StockLockedTo to, Message message, Channel channel) throws IOException {System.out.println("2分钟后收到解锁库存的消息,调用解锁方法");try {wareSkuService.unlockStock(to);/*** 手动应答,消费者读取完mq消息后,要给生产者一个答复,执行成功了就回复成功,不是批量回复* 1、消息的标记 tag* 2、是否批量应答 multiple false代表不批量应答信道中的消息  true代表批量*/channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {//有异常就拒绝消费,将消息继续扔到队列里,让别人消费channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}/*** 库存服务监听 订单服务的关单信息,订单服务关单后,又发送了一条信息给交换机,交换机发送到了库存的释放队列上* @param orderTo  orderEntity* @param message  * @param channel* @throws IOException*/@RabbitHandlerpublic void handleOrderCloseRelease(OrderTo orderTo,Message message,Channel channel) throws IOException {try {wareSkuService.unlockStock(orderTo);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);}}
}

第五步:解锁业务实现

 /*** 订单解锁* 防止订单服务卡顿,导致订单状态消息一直改不了,库存消息优先到期,* @param orderTo*/@Transactional@Overridepublic void unlockStock(OrderTo orderTo) {//获取订单号String orderSn = orderTo.getOrderSn();//获取工作单,防止重复解锁库存WareOrderTaskEntity taskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn);//查询工作单idLong id = taskEntity.getId();//从库存详情工作表里,根据工作单id找到所有没有解锁的库存,然后进行解锁,工作单id可能会重复List<WareOrderTaskDetailEntity> entities = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>().eq("task_id", id).eq("lock_status", 1));for (WareOrderTaskDetailEntity entity : entities) {unLockStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum(),entity.getId());}}/*** 解锁库存* 哪个商品、哪个仓库、数量、库存工作详情表id*/public void unLockStock(Long skuId, Long wareId, Integer num, Long taskDetailId) {//库存解锁wareSkuDao.unLockStock(skuId, wareId, num, taskDetailId);//更新库存工作详情表的状态为已解锁库存WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity();detailEntity.setId(taskDetailId);detailEntity.setLockStatus(2); //变为已解锁//根据id更新 lock_status 锁状态为 已解锁wareOrderTaskDetailService.updateById(detailEntity);}<update id="unLockStock">update wms_ware_skuset stock = stock + #{num},stock_locked = stock_locked - #{num}where skuId =#{skuId}and wareId =#{wareId}</update>

【订单服务】库存解锁和关单相关推荐

  1. 商城订单中心实现及用户关单实现思路

    商城订单中心实现及用户关单实现思路 一.订单服务 1.1.订单中心 1.2.订单构成 1.3.订单状态 1.4.订单流程 1.5.订单幂等性处理 1.6.订单业务流程 二.关单方式 2.1.Rabbi ...

  2. 服务条目与采购订单、采购申请、工单、项目及WBS的关系

    最近完成了服务条目审批的工作流,其中服务条目(ML81N)审批业务是PM和PS模块共有的业务.本文分析一下服务条目和采购订单.采购申请.工单.项目及WBS的关系. 一般而言,到服务条目审批要经过之前的 ...

  3. 谷粒商城--订单服务--高级篇笔记十一

    1.页面环境搭建 1.1 静态资源导入nginx 等待付款 --------->detail 订单页 --------->list 结算页 --------->confirm 收银页 ...

  4. 【谷粒商城之订单服务-支付】

    本笔记内容为尚硅谷谷粒商城订单服务支付部分 目录 一.支付宝沙箱 沙箱环境 二.公钥.私钥.加密.加签.验签 1.公钥私钥 2.加密和数字签名 3.对称加密和非对称加密 三.内网穿透 四.整合支付 1 ...

  5. 28.订单服务-RabbitMQ

    RabbitMQ延时队列--实现定时任务 消息的TTL:消息的存活时间 RabbitMQ可以分别对队列和消息设置TTL: 对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做设置,超 ...

  6. 订单服务------技术点及亮点

    大技术 线程池来实现异步任务(亮点1) /*** 去结算确认页时封装订单确认页返回需要用的数据* @return* @throws ExecutionException* @throws Interr ...

  7. 谷粒商城二十订单服务

    rabbitmq相关知识 // 静态页面的引入,静态资源上传nginx等192.168.56.10 gulimall.com 192.168.56.10 search.gulimall.com 192 ...

  8. 谷粒商城二十二订单服务支付宝支付

    我们支付暂时只开发支付宝,官方文档在蚂蚁金服开放平台-电脑网站支付. 按照正规的流程,我们的系统要接入支付宝,肯定是需要大量的审核过程,而且需要我们的项目上线. 那现在我们就想测试该怎么办?支付宝为我 ...

  9. 订单服务-----功能实现逻辑

    订单服务的实现流程(确认订单->提交订单->支付) 1.整合SpringSession 使用SpringSession的目的是来解决分布式session不同步不共享的问题,其实就是为了让登 ...

最新文章

  1. php如何添加超链接,如何使用phpcms添加超链接
  2. 关于Python 3.9,那些你不知道的事
  3. 【机器学习】随机森林、GBDT、XGBoost、LightGBM等集成学习代码练习
  4. javaweb家居用品线上销售系统_智能家居订单管理系统方案设计路线
  5. 如何在 Github 工作流文件里引用自定义实现的 action
  6. Codeforces Round #375 (Div. 2) D. Lakes in Berland 贪心
  7. LFS chroot后装glibc时编译出错/bin/sh: command substitution: line 3: syntax error near unexpected token `)
  8. 网络蜘蛛爬取邮箱地址
  9. bat脚本积累(三)—— bat的注释
  10. Maxwell 介绍
  11. 综治应急指挥中心建设方案
  12. android动态设置渐变背景
  13. 安装语言包-英文(美国)
  14. 因为现在的手机大部分都不能换电池,是不是手机使用1至2年就需要更换了?...
  15. windows 2003 x86 32位中Oracle 10G数据库使用超过1.7G的sga的方法
  16. 推荐几个办公软件的神器
  17. 所需即所获:IDE = _plugins_ + vim
  18. 这些秋季儿童养生小常识,你要知道!
  19. mysql ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using passwor...
  20. 前端工程筹建NodeJs+gulp+bower

热门文章

  1. caffe-ssd 检测视频并保存
  2. 移动互联网开发技术教学网站项目研究第六篇
  3. 终于大橘已定,分享一波面经(美团、小米、华为、阿里等)
  4. 实用生活英语句子 174句
  5. C#关于操作符的重载
  6. Numpy矩阵乘积函数(dot)运算规则解析
  7. MySQL之数据库三大范式
  8. ajax用jquery怎么实现,ajax使用jquery的实现方式
  9. 摄像头位置和角度变量的推、拉、摇、移、跟、升降、甩是什么意思?
  10. grafana在Linux插件,Grafana图表插件-业务数据(BI)表现增强