RabbitMQ系列(15)--死信队列的简介与死信队列和死信消费者的实现

news2024/7/5 20:54:41

1、死信的概念

死信,顾名思义就是无法被消费的消息,一般来说producer(生产者)将消息投递到broker或直接放到queue(队列)中,consumer(消费者)从queue(队列)取出消息进行消费,但某些时候由于特定的原因导致queue(队列)中的消息无法被消费,若这些消息没有后续的处理,则这些消息就变成了死信,有死信自然就有了死信队列

2、死信的应用场景

为保证订单业务的消息数据不丢失,需要使用RabbitMQ的死信队列机制,当消息发生异常时,将消息投入死信队列中

3、死信的来源

(1)消息TTL(存活时间)过期

(2)队列达到最大长度(队列满了,无法再添加数据到mq中)

(3)消息被拒绝(basic.reject或basic.nack)并且requeue=false

4、死信队列的实现

(1)我们将根据这张死信队列的代码架构图来实现死信队列

(2)新建一个名为dead的包,用于装实现死信队列的代码

效果图:

(3)新建一个名为Consumer01的类用于编写消费者的代码

代码如下:

 注:RabbitMqUtils工具类的实现在我的另一篇文章里,有需要的同学可以查看参考

RabbitMQ系列(6)--RabbitMQ模式之工作队列(Work queues)的简介及实现_Ken_1115的博客-CSDN博客

package com.ken.dead;

import com.ken.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;

import java.util.HashMap;
import java.util.Map;

public class Consumer01 {

    //普通交换机的名称
    public static  final String NORMAL_EXCHANGE = "normal_exchange";
    //死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";

    //普通队列的名称
    public static  final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();

        //声明普通死信交换机,类型为direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明死信交换机,类型为direct
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);

        //用于在消息成为死信后,把消息转发到死信交换机dead_exchange里
        Map<String, Object> arguments = new HashMap<>();
        //正常队列设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信routingkey
        arguments.put("x-dead-letter-routing-key","dead");
        //设置正常队列长度的限制
        //arguments.put("x-max-length",6);
        /**
         * 声明普通队列
         * 第一个参数:队列名称
         * 第二个参数:服务器重启后队列是否还存在,即队列是否持久化,true为是,false为否,默认false,即消息存储在内存中而不是硬盘中
         * 第三个参数:该队列是否只供一个消费者进行消费,是否进行消息共享,true为只允许一个消费者进行消费,false为允许多个消费者对队列进行消费,默认false
         * 第四个参数:是否自动删除,最后一个消费者断开连接后该队列是否自动删除,true自动删除,false不自动删除
         * 第五个参数:其他参数
         */
        channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
        /**
         * 声明死信队列
         * 第一个参数:队列名称
         * 第二个参数:服务器重启后队列是否还存在,即队列是否持久化,true为是,false为否,默认false,即消息存储在内存中而不是硬盘中
         * 第三个参数:该队列是否只供一个消费者进行消费,是否进行消息共享,true为只允许一个消费者进行消费,false为允许多个消费者对队列进行消费,默认false
         * 第四个参数:是否自动删除,最后一个消费者断开连接后该队列是否自动删除,true自动删除,false不自动删除
         * 第五个参数:其他参数
         */
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
        //普通队列与普通交换机通过routingkey进行捆绑
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normal");
        //死信队列与死信交换机通过routingkey进行捆绑
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"dead");

        /**
         * 声明消费者接收消息后的回调方法(由于回调方法DeliverCallback是函数式接口,所以需要给DeliverCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数deliverCallback的类型DeliverCallback用 @FunctionalInterface注解规定DeliverCallback是一个函数式接口,所以要往deliverCallback参数传的值要是一个函数
         *
         * 以下是DeliverCallback接口的源代码
         *  @FunctionalInterface
         *  public interface DeliverCallback {
         *      void handle (String consumerTag, Delivery message) throws IOException;
         *  }
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer01接收的消息是:" + new String(message.getBody(),"UTF-8"));
        };

        channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
    }

}

(4)复制Consumer01类并粘贴重命名为Consumer02

代码如下:

package com.ken.dead;

import com.ken.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.util.HashMap;
import java.util.Map;

public class Consumer02 {

    //普通队列的名称
    public static  final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        /**
         * 声明消费者接收消息后的回调方法(由于回调方法DeliverCallback是函数式接口,所以需要给DeliverCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数deliverCallback的类型DeliverCallback用 @FunctionalInterface注解规定DeliverCallback是一个函数式接口,所以要往deliverCallback参数传的值要是一个函数
         *
         * 以下是DeliverCallback接口的源代码
         *  @FunctionalInterface
         *  public interface DeliverCallback {
         *      void handle (String consumerTag, Delivery message) throws IOException;
         *  }
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer02接收的消息是:" + new String(message.getBody(),"UTF-8"));
        };

        channel.basicConsume(DEAD_QUEUE,true,deliverCallback,consumerTag -> {});
    }

}

(5)新建一个名为Producer的类用于编写生产者的代码

代码如下:

package com.ken.dead;

import com.ken.utils.RabbitMqUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;

/**
 * 生产者
 */
