rabbitmq分布式事务解决方案

2023-11-05

业务;流程图

发送消息到mq 流程

  • 用户下订单创建订单信息,且创建一条订单冗余信息 status 为 0
  • 发送订单信息到mq , 使用ack 消息确认机制,确认消息发送成功修改订单状态为 1(表示消息已发送)
  • 启动一个定时任务 排查 订单状态为 0 的订单 发送消息到mq ack 确认修改状态status 为1(同上)
  • 以上来确保消息成功发送给mq

接收mq消息(消费)

  • 接收消息业务正常执行完成手动确认消息
  • 抛出异常 使用 死信队列 将消息交给 死信队列完成
  • 配置死信队列 将业务接收消息的queue 与死信队列 绑定
  • 死信队列 在执行一次业务代码,如果抛出异常,则发送短信,或者写入数据库,或人工干预

代码示例

service 1 发送消息

配置信息

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/distribution
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.rabbitmq.port=5672
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.virtual-host=/
spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true
# 开启手动 ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual
# 开启重试
spring.rabbitmq.listener.simple.retry.enabled=true
# 最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=3
# 重试时间
spring.rabbitmq.listener.simple.retry.initial-interval=2000ms
server.port=9001

配置基础的rabbitmq

@Configuration
public class RabbitMQConfig {
    public static final String TOPIC_QUEUE1 = "topic.queue1";
    public static final String TOPIC_QUEUE2 = "topic.queue2";
    public static final String TOPIC_EXCHANGE = "topic.exchange";

    @Value("${spring.rabbitmq.host}")
    private String host;
    @Value("${spring.rabbitmq.port}")
    private int port;
    @Value("${spring.rabbitmq.username}")
    private String username;
    @Value("${spring.rabbitmq.password}")
    private String password;

    @Autowired
    private ConnectionFactory connectionFactory;

    @Bean(name = "connectionFactory")
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
        connectionFactory.setPublisherReturns(true);
        return connectionFactory;
    }
bean
/**
 * 订单数据
 */
@Data
public class OrderInfo {

    private String orderId;
    private String userId;
    private String orderContent;
    private String createTime;
}
// 订单冗余表
@Data
public class OrderInfoMessage {

    private String orderId;
    private int status;  // 0 表示为发送消息到mq 1 表示消息已发送到mq
    private String order_content;
    private String unique_id;
}

发送消息 到mq

这里 创建订单表数据 ,创建订单冗余表数据 默认 status 0 表示为未发送消息到 mq

@Service
public class MqOrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderInfoMessageMapper orderInfoMessageMapperl;

    @Autowired
    private OrderMqService orderMqService;

    public void mqSaveOrder(OrderInfo orderInfo){
        // 订单信息-- 插入订单系统 ,订单事务数据库
        saveOrderMessage(orderInfo);
        // 通过http 接口发送订单到运单系统 mq 发送
        orderMqService.sendMessage(orderInfo);
    }

    /**
     * 事务数据库
     * @param orderInfo
     */
    private void saveOrderMessage(OrderInfo orderInfo){
        // 插入数据到订单表
        orderMapper.insert(orderInfo);
        // 插入数据到订单事务表
        OrderInfoMessage orderInfoMessage = new OrderInfoMessage();
        orderInfoMessage.setOrder_content(orderInfo.getOrderContent());
        orderInfoMessage.setStatus(0);
        orderInfoMessage.setUnique_id(orderInfo.getUserId());
        orderInfoMessage.setOrderId(orderInfo.getOrderId());
        orderInfoMessageMapperl.insert(orderInfoMessage);
    }
    
}

发送消息到mq 使用 ack 确认机制 修改status 为 1 表示 消息已发送到 mq
如果抛出异常 等待 定时任务发送消息到mq

