微服务-下单退单流程

主要使用的技术

1.SpringCloud

2.RocketMq

数据库建表

主要的表有 商品表、商品流水表、订单表

(大致的关系,主要是为了方便模拟,所以简化了表之间关系与字段)

DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods`  (
  `goods_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '商品唯一标识',
  `goods_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名',
  `num` bigint(11) NULL DEFAULT NULL COMMENT '商品库存',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`goods_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for goods_flow
-- ----------------------------
DROP TABLE IF EXISTS `goods_flow`;
CREATE TABLE `goods_flow`  (
  `flow_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '商品流水唯一流水号标识',
  `goods_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品唯一标识',
  `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作类别',
  `user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户唯一标识',
  `order_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单唯一标识',
  `num` bigint(11) NULL DEFAULT NULL COMMENT '数量',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `is_delete` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否删除',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`flow_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders`  (
  `order_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单唯一标识',
  `user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户唯一标识',
  `order_status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单状态(订单创建>订单支付>订单生产>订单确认>订单完成)',
  `goods_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品唯一标识',
  `goods_num` int(11) NULL DEFAULT NULL COMMENT '商品数量',
  `count_fee` decimal(10, 0) NULL DEFAULT NULL COMMENT '总计费用',
  `step` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '步骤',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

过程逻辑

其中分解为两个微服务,分别为订单微服务和商品微服务

下单流程

orderController

    /**
     * 下单
     * @return
     */

    @PostMapping("/submit")
    public R<?> submit(String goodsId,Long num){

        Order order = new Order();
        order.setUserId("user1");
        order.setCreateTime(LocalDateTime.now());
        order.setGoodsId(goodsId);
        order.setGoodsNum(num.intValue());
        order.setOrderStatus("订单创建");
        //支付状态
        order.setStep("N");

        //提交订单 未支付
        boolean submitIsSuccess = iOrderService.submit("user1", order);

        return R.ok(submitIsSuccess);

    }

orderService

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean submit(String userId, Order order) {

        //存储订单 saveOrder
        this.baseMapper.insert(order);

        //调用商品服务 锁定库存
        Long goodsNum = order.getGoodsNum().longValue();
        R<?> lock = remoteGoodsService.lock(order.getGoodsId(), goodsNum, order.getOrderId(), order.getUserId());

        // 锁定不成,抛异常,让回滚订单
        if (lock.getCode()!= 200) {
            throw new RuntimeException(lock.getMsg());
        }
        //发送消息,如果三十分钟后没有支付,则取消订单
        SendStatus sendStatus = orderCancelTemplate
                .syncSend(RocketMqConstant.ORDER_CANCEL_TOPIC,
                        new GenericMessage<>(order.getOrderId()),
                        RocketMqConstant.TIMEOUT,
                        RocketMqConstant.CANCEL_ORDER_DELAY_LEVEL).getSendStatus();

        if (!Objects.equals(sendStatus,SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常,发的出去无所谓
            throw new RuntimeException("系统异常");
        }

        return true;
    }

取消订单流程

(1)用户主动取消订单

orderController

    /**
     * 取消订单
     */
    @PutMapping("/cancel/{orderId}")
    public R<String> cancelOrder(@PathVariable("orderId") String orderId) {
        String userId = "user1";
        LambdaQueryWrapper<Order> wrapper = new QueryWrapper<Order>().lambda()
                .eq(Order::getOrderId, orderId)
                .eq(Order::getUserId, userId);
        Order order = iOrderService.getOne(wrapper);
        if (order.getOrderStatus() == "订单支付") {
            // 订单已支付,无法取消订单
            return R.fail("订单已支付,不可取消");
        }
        // 如果订单未支付的话,将订单设为取消状态
        iOrderService.cancelOrder(order.getOrderId());
        return R.ok();
    }

orderService

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelOrder(String orderId) {
        log.info("cancelOrder:{}",orderId);

        LambdaQueryWrapper<Order> queryWrapper = new QueryWrapper<Order>()
                .lambda().eq(Order::getOrderId, orderId).eq(Order::getStep, "N");

        Order order = this.baseMapper.selectOne(queryWrapper);


        if (order.getOrderStatus() == "取消订单") {
            return;
        }
        //订单支付状态为N,设置订单状态为 取消订单
        this.baseMapper.cancelOrder("取消订单",order.getOrderId());
        // 通知-解锁库存状态
        SendStatus stockSendStatus = stockMqTemplate
                .syncSend(RocketMqConstant.STOCK_UNLOCK_TOPIC, new GenericMessage<>(orderId)).getSendStatus();
        if (!Objects.equals(stockSendStatus,SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常,发的出去无所谓
            throw new RuntimeException("系统异常");
        }
    }

商品库存锁定和取消锁定流程

goodsService

@Service
@Slf4j
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods> implements IGoodsService {


    @Autowired
    private RocketMQTemplate stockMqTemplate;

    @Resource
    private GoodsFlowMapper goodsFlowMapper;

    @Resource
    private RemoteOrderService remoteOrderService;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean lock(String goodsId, Long num,String orderId,String userId){
        // 减商品库存

        int reduceStockIsSuccess = this.baseMapper.reduceStock(goodsId, num);
        if(reduceStockIsSuccess<1){
            throw new RuntimeException("库存不足!goodsId:"+goodsId);
        }
        // (记入商品库存流水日志)保存库存锁定信息

        GoodsFlow goodsFlow = new GoodsFlow();
        goodsFlow.setUserId(userId);
        goodsFlow.setNum(num);
        goodsFlow.setGoodsId(goodsId);
        goodsFlow.setOrderId(orderId);
        goodsFlow.setType("-");
        goodsFlow.setIsDelete("N");
        goodsFlow.setCreateTime(LocalDateTime.now());

        goodsFlowMapper.insert(goodsFlow);
        // 一个小时后解锁库存
        //延时信息无法立即在可视化界面上查看
        SendStatus sendStatus =
                stockMqTemplate.syncSend(RocketMqConstant.STOCK_UNLOCK_TOPIC, new GenericMessage<>(orderId)
                , RocketMqConstant.TIMEOUT, RocketMqConstant.CANCEL_ORDER_DELAY_LEVEL + 1).getSendStatus();

        //stockMqTemplate.convertAndSend(RocketMqConstant.STOCK_UNLOCK_TOPIC, "order-string-test");

        if (!Objects.equals(sendStatus,SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常,发的出去无所谓
            throw new RuntimeException("服务器出了点小差");
        }

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlockStock(String orderId) {
        log.info("unlockStock:{}",orderId);
        //远程调用 获取订单状态
        R<Order> orderResponse = remoteOrderService.getById(orderId);
        log.info("unlockStock.orderResponse:{}",orderResponse.toString());
        if (orderResponse.getCode()!=200) {
            throw new RuntimeException(orderResponse.getMsg());
        }
        Order order = orderResponse.getData();
        // 该订单没有下单成功,或订单已取消,赶紧解锁库存
        if(order.getOrderStatus().equals("取消订单") == false){
            return;
        }
        //根据订单号 获取锁定的库存
        LambdaQueryWrapper<GoodsFlow> queryWrapper = new QueryWrapper<GoodsFlow>()
                .lambda().eq(GoodsFlow::getOrderId, orderId);
        GoodsFlow goodsFlow = goodsFlowMapper.selectOne(queryWrapper);
        goodsFlow.setIsDelete("Y");
        //还原商品库存
        this.baseMapper.addStock(goodsFlow.getGoodsId(),goodsFlow.getNum());
        //解锁库存
        goodsFlowMapper.updateById(goodsFlow);
    }

}

监听消息队列中的订单过期为未支付消息

@Component
@RocketMQMessageListener(topic = RocketMqConstant.ORDER_CANCEL_TOPIC,consumerGroup = RocketMqConstant.ORDER_CANCEL_TOPIC)
public class OrderCancelConsumer implements RocketMQListener<String> {

    @Autowired
    private IOrderService orderService;

    /**
     * 订单取消状态修改后再进行其他服务
     */
    @Override
    public void onMessage(String orderId) {
        // 如果订单未支付的话,将订单设为取消状态
        orderService.cancelOrder(orderId);
    }
}

监听消息队列中的取消锁定操作消息

@Component
@RocketMQMessageListener(topic = RocketMqConstant.STOCK_UNLOCK_TOPIC,consumerGroup = RocketMqConstant.STOCK_UNLOCK_TOPIC)
public class StockUnlockConsumer implements RocketMQListener<String> {

    @Autowired
    private IGoodsService goodsService;

    /**
     * 1、库存锁定一定时间后,如果订单支付未支付,则解锁库存(有可能库存锁定成功,订单因为异常回滚导致订单未创建)
     * 2、取消订单,直接解锁库存
     */
    @Override
    public void onMessage(String orderId) {
        goodsService.unlockStock(orderId);
    }
}

测试

商品库存500,线程数开启1000并发值,测试下单数是否符合库存数,并测试订单过期库存回滚操作。

微服务-下单退单流程

 微服务-下单退单流程

微服务-下单退单流程

微服务-下单退单流程

微服务-下单退单流程

上一篇:Python学习笔记之装饰器的使用


下一篇:视图