public class Producer {

    //普通交换机的名称
    public static  final String NORMAL_EXCHANGE = "normal_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        //死信消息,设置TTL时间(存活时间),单位是ms 10000ms = 10s
        AMQP.BasicProperties properties= new AMQP.BasicProperties().builder().expiration("10000").build();
        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            /**
             * 用信道对消息进行发布(消息持久化)
             * 第一个参数:发送到哪个交换机
             * 第二个参数:路由的Key值是哪个,本次是队列名
             * 第三个参数:其他参数信息
             * 第四个参数:发送消息的消息体
             */
            channel.basicPublish(NORMAL_EXCHANGE,"normal",properties,message.getBytes());
        }
        
    }

}

 (6)先运行Consumer01,生成队列和交换机 

(7)然后停止Consumer01,模拟消费者宕机

(8)运行Producer

(9)观察normal_queue队列和dead_queue队列消息数量的变化,一开始normal_queue队列里有10条消息,过了10s后消息都到了dead_queue队列里,证明消费者消费消息失败,消息从normal_queue队列移到了dead_queue队列里,由此可见当消息TTL过期后,死信队列成功运行

(10)启动Consumer02,可以看到Consumer02消费了dead_queue队列里的消息

(10)删除normal_queue队列

(11) 修改Produces的代码,把设置TTL时间的代码注释掉,basicPublish方法的第3个参数设置为null

代码如下:

package com.ken.dead;

import com.ken.utils.RabbitMqUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;

/**
 * 生产者
 */
public class Producer {

    //普通交换机的名称
    public static  final String NORMAL_EXCHANGE = "normal_exchange";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        //死信消息,设置TTL时间(存活时间),单位是ms 10000ms = 10s
        //AMQP.BasicProperties properties= new AMQP.BasicProperties().builder().expiration("10000").build();
        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            /**
             * 用信道对消息进行发布(消息持久化)
             * 第一个参数:发送到哪个交换机
             * 第二个参数:路由的Key值是哪个,本次是队列名
             * 第三个参数:其他参数信息
             * 第四个参数:发送消息的消息体
             */
            channel.basicPublish(NORMAL_EXCHANGE,"normal",null,message.getBytes());
        }

    }

}

(12)修改Consumer01的代码,把之前注释的用于限制队列长度的代码放出来

代码如下:

package com.ken.dead;

import com.ken.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;

import java.util.HashMap;
import java.util.Map;

public class Consumer01 {

    //普通交换机的名称
    public static  final String NORMAL_EXCHANGE = "normal_exchange";
    //死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";

    //普通队列的名称
    public static  final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();