@Service
public class OrderMqService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private OrderInfoMessageMapper orderInfoMessageMapperl;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * PostConstruct 不是spring 提供的 是java 提供
     * 该注解用来修饰一个非静态的  void 方法被次注解修饰的方法会在服务器加载 servlet的时候执行
     * 并且只会执行一次 PostConstruct在 构造函数之后执行在 init 之前执行
     */
    @PostConstruct
    public void regCaliback() {
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("cause " + cause);
                // 如果 ack 为true 表示已收到消息
                String orderId = correlationData.getId();
                if (!ack) {
                    // 这里需要进行其他方式存储
                    System.out.println("MQ 队列响应失败 orderId 为 " + orderId);
                    return;
                }
                int update = jdbcTemplate.update("update order_info_message set status = 1 where order_id =?", orderId);
//                    使用mybatisplus 修改失败报错 HikariDataSource HikariDataSource (HikariPool-1) has been closed.
//                    OrderInfoMessage orderInfoMessage = new OrderInfoMessage();
//                    orderInfoMessage.setStatus(1);
//                    QueryWrapper<OrderInfoMessage> queryWrapper = new QueryWrapper();
//                    queryWrapper.lambda().eq(OrderInfoMessage::getOrderId,orderId);
//                    int update = orderInfoMessageMapperl.update(orderInfoMessage, queryWrapper);
                if (update == 1) {
                    System.out.println("本地消息状态修改成功,消息成功投递到消息队列中 ");
                } else {
                    System.out.println("本地消息状态修改失败,出现异常 " + 88888888);
                }
            }
        });

    }

    @PostConstruct
    public void test() {
        System.out.println("-----------PostConstruct执行-----------");
    }

    public void sendMessage(OrderInfo orderInfo) {
    
        // 通过http 接口发送订单到运单系统 mq 发送
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId(orderInfo.getOrderId());
        rabbitTemplate.convertAndSend("order_fanout_exchange", "", JSONObject.toJSONString(orderInfo), correlationData);
    }

}

这里我有时候会报错 HikariDataSource HikariDataSource (HikariPool-1) has been closed. 获取不到数据库连接 不太明白,有大佬可以留言帮忙解决一下.


定时任务处理 status 为0 的 发送消息到mq

@EnableScheduling
@Component
public class RabbitmqTask {

    @Autowired
    private OrderInfoMessageMapper orderInfoMessageMapper;
    @Autowired
    private OrderMqService orderMqService;
    @Autowired
    private OrderMapper orderMapper;

    @Scheduled(cron = "0 0/1 * * * ?")
    public void rabbitmqTask() {
        // 获取所有的 status 为 0的 数据重新发送到mq
        QueryWrapper<OrderInfoMessage> queryWrapper =  new QueryWrapper<>();
        queryWrapper.lambda().eq(OrderInfoMessage::getStatus,0);
        List<OrderInfoMessage> orderInfoMessageList = orderInfoMessageMapper.selectList(queryWrapper);
        for (OrderInfoMessage orderInfoMessage : orderInfoMessageList) {
            if (Objects.nonNull(orderInfoMessage)){
                String orderId = orderInfoMessage.getOrderId();
                System.out.println("task 定時任務 order 为 "+ orderId);
                OrderInfo orderInfo = orderMapper.selectById(orderId);
                orderMqService.sendMessage(orderInfo);
            }
        }
    }
}

以上发送消息就完成了

接收消息代码 --> 从mq 消费消息

声明死信队列 且将 order.queue 队列与之绑定

@Configuration
public class RabbitMQConfiguration {

    // 死信队列
    // 声明注册 direct 模式交换机
    @Bean
    public DirectExchange deadExchange(){
        return new DirectExchange("dead_direct_exchange",true,false);
    }
    @Bean
    public Queue deadqueue(){
        HashMap<String, Object> args = new HashMap<>();
        args.put("x-message-ttl",5000);
        return new Queue("dead.direct.queue",true);
    }
    @Bean
    public Binding deadbind(){
        return BindingBuilder.bind(deadqueue()).to(deadExchange()).with("dead");
    }


    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("order_fanout_exchange",true,false);
    }

    @Bean
    public Queue orderQueue(){
        HashMap<String, Object> args = new HashMap<>();
        // 绑定死信队列 交换机
        args.put("x-dead-letter-exchange","dead_direct_exchange");
         args.put("x-dead-letter-routing-key","dead"); // fanout 不需要配置 路由key
        return new Queue("order.queue",true,false,false,args);
    }
    @Bean
    public Binding bindorder(){
        return BindingBuilder.bind(orderQueue()).to(fanoutExchange());
    }
}

消费者消费消息–>
order.queue 这个队列是监听这 正常消费消息
dead.direct.queue 这个队列是死信队列 当order.queue抛出异常之后会将消息发送到这个队列中来.

/**
 * 消费者
 */
@Service
public class OrderMqConsumer {

    @Autowired
    private DistributionCenterService distributionCenterService;

