✅P296-298_商城业务-订单服务-库存解锁逻辑-库存自动解锁完成-测试库存自动解锁

gong_yz大约 5 分钟谷粒商城

解锁场景

1、通过查询订单的锁库存信息:

  • 如果有则仅仅说明库存锁定成功,还需判断是否有订单信息
    • 如果有订单信息则判断订单状态,若订单状态已取消则解锁库存,反之:不能解锁库存
    • 如果没有订单信息则需要解锁库存;

2、锁库存失败,库存回滚了,这种情况无需解锁

3、如果没有锁库存信息则无需任何操作。


逻辑实现

接收订单信息VO

cfmall-ware/src/main/java/com/gyz/cfmall/ware/vo/OrderVo.java

package com.gyz.cfmall.ware.vo;

import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;

/**
 * @author: gongyuzhuo
 * @since: 2024-02-06 18:26
 * @description:
 */
@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;
}

远程查询订单状态

cfmall-ware/src/main/java/com/gyz/cfmall/ware/feign/OrderFeignService.java

package com.gyz.cfmall.ware.feign;

import com.gyz.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @author: gongyuzhuo
 * @since: 2024-02-06 18:28
 * @description:
 */
@FeignClient("cfmall-order")
public interface OrderFeignService {
    @GetMapping(value = "/order/order/status/{orderSn}")
    R getOrderStatus(@PathVariable("orderSn") String orderSn);
}

cfmall-order/src/main/java/com/gyz/cfmall/order/controller/OrderController.java

/**
 * 根据订单编号查询订单状态
 * @param orderSn
 * @return
 */
@GetMapping(value = "/status/{orderSn}")
public R getOrderStatus(@PathVariable("orderSn") String orderSn) {
    OrderEntity orderEntity = orderService.getOrderByOrderSn(orderSn);
    return R.ok().setData(orderEntity);
}
@Override
public OrderEntity getOrderByOrderSn(String orderSn) {
    OrderEntity orderEntity = this.baseMapper.selectOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
    return orderEntity;
}

监听事件

package com.gyz.cfmall.ware.listener;

import com.alibaba.fastjson.JSON;
import com.gyz.cfmall.ware.service.WareSkuService;
import com.gyz.common.to.OrderTo;
import com.gyz.common.to.mq.StockLockedTo;
import com.rabbitmq.client.Channel;

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;


@Slf4j
@RabbitListener(queues = "stock.release.stock.queue")
@Service
public class StockReleaseListener {

    @Autowired
    private WareSkuService wareSkuService;

    /**
     * 1、库存自动解锁
     * 下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁
     * <p>
     * 2、订单失败
     * 库存锁定失败
     * <p>
     * 只要解锁库存的消息失败,一定要告诉服务解锁失败
     */
    @RabbitHandler
    public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException {
        log.info("【收到解锁库存的信息】:{}", JSON.toJSONString(to));
        //解锁库存
        wareSkuService.unlockStock(to);
    }
}
    @Override
    public void unlockStock(StockLockedTo to) {
        //库存工作单的id
        StockDetailTo detail = to.getDetailTo();
        Long detailId = detail.getId();

        /**
         * 解锁
         * 1、查询数据库关于这个订单锁定库存信息
         *   有:证明库存锁定成功了
         *      解锁:订单状况
         *          1、没有这个订单,必须解锁库存
         *          2、有这个订单,不一定解锁库存
         *              订单状态:已取消:解锁库存
         *                      已支付:不能解锁库存
         */
        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>() {
                });

                //判断订单状态是否已取消或者支付或者订单不存在
                if (orderInfo == null || orderInfo.getStatus() == 4) {
                    //订单已被取消,才能解锁库存
                    if (taskDetailInfo.getLockStatus() == 1) {
                        //当前库存工作单详情状态1,已锁定,但是未解锁才可以解锁
                        unLockStock(detail.getSkuId(), detail.getWareId(), detail.getSkuNum(), detailId);
                    }
                }
            } else {
                //消息拒绝以后重新放在队列里面,让别人继续消费解锁
                //远程调用服务失败
                throw new RuntimeException("远程调用订单服务失败");
            }
        } else {
            //订单的库存详细信息不存在,无需解锁
        }
    }
    /**
     * 解锁库存的方法
     *
     * @param skuId
     * @param wareId
     * @param skuNum
     * @param taskDetailId
     */
    public void unLockStock(Long skuId, Long wareId, Integer skuNum, Long taskDetailId) {

        //库存解锁
        wareSkuDao.unLockStock(skuId, wareId, skuNum);

        //更新工作单的状态
        WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity();
        taskDetailEntity.setId(taskDetailId);
        //变为已解锁
        taskDetailEntity.setLockStatus(2);
        wareOrderTaskDetailService.updateById(taskDetailEntity);
    }

cfmall-ware/src/main/resources/mapper/WareSkuDao.xml

<update id="unLockStock">
    UPDATE wms_ware_sku
    SET stock_locked = stock_locked - #{num}
    WHERE
        sku_id = ${skuId}
      AND ware_id = #{wareId}
</update>

手动ACK

远程服务调用可能会出现失败,需要设置手动ACK,确保其它服务能消费此消息

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual #手动ACK

修改监听事件

package com.gyz.cfmall.ware.listener;

import com.alibaba.fastjson.JSON;
import com.gyz.cfmall.ware.service.WareSkuService;
import com.gyz.common.to.OrderTo;
import com.gyz.common.to.mq.StockLockedTo;
import com.rabbitmq.client.Channel;

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;


@Slf4j
@RabbitListener(queues = "stock.release.stock.queue")
@Service
public class StockReleaseListener {

    @Autowired
    private WareSkuService wareSkuService;

    /**
     * 1、库存自动解锁
     * 下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁
     * <p>
     * 2、订单失败
     * 库存锁定失败
     * <p>
     * 只要解锁库存的消息失败,一定要告诉服务解锁失败
     */
    @RabbitHandler
    public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException {
        log.info("【收到解锁库存的信息】:{}", JSON.toJSONString(to));
        try {
            //解锁库存
            wareSkuService.unlockStock(to);
            // 手动删除消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            // 解锁失败 将消息重新放回队列,让别人消费
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }
}

拦截器放行指定接口