        //声明普通死信交换机,类型为direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明死信交换机,类型为direct
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);

        //用于在消息成为死信后,把消息转发到死信交换机dead_exchange里
        Map<String, Object> arguments = new HashMap<>();
        //正常队列设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信routingkey
        arguments.put("x-dead-letter-routing-key","dead");
        //设置正常队列长度的限制
        arguments.put("x-max-length",6);
        /**
         * 声明普通队列
         * 第一个参数:队列名称
         * 第二个参数:服务器重启后队列是否还存在,即队列是否持久化,true为是,false为否,默认false,即消息存储在内存中而不是硬盘中
         * 第三个参数:该队列是否只供一个消费者进行消费,是否进行消息共享,true为只允许一个消费者进行消费,false为允许多个消费者对队列进行消费,默认false
         * 第四个参数:是否自动删除,最后一个消费者断开连接后该队列是否自动删除,true自动删除,false不自动删除
         * 第五个参数:其他参数
         */
        channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
        /**
         * 声明死信队列
         * 第一个参数:队列名称
         * 第二个参数:服务器重启后队列是否还存在,即队列是否持久化,true为是,false为否,默认false,即消息存储在内存中而不是硬盘中
         * 第三个参数:该队列是否只供一个消费者进行消费,是否进行消息共享,true为只允许一个消费者进行消费,false为允许多个消费者对队列进行消费,默认false
         * 第四个参数:是否自动删除,最后一个消费者断开连接后该队列是否自动删除,true自动删除,false不自动删除
         * 第五个参数:其他参数
         */
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
        //普通队列与普通交换机通过routingkey进行捆绑
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normal");
        //死信队列与死信交换机通过routingkey进行捆绑
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"dead");

        /**
         * 声明消费者接收消息后的回调方法(由于回调方法DeliverCallback是函数式接口,所以需要给DeliverCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数deliverCallback的类型DeliverCallback用 @FunctionalInterface注解规定DeliverCallback是一个函数式接口,所以要往deliverCallback参数传的值要是一个函数
         *
         * 以下是DeliverCallback接口的源代码
         *  @FunctionalInterface
         *  public interface DeliverCallback {
         *      void handle (String consumerTag, Delivery message) throws IOException;
         *  }
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Consumer01接收的消息是:" + new String(message.getBody(),"UTF-8"));
        };

        channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
    }

}

(13)停掉所有程序,然后重新运行Consumer01,生成队列和交换机 

 (14)然后停止Consumer01,模拟消费者宕机

(15)重新运行Producer

(16)观察normal_queue队列和dead_queue队列消息数量的变化,可以看到normal_queue队列只能堆积最多6条消息,而剩余的4条消息都移到了dead_queue队列里,由此可见当队列达到最大长度6条后,死信队列成功运行

 (17)删除normal_queue队列和dead_queue队列

(18)修改Consumer01的代码,注释掉限制队列长度的代码,修改消息应答的部分代码 

代码如下:

package com.ken.dead;

import com.ken.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;

import java.util.HashMap;
import java.util.Map;

public class Consumer01 {

    //普通交换机的名称
    public static  final String NORMAL_EXCHANGE = "normal_exchange";
    //死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";

    //普通队列的名称
    public static  final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();

        //声明普通死信交换机,类型为direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明死信交换机,类型为direct
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);

        //用于在消息成为死信后,把消息转发到死信交换机dead_exchange里
        Map<String, Object> arguments = new HashMap<>();
        //正常队列设置死信交换机
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信routingkey
        arguments.put("x-dead-letter-routing-key","dead");
        //设置正常队列长度的限制
        //arguments.put("x-max-length",6);
        /**
         * 声明普通队列
         * 第一个参数:队列名称
         * 第二个参数:服务器重启后队列是否还存在,即队列是否持久化,true为是,false为否,默认false,即消息存储在内存中而不是硬盘中
         * 第三个参数:该队列是否只供一个消费者进行消费,是否进行消息共享,true为只允许一个消费者进行消费,false为允许多个消费者对队列进行消费,默认false
         * 第四个参数:是否自动删除,最后一个消费者断开连接后该队列是否自动删除,true自动删除,false不自动删除
         * 第五个参数:其他参数
         */
        channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
        /**
         * 声明死信队列
         * 第一个参数:队列名称
         * 第二个参数:服务器重启后队列是否还存在,即队列是否持久化,true为是,false为否,默认false,即消息存储在内存中而不是硬盘中
         * 第三个参数:该队列是否只供一个消费者进行消费,是否进行消息共享,true为只允许一个消费者进行消费,false为允许多个消费者对队列进行消费,默认false
         * 第四个参数:是否自动删除,最后一个消费者断开连接后该队列是否自动删除,true自动删除,false不自动删除
         * 第五个参数:其他参数
         */
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
        //普通队列与普通交换机通过routingkey进行捆绑
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normal");
        //死信队列与死信交换机通过routingkey进行捆绑
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"dead");

        /**
         * 声明消费者接收消息后的回调方法(由于回调方法DeliverCallback是函数式接口,所以需要给DeliverCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数deliverCallback的类型DeliverCallback用 @FunctionalInterface注解规定DeliverCallback是一个函数式接口,所以要往deliverCallback参数传的值要是一个函数
         *
         * 以下是DeliverCallback接口的源代码
         *  @FunctionalInterface
         *  public interface DeliverCallback {
         *      void handle (String consumerTag, Delivery message) throws IOException;
         *  }
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String mes = new String(message.getBody(),"UTF-8");
            //消息被拒绝,basicReject第二个参数是false表示被拒绝后不放回队列
            if("info5".equals(mes)) {
                System.out.println("Consumer01接收的消息是:" + mes + ",此消息被拒绝");
                channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
            }else {
                System.out.println("Consumer01接收的消息是:" + mes);
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            }
        };
        //开启手动应答
        channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,consumerTag -> {});
    }

}

 (19)停掉所有程序,然后重新运行Consumer01,生成队列和交换机 

(20)重新运行Producer

(21)查看Consumer01控制台的输出,观察dead_queue队列消息数量的变化,因为没有开启Consumer02消费dead_queue队列,可以看到dead_queue队列堆积了1条消息,查看这条消息,可以看出就是我们拒绝掉的info5,这证明消费者拒绝消费消息info5后,消息info5从normal_queue队列移到了dead_queue队列里,由此可见当消息被拒绝消费后,死信队列成功运行

(22)启动Consumer02,可以看到Consumer02消费了dead_queue队列里的消息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/726345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Css基础:盒子模型

1.盒子模型的构成&#xff1a; 边框 外边距 内边距 实际内容 2.table表格的单元格之间的线太粗需要border-collapse:collapse;合并一下边框宽度 3.内边距 padding 4.外边距 margin 块元素水平居中的做法&#xff0c;margin:0 auto; 行内元素和行内块元素 水平居中做…

FreeRTOS ~(五)队列的常规使用 ~ (2/5)队列解决互斥缺陷

前情提要 FreeRTOS ~&#xff08;四&#xff09;同步互斥与通信 ~ &#xff08;2/3&#xff09;互斥的缺陷 举例子说明&#xff1a;利用队列解决前述的"互斥的缺陷"问题 static QueueHandle_t xQueueUARTHandle;/* 利用队列的写数据和读数据来做类似标志位的工作,类…

C++笔记之数组拷贝和vector拷贝

C笔记之数组拷贝和vector拷贝 code review! 文章目录 C笔记之数组拷贝和vector拷贝一.C数组拷贝1.使用循环2.使用std::copy算法3.使用std::array 二.C语言数组拷贝1.使用循环2.使用memcpy函数3.使用for循环和指针 三.CVector拷贝四.公众号&#xff1a;三戒纪元 博文摘抄——C…

Dynamsoft 条形码阅读器 10.0.0 Crack

Dynamsoft 条形码阅读器 10.0.0 将来自不同来源的图像数据转换为标准输入图像数据。 7月 06&#xff0c; 2023 - 10&#xff1a;32新版本 特征 SDK经过重构&#xff0c;与DynamsoftCaptureVision&#xff08;DCV&#xff09;架构集成&#xff0c;该架构包括&#xff1a; ImageS…

提升车道运行效率——远眺智慧可变车道控制系统

精细科学的交通组织是提升道路通行效率的有效途径。可变导向车道&#xff08;后文简称可变车道&#xff09;作为精细化交通组织的重要手段&#xff0c;能够有效地利用现有道路空间资源、提高道路通行效率。在交通拥堵日趋严重的情况下&#xff0c;其在保障交通畅通方面起着重要…

Cisco AnyConnect Secure Mobility Client 4.10.07062 (macOS, Linux, Windows)

Cisco AnyConnect Secure Mobility Client 4.10.07062 (macOS, Linux, Windows) Cisco Secure Client&#xff08;包括 AnyConnect&#xff09; 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-anyconnect-4/&#xff0c;查看最新版。原创作品&#xff0c;转载请保…

warning LNK4098: 默认库“msvcrtd.lib”与其他库的使用冲突;请使用 /NODEFAULTLIB:library

1>LINK : warning LNK4098: 默认库“msvcrtd.lib”与其他库的使用冲突&#xff1b;请使用 /NODEFAULTLIB:library 1>LINK : warning LNK4098: 默认库“LIBCMTD”与其他库的使用冲突&#xff1b;请使用 /NODEFAULTLIB:library 知识背景&#xff1a; VC中有4个CRT链接库版…

java项目之九宫格日志网站(ssm+jsp+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的九宫格日志网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌&#x…

读发布!设计与部署稳定的分布式系统(第2版)笔记21_实例层之配置

1. 导致运维失误的两大因素 1.1. 隐秘的连锁反应 1.2. 暗藏的高复杂度 1.3. 影响着配置属性 2. 配置 2.1. 配置属性是系统用户接口的一部分&#xff0c;供支持其开发和运维的人员使用 2.1.1. 最易被忽视 2.2. 生产级别的软件都有大量可配置的属性 2.2.1. 主机名 2.2.2…

el-breadcrumb面包屑详解

el-breadcrumb面包屑详解 封装面包屑组件 <template><div class"crumb"><el-breadcrumb separator"/"><template v-for"(item,index) in levelList"><el-breadcrumb-item :key"item_ index">{{item.na…

SQL Server 2012数据库允许远程连接设置

1、打开 SQL Server Management Studio 2、打开 Security 按照如下设置&#xff0c;然后点确定 3、打开SQL Server Configuration ManagerMent 4、如下图都设置为Enabled 6、sql server重启

寻找最佳同步云盘:如何选择适合您的高效、可靠的云端存储方案?

同步云盘是集备份、同步、共享于一体的软件&#xff0c;备受用户青睐。在这个信息化的时代&#xff0c;我们经常需要在不同的设备之间共享文件&#xff0c;而同步云盘正可以帮助我们在多台设备之间同步文件。不过目前市面上同步云盘众多&#xff0c;我们该如何选择同步云盘呢&a…

JMeter常用业务知识和组件(5)

这里写目录标题 一、信息头管理器1案例、测试开发平台登录接口2案例、测试平台获取测试用例接口 二、HTTP请求默认值案例1&#xff1a;实现登录接口测试 三、Cookie管理器&#xff08;有问题&#xff09;案例1&#xff1a;开源项目TPshop商城登录案例案例2&#xff1a;(有问题)…

双向复制粘贴半监督医学图像分割

文章目录 Bidirectional Copy-Paste for Semi-Supervised Medical Image Segmentation摘要本文方法实验结果 Bidirectional Copy-Paste for Semi-Supervised Medical Image Segmentation 摘要 在半监督医学图像分割中&#xff0c;存在标记数据与未标记数据分布不匹配的问题。…

Python基础 —— 变量

〇、概述 变量&#xff0c;正如其字面意思所言&#xff0c;即 *“变化的量*”&#xff0c;通过变量&#xff0c;可以将数据临时存储。 这就好像图书馆的书架&#xff0c;存放着一本本包含着很多知识的书。为了方便查找&#xff0c;图书馆里的每本书都有编号&#xff0c;书架也…

#VCS# 关于VCS 编译选项:+vcs+initreg+random的理解(3)回头是岸

前段时间,有时间整理了一下关于+vcs+initreg+random 编译和仿真选项的心得,草草写了两篇笔记。自觉得对该选项已经掌握了,后来实际应用中再次触及到了该知识点,不想又卡壳了。今天,继续追加一篇,希望对大家能有所帮助!不正之处,请指正。 对于上述规则rule#2和 rule#3,…

DM数据库迁移,从DM到DM

1、打开DM数据迁移工具 2、新建工程 3、新建迁移 4、下一步 如果连接不上可能需要指定驱动

ChatGPT Prompting开发实战(一)

第7章 ChatGPT Prompting开发实战 7.1 Prompting在LangChain框架中的应用 本节跟大家讲提示工程(Prompt Engineering),主要基于工业级的源码以及具体的项目,无论是工程人员,还是不具有技术背景的人员,大家多少都听说过提示词,或者频繁使用过,简单而言,当我们使用OpenA…

品牌低价监测要注意哪些方面

对于所有销售渠道的价格&#xff0c;品牌一般都会有相应的标准&#xff0c;比如会设置建议零售价&#xff0c;这也是为了渠道间的合理发展&#xff0c;让品牌能够长期更好的生存&#xff0c;所以设置了相应价格线的产品&#xff0c;品牌当然不想出现低价产品链接。所以针对价格…

关于旋转向量和罗德里格斯旋转公式

使用cv2.Rodrigues函数即可实现 import cv2 import numpy as np # 相机标定 ret, rvec, tvec, idx, std_dev cv2.calibrateCamera(...) # 将rvec转换为旋转矩阵 R cv2.Rodrigues(rvec) # 将旋转矩阵转换为欧拉角 roll, pitch, yaw cv2.decomposeHomographyMat(R…