    private int count = 1;
    /**
     *   解决消息重试方法
     *   1. 控制重发的次数 ( 如果使用了 try catch 配置的重试次数会失效 互逆操作)
     *   2. try catch + 手动ack
     *   3. try catch + 手动ack + 死信队列 + 加上(完美)
     */
    @RabbitListener(queues = {"order.queue"})
    public void messageConsumer(String ordermsg, Channel channel,
                                CorrelationData correlationData,
                                @Header(AmqpHeaders.DELIVERY_TAG) Long tag) throws Exception {
       try {
           // 1. 获取消息队列中的消息
           System.out.println("收到mq 的消息 是 "+ordermsg+" count "+count++);
           // 2. 获取订单信息
           int a= 0/0;
           OrderInfo orderInfo = JSONObject.parseObject(ordermsg).toJavaObject(OrderInfo.class);
           // 3.获取订单id
           String orderId = orderInfo.getOrderId();
           // 4.派单
           distributionCenterService.dispatch(orderId);
           // 手动确认消息 正常情况
           channel.basicAck(tag,false);
       }catch (Exception e){
           /**
            * 如果出现异常情况,根据实际情况去重发
            * 参数 1 :消息的tag 参数 2 false 多条处理 参数 3 requeque 重发
            * false 不会重发,会把消息打入死信队列
            * true 的话会死循环重发,建议使用true 的话 不要使用 try catch 否则会出现死循环
            */
           channel.basicNack(tag,false,false);
       }
    }

    /**
     * 死信队列
     * @param ordermsg
     * @param channel
     * @param correlationData
     * @param tag
     * @throws Exception
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "dead.direct.queue",durable = "true"),
            exchange = @Exchange(name = "dead_direct_exchange",
                    type = ExchangeTypes.TOPIC,
                    ignoreDeclarationExceptions = "true"),
            key = {"dead"}
    ))
    public void deadMessageConsumer(String ordermsg, Channel channel,
                                CorrelationData correlationData,
                                @Header(AmqpHeaders.DELIVERY_TAG) Long tag) throws Exception {
        try {
            // 1. 获取消息队列中的消息
            System.out.println("收到mq 的消息 是 "+ordermsg+" count "+count++);
            // 2. 获取订单信息
            OrderInfo orderInfo = JSONObject.parseObject(ordermsg).toJavaObject(OrderInfo.class);
            // 3.获取订单id
            String orderId = orderInfo.getOrderId();
            // 冪等问题
            // 4.派单
            distributionCenterService.dispatch(orderId);
            // 手动确认消息 正常情况
            channel.basicAck(tag,false);
        }catch (Exception e){
            // 异常情况 引入人工 发送短信,存储数据库

        }
    }
}

关于幂等性
  • 我们可以考虑 使用数据库的主键唯一,或者使用分布式锁

基础的就完成了,大家觉得有改进的,或有问题的地方可以在评论区留言.
HikariDataSource HikariDataSource (HikariPool-1) has been closed. 这个有大佬帮忙解决一下么,想知道原因0.0

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

rabbitmq分布式事务解决方案 的相关文章

  • Java 中等效的并行扩展

    我在 Net 开发中使用并行扩展有一些经验 但我正在考虑在 Java 中做一些工作 这些工作将受益于易于使用的并行库 JVM 是否提供任何与并行扩展类似的工具 您应该熟悉java util concurrent http java sun
  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • Grails 3.x bootRun 失败

    我正在尝试在 grails 3 1 11 中运行一个项目 但出现错误 失败 构建失败并出现异常 什么地方出了错 任务 bootRun 执行失败 进程 命令 C Program Files Java jdk1 8 0 111 bin java
  • 在 Java 中连接和使用 Cassandra

    我已经阅读了一些关于 Cassandra 是什么以及它可以做什么的教程 但我的问题是如何在 Java 中与 Cassandra 交互 教程会很好 如果可能的话 有人可以告诉我是否应该使用 Thrift 还是 Hector 哪一个更好以及为什
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • JAXb、Hibernate 和 beans

    目前我正在开发一个使用 Spring Web 服务 hibernate 和 JAXb 的项目 1 我已经使用IDE hibernate代码生成 生成了hibernate bean 2 另外 我已经使用maven编译器生成了jaxb bean
  • INSERT..RETURNING 在 JOOQ 中不起作用

    我有一个 MariaDB 数据库 我正在尝试在表中插入一行users 它有一个生成的id我想在插入后得到它 我见过this http www jooq org doc 3 8 manual sql building sql statemen
  • Android MediaExtractor seek() 对 MP3 音频文件的准确性

    我在使用 Android 时无法在eek 上获得合理的准确度MediaExtractor 对于某些文件 例如this one http www archive org download emma solo librivox emma 01
  • 控制Android的前置LED灯

    我试图在用户按下某个按钮时在前面的 LED 上实现 1 秒红色闪烁 但我很难找到有关如何访问和使用前置 LED 的文档 教程甚至代码示例 我的意思是位于 自拍 相机和触摸屏附近的 LED 我已经看到了使用手电筒和相机类 已弃用 的示例 但我
  • 反射找不到对象子类型

    我试图通过使用反射来获取包中的所有类 当我使用具体类的代码 本例中为 A 时 它可以工作并打印子类信息 B 扩展 A 因此它打印 B 信息 但是当我将它与对象类一起使用时 它不起作用 我该如何修复它 这段代码的工作原理 Reflection
  • 路径中 File.separator 和斜杠之间的区别

    使用有什么区别File separator和一个正常的 在 Java 路径字符串中 与双反斜杠相反 平台独立性似乎不是原因 因为两个版本都可以在 Windows 和 Unix 下运行 public class SlashTest Test
  • 从 127.0.0.1 到 2130706433,然后再返回

    使用标准 Java 库 从 IPV4 地址的点分字符串表示形式获取的最快方法是什么 127 0 0 1 到等效的整数表示 2130706433 相应地 反转所述操作的最快方法是什么 从整数开始2130706433到字符串表示形式 127 0
  • 为什么HashMap不能保证map的顺序随着时间的推移保持不变

    我在这里阅读有关 Hashmap 和 Hashtable 之间的区别 http javarevisited blogspot sg 2010 10 difference Between hashmap and html http javar
  • Java Integer CompareTo() - 为什么使用比较与减法?

    我发现java lang Integer实施compareTo方法如下 public int compareTo Integer anotherInteger int thisVal this value int anotherVal an
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • Java列表的线程安全

    我有一个列表 它将在线程安全上下文或非线程安全上下文中使用 究竟会是哪一个 无法提前确定 在这种特殊情况下 每当列表进入非线程安全上下文时 我都会使用它来包装它 Collections synchronizedList 但如果不进入非线程安
  • 声明的包“”与预期的包不匹配

    我可以编译并运行我的代码 但 VSCode 中始终显示错误 早些时候有一个弹出窗口 我不记得是什么了 我点击了 全局应用 从那以后一直是这样 Output is there but so is the error The declared
  • simpleframework,将空元素反序列化为空字符串而不是 null

    我使用简单框架 http simple sourceforge net http simple sourceforge net 在一个项目中满足我的序列化 反序列化需求 但在处理空 空字符串值时它不能按预期工作 好吧 至少不是我所期望的 如
  • Spring Boot @ConfigurationProperties 不从环境中检索属性

    我正在使用 Spring Boot 1 2 1 并尝试创建一个 ConfigurationProperties带有验证的bean 如下所示 package com sampleapp import java net URL import j

随机推荐

  • ctf.show web7

    判断字符型 1 报错 1 更换 反复尝试如果正常返回为字符型注入 判断数字型注入的方法 1 与原界面返回不同或报错 1 and 1 1与原页面返回相同 1 and 1 2与原页面返回不同 即可得出为数字型 id 1 order by 3 与
  • 微信小程序新规,规范用户隐私保护指引

    背景 新功能发版突然遇到弹窗提示 需要更新用户隐私保护指引的设置 否则无法发布新版本 这里吐槽一下 在此之前微信小程序后台消息并未收到相关通知 解决路径如下 入口有两处 第一处如图 第二处入口 发布版本时会有提示 直接拦截 让完善协议 如图
  • java中的<;和>;分别是什么意思

    今天在做java笔试题的时候 有一题出现了这个符号 由于我不认识这个符号就做错了 题目如下 这题的答案是A 而我选了C 后面百度才知道 lt 的意思是小于 lt 符号 在用markdown写文章的时候 就会显示 笔者在这里贴出其他类似的符号
  • C#异步编程学习笔记4 之 异步函数

    C 异步编程学习笔记4 之 异步函数 异步函数 awaiting async 修饰符 异步方法如何执行 可以 await 什么 捕获本地状态 await 之后在哪个线程上执行 UI 上的 await 代码运行原理 与粗粒度的并发相比 编写异
  • Blender插件BoxCutter 7.1.7v15 硬表面建模2.91+教程Box Cutter

    Boxcutter旨在成为最快的屏幕3d视图绘图切割器 通过时间和经验来学习和增强了工具 以使工作流程尽可能地人性化地优化用户 提供各种行为来个性化体验 以使事情保持流畅 每天都会对这些工具进行严格的测试 以确保它们不仅可以与当前版本的Bl
  • Flask-文件上传

    在Flask中处理文件上传非常简单 它需要一个enctype属性设置为 multipart form data 的HTML表单 将该文件提交到指定URL 也可以配置上传文件路径和指定上传文件大小 实例 upload html文件中包含一个f
  • 征战开发板从无到有(三)

    接上一篇 翘首已盼的PCB板子做好了 管脚约束信息都在PCB板上体现出来了 很满意 会不会成为爆款呢 嘿嘿 来 先看看PCB裸板美图 由于征战开发板电路功能兼容小梅哥ACX720 大家可以直接用小梅哥的视频来学习 不会影响学习体验 现在学习
  • C语言最重要的知识点【入门干货】

    C语言最重要的知识点 总体上必须清楚的 1 程序结构是三种 顺序结构 选择结构 分支结构 循环结构 2 读程序都要从main 入口 然后从最上面顺序往下读 碰到循环做循环 碰到选择做选择 有且只有一个main函数 3 计算机的数据在电脑中保
  • RuntimeError: cublas runtime error : resource allocation failed at

    root bsyocr server train tail trainall210722 6 log txt File home server train pytorch pretrained modeling py line 300 in
  • Nginx的安装(实践记录)

    1 安装nginx需要系统中有gcc环境 先查看本机是否安装gcc gcc version 如果没有就需要安装 gcc gcc c gcc g gcc gnat gcc java gcc objc libgcj libgcj devel l
  • C/C++ 杨辉三角形

    题目描述 还记得中学时候学过的杨辉三角形吗 具体的定义这里不再描述 你可以参考以下的图形 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 输入 输入数据包含多个测试实例 每个测试实例的输入只包含一个正整数n 1 lt n lt
  • AJAX学习笔记8 跨域问题及解决方案

    AJAX学习笔记7 AJAX实现省市联动 biubiubiu0706的博客 CSDN博客 跨域 指一个域名的网页去请求另外一个域名资源 比如百度页面去请求京东页面资源 同源与不同源三要素 协议 域名 端口 协议一致 域名一致 端口一致 才算
  • JAVA中的内存分配

    JAVA中的内存分配 栈 方法运行时使用的内存 比如main方法的运行 进入方法栈中执行 堆 存储对象或数组 new来创建的 都存储在堆内存中 方法区 存储可以运行的class文件 本地方法栈 JVM在使用操作系统功能的时候使用 和我们开发
  • 查询、关闭正在运行的Tomcat端口

    查询正在使用的端口 快捷键win R 输入cmd 回车 打开cmd窗口 查看所有的端口进程 请输入netstat ano 查看某个特定端口 输入netstat ano findstr 8089 关闭某个端口进程 输入taskkill f p
  • Javaweb

    一 创建包和类来编译servlet程序 二 编译和运行
  • 如何在老版本浏览器中丝滑地使用JS新特性(ES6)

    如何在老版本浏览器中丝滑地使用JS新特性呢 如何在老版本浏览器中丝滑地使用JS新特性呢 有两种方法可以帮助我们实现 第一种方法就是我们用JS原有的方法 自己去实现JS的新特性 不是说好的丝滑使用新特性吗 就这 哈哈哈 别急 客官留步 我还有
  • spring boot 数据库层

    项目开启 首先设计数据库以及存储表 表的联系 需要存贮的信息 基本表的性质 基本表与中间表 临时表不同 因为它具有如下四个特性 1 原子性 基本表中的字段是不可再分解的 2 原始性 基本表中的记录是原始数据 基础数据 的记录 3 演绎性 由
  • openid和unionid的区别

    openid和unionid的区别 1 微信openid和unionid长度是不一样的 openid 28 unionid 29 2 openid同一用户同一应用唯一 unionid同一用户不同应用唯一 这里的不同应用是指在同一微信开发平台
  • C++学习6

    堆 是存在于某个作用域的一个内存空间 例如 当你调用函数 函数本身会形成一个栈用来放置它所接收的参数 以及返回地址 栈 由操作系统提供的一个全局的内存空间 程序可动态分配 内存管理 生命周期 栈对象 离开堆的作用域 会调用对象的析构函数 内
  • rabbitmq分布式事务解决方案

    发送消息到mq 流程 用户下订单创建订单信息 且创建一条订单冗余信息 status 为 0 发送订单信息到mq 使用ack 消息确认机制 确认消息发送成功修改订单状态为 1 表示消息已发送 启动一个定时任务 排查 订单状态为 0 的订单 发