RabbitMQ - 死信队列,延时队列

news2024/11/17 1:43:48

Time-To-Live and Expiration — RabbitMQ

一、死信队列

Dead Letter Exchanges — RabbitMQ

死信队列:

DLX 全称(Dead-Letter-Exchange),称之为死信交换器,当消息变成一个死信之后,如果这个消息所在的队列存在x-dead-letter-exchange参数,那么它会被发送到x-dead-letter-exchange对应值的交换器上,这个交换器就称之为死信交换器,与这个死信交换器绑定的队列就是死信队列

死信消息:

  • 消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
  • 消息过期(消息TTL过期。TTL:Time To Live的简称,即过期时间)
  • 队列达到最大的长度

过期消息:

在 rabbitmq 中存在2种方法可设置消息的过期时间:

  • 第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间
  • 第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样

如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息

队列设置:在队列申明的时候使用** x-message-ttl **参数,单位为 毫秒;

  • 队列中这个属性的设置要在第一次声明队列的时候设置才有效,如果队列一开始已存在且没有这个属性,则要删掉队列再重新声明才可以。
  • 队列的 TTL 只能被设置为某个固定的值,一旦设置后则不能更改,否则会抛出异常

单个消息设置:是设置消息属性的 expiration 参数的值,单位为 毫秒。

说明:

对于第一种设置队列属性的方法,一旦消息过期,就会从队列中抹去;而在第二种方法中,即使消息过期,也不会马上从队列中抹去,因为每条消息是否过期是在即将投递到消费者之前判定的

 1. 生产者:
  声明队列的时候用属性指定其死信队列交换机名称。

测试:

package rabbitmq;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {

    public static ConnectionFactory getConnectionFactory() {
        // 创建连接工程,下面给出的是默认的case
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.99.100");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        return factory;
    }

    public static void main(String[] args) throws IOException, TimeoutException  {
        ConnectionFactory connectionFactory = getConnectionFactory();
        Connection newConnection = null;
        Channel createChannel = null;
        try {
            newConnection = connectionFactory.newConnection();
            createChannel = newConnection.createChannel();
            
            // 声明一个正常的direct类型的交换机
            createChannel.exchangeDeclare("order.exchange", BuiltinExchangeType.DIRECT);
            // 声明死信交换机为===order.dead.exchange
            String dlxName = "order.dead.exchange";
            createChannel.exchangeDeclare(dlxName, BuiltinExchangeType.DIRECT);
            // 声明队列并指定死信交换机为上面死信交换机
            Map<String, Object> arg = new HashMap<String, Object>();
            arg.put("x-dead-letter-exchange", dlxName);
            createChannel.queueDeclare("myQueue", true, false, false, arg);
            
            String message = "测试消息";
            createChannel.basicPublish("order.exchange", "routing_key_myQueue", null, message.getBytes());
            System.out.println("消息发送成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (createChannel != null) {
                createChannel.close();
            }
            if (newConnection != null) {
                newConnection.close();
            }
        }
        
    }
}

结果:

(1)生成两个Exchange

 (2)队列myQueue的死信队列有属性

2. 消费者: 
  一个消费者监听正常队列,一个消费者监听死信队列。(只是绑定的交换机不同)

消费者一:监听正常队列

package rabbitmq;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Consumer {

    public static ConnectionFactory getConnectionFactory() {
        // 创建连接工程,下面给出的是默认的case
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.99.100");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        return factory;
    }

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = getConnectionFactory();
        Connection newConnection = null;
        Channel createChannel = null;
        try {
            newConnection = connectionFactory.newConnection();
            createChannel = newConnection.createChannel();

            // 队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
            createChannel.queueBind("myQueue", "order.exchange", "routing_key_myQueue");

            createChannel.basicConsume("myQueue", false, "", new DefaultConsumer(createChannel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                        byte[] body) throws IOException {

                    System.out.println("consumerTag: " + consumerTag);
                    System.out.println("envelope: " + envelope);
                    System.out.println("properties: " + properties);
                    String string = new String(body, "UTF-8");
                    System.out.println("接收到消息: -》 " + string);

                    long deliveryTag = envelope.getDeliveryTag();
                    Channel channel = this.getChannel();
                    System.out.println("拒绝消息, 使之进入死信队列");
                    System.out.println("时间: " + new Date());
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                    }
                    
                    // basicReject第二个参数为false进入死信队列或丢弃
                    channel.basicReject(deliveryTag, false);
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }

    }
}

消费者二:监听死信队列

package rabbitmq;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Consumer2 {

    public static ConnectionFactory getConnectionFactory() {
        // 创建连接工程,下面给出的是默认的case
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.99.100");
        factory.setPort(5672);
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        return factory;
    }

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = getConnectionFactory();
        Connection newConnection = null;
        Channel createChannel = null;
        try {
            newConnection = connectionFactory.newConnection();
            createChannel = newConnection.createChannel();

            // 队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
            createChannel.queueBind("myQueue", "order.dead.exchange", "routing_key_myQueue");

            createChannel.basicConsume("myQueue", false, "", new DefaultConsumer(createChannel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                        byte[] body) throws IOException {

                    System.out.println("时间: " + new Date());
                    
                    System.out.println("consumerTag: " + consumerTag);
                    System.out.println("envelope: " + envelope);
                    System.out.println("properties: " + properties);
                    String string = new String(body, "UTF-8");
                    System.out.println("接收到消息: -》 " + string);

                    long deliveryTag = envelope.getDeliveryTag();
                    Channel channel = this.getChannel();
                    channel.basicAck(deliveryTag, true);
                    System.out.println("死信队列中处理完消息息");
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }

    }
}

结果: 消费者一先正常监听到,basicReject为false拒绝后进入死信队列;消费者二监听的死信队列收到消息。

消费者一打出的日志如下:

consumerTag: amq.ctag-0noHs24F0FsGe-dfwwqWNw
envelope: Envelope(deliveryTag=1, redeliver=false, exchange=order.exchange, routingKey=routing_key_myQueue)
properties: #contentHeader<basic>(content-type=null, content-encoding=null, headers=null, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
接收到消息: -》 测试消息
拒绝消息, 使之进入死信队列
时间: Sat Nov 07 12:18:44 CST 2020

消费者二打出的日志如下:

时间: Sat Nov 07 12:18:47 CST 2020
consumerTag: amq.ctag-ajYMpMFkXHDiYWkD3XFJ7Q
envelope: Envelope(deliveryTag=1, redeliver=false, exchange=order.dead.exchange, routingKey=routing_key_myQueue)
properties: #contentHeader<basic>(content-type=null, content-encoding=null, headers={x-death=[{reason=rejected, count=1, exchange=order.exchange, time=Sat Nov 07 01:52:19 CST 2020, routing-keys=[routing_key_myQueue], queue=myQueue}]}, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
接收到消息: -》 测试消息
死信队列中处理完消息息

注意:

  进入死信队列之后,headers 加了一些死信相关的信息,包括原队列以及进入死信的原因。

补充:在队列进入死信队列之前也可以修改其routingKey,而且只有在指定x-dead-letter-exchange的前提下才能修改下面属性,否则会报错

(1)修改生产者声明队列的方式,如下:

// 声明一个正常的direct类型的交换机
            createChannel.exchangeDeclare("order.exchange", BuiltinExchangeType.DIRECT);
            // 声明死信交换机为===order.dead.exchange
            String dlxName = "order.dead.exchange";
            createChannel.exchangeDeclare(dlxName, BuiltinExchangeType.DIRECT);
            // 声明队列并指定死信交换机为上面死信交换机
            Map<String, Object> arg = new HashMap<String, Object>();
            arg.put("x-dead-letter-exchange", dlxName);
            // 修改进入死信队列的routingkey,如果不修改会使用默认的routingKey
            arg.put("x-dead-letter-routing-key", "routing_key_myQueue_dead");
            createChannel.queueDeclare("myQueue", true, false, false, arg);

(2)修改监听死信队列的消费者二:

// 队列绑定交换机-channel.queueBind(队列名, 交换机名, 路由key[广播消息设置为空串])
            createChannel.queueBind("myQueue", "order.dead.exchange", "routing_key_myQueue_dead");

结果,收到消费者二收到的信息如下:

时间: Sat Nov 07 12:27:08 CST 2020
consumerTag: amq.ctag-THqpEdYH_-iNeCIccgpuaw
envelope: Envelope(deliveryTag=1, redeliver=false, exchange=order.dead.exchange, routingKey=routing_key_myQueue_dead)
properties: #contentHeader<basic>(content-type=null, content-encoding=null, headers={x-death=[{reason=rejected, count=1, exchange=order.exchange, time=Sat Nov 07 02:00:41 CST 2020, routing-keys=[routing_key_myQueue], queue=myQueue}]}, delivery-mode=null, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
接收到消息: -》 测试消息
死信队列中处理完消息

二、延时队列

延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费

RabbitMQ本身没提供延时队列,我们可以利用消息的生存时间和死信队列实现延时

典型的应用场景就是订单30分钟内未支付就关闭订单,还有一种场景,账单24小时未确认,就发送提醒消息

延时队列插件安装

2.1.1、yml配置

spring:
    rabbitmq:
        host: 192.168.99.12
        port: 5672
        username: guest
        password: guest
        # 发送确认
        publisher-confirms: true
        # 路由失败回调
        publisher-returns: true
        template:
            # 必须设置成true 消息路由失败通知监听者,false 将消息丢弃
            mandatory: true
        #消费端
        listener:
            simple:
                # 每次从RabbitMQ获取的消息数量
                prefetch: 1
                default-requeue-rejected: false
                # 每个队列启动的消费者数量
                concurrency: 1
                # 每个队列最大的消费者数量
                max-concurrency: 1
                # 签收模式为手动签收-那么需要在代码中手动ACK
                acknowledge-mode: manual
#邮件队列
email:
    queue:
        name: demo.email

#邮件交换器名称
exchange:
    name: demoTopicExchange

#死信队列
dead:
    letter:
        queue:
            name: demo.dead.letter
        exchange:
            name: demoDeadLetterTopicExchange

#延时队列
delay:
    queue:
        name: demo.delay
    exchange:
        name: demoDelayTopicExchange

2.1.2、延时队列配置

/**
 * rabbitmq 配置
 *
 * @author DUCHONG
 * @since 2020-08-23 14:05
 **/
@Configuration
@Slf4j
public class RabbitmqConfig {


    @Value("${email.queue.name}")
    private String emailQueue;
    @Value("${exchange.name}")
    private String topicExchange;
    @Value("${dead.letter.queue.name}")
    private String deadLetterQueue;
    @Value("${dead.letter.exchange.name}")
    private String deadLetterExchange;
    @Value("${delay.queue.name}")
    private String delayQueue;
    @Value("${delay.exchange.name}")
    private String delayExchange;

    @Bean
    public Queue emailQueue() {

        Map<String, Object> arguments = new HashMap<>(2);
        // 绑定死信交换机
        arguments.put("x-dead-letter-exchange", deadLetterExchange);
        // 绑定死信的路由key
        arguments.put("x-dead-letter-routing-key", deadLetterQueue+".#");

        return new Queue(emailQueue,true,false,false,arguments);
    }


    @Bean
    TopicExchange emailExchange() {
        return new TopicExchange(topicExchange);
    }


    @Bean
    Binding bindingEmailQueue() {
        return BindingBuilder.bind(emailQueue()).to(emailExchange()).with(emailQueue+".#");
    }


    //私信队列和交换器
    @Bean
    public Queue deadLetterQueue() {
        return new Queue(deadLetterQueue);
    }

    @Bean
    TopicExchange deadLetterExchange() {
        return new TopicExchange(deadLetterExchange);
    }

    @Bean
    Binding bindingDeadLetterQueue() {
        return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with(deadLetterQueue+".#");
    }
    //延时队列
    @Bean
    public Queue delayQueue() {
        return new Queue(delayQueue);
    }

    @Bean
    CustomExchange delayExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "topic");
        //参数二为类型:必须是x-delayed-message
        return new CustomExchange(delayExchange, "x-delayed-message", true, false, args);

    }

    @Bean
    Binding bindingDelayQueue() {
        return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(delayQueue+".#").noargs();
    }
}

2.2、消息发送方

30分钟时间太久了,这里延时2分钟来看效果

@Configuration
@EnableScheduling
@Slf4j
public class ScheduleController {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Value("${exchange.name}")
    private String topicExchange;

    @Value("${delay.exchange.name}")
    private String delayTopicExchange;

    @Scheduled(cron = "0 0/1 * * * ?")
    public void sendEmailMessage() {

        String msg = RandomStringUtils.randomAlphanumeric(8);
        JSONObject email=new JSONObject();
        email.put("content",msg);
        email.put("to","duchong@qq.com");
        CorrelationData correlationData=new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(topicExchange,"demo.email.x",email.toJSONString(),correlationData);
        log.info("---发送 email 消息---{}---messageId---{}",email,correlationData.getId());
    }


    @Scheduled(cron = "0 0/1 * * * ?")
    public void sendDelayOrderMessage() throws Exception{

        //订单号 id实际是保存订单后返回的,这里用uuid代替
        String orderId = UUID.randomUUID().toString();
        // 模拟订单信息
        JSONObject order=new JSONObject();
        order.put("orderId",orderId);
        order.put("goodsName","vip充值");
        order.put("orderAmount","99.00");
        CorrelationData correlationData=new CorrelationData(orderId);
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setMessageId(orderId);
        //30分钟时间太长,这里延时120s消费
        messageProperties.setHeader("x-delay", 120000);
        Message message = new Message(order.toJSONString().getBytes(CharEncoding.UTF_8), messageProperties);

        rabbitTemplate.convertAndSend(delayTopicExchange,"demo.delay.x",message,correlationData);

        log.info("---发送 order 消息---{}---orderId---{}",order,correlationData.getId());
        //睡一会,为了看延迟效果
        TimeUnit.MINUTES.sleep(10);
    }
}

2.3、消息消费方

@Component
@Slf4j
public class MessageHandler {


    /**
     * 邮件发送
     * @param message
     * @param channel
     * @param headers
     * @throws IOException
     */
    @RabbitListener(queues ="demo.email")
    @RabbitHandler
    public void handleEmailMessage(Message message, Channel channel, @Headers Map<String,Object> headers) throws IOException {

        try {

            String msg=new String(message.getBody(), CharEncoding.UTF_8);
            JSONObject jsonObject = JSON.parseObject(msg);
            jsonObject.put("messageId",headers.get("spring_returned_message_correlation"));
            log.info("---接受到消息---{}",jsonObject);
			//主动异常
			int m=1/0;
            //手动签收
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }
        catch (Exception e) {
            log.info("handleEmailMessage捕获到异常,拒绝重新入队---消息ID---{}", headers.get("spring_returned_message_correlation"));
            //异常,ture 重新入队,或者false,进入死信队列
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);

        }
    }

    /**
     * 死信消费者,自动签收开启状态下,超过重试次数,或者手动签收,reject或者Nack
     * @param message
     */
    @RabbitListener(queues = "demo.dead.letter")
    public void handleDeadLetterMessage(Message message, Channel channel,@Headers Map<String,Object> headers) throws IOException {

        //可以考虑数据库记录,每次进来查数量,达到一定的数量,进行预警,人工介入处理
        log.info("接收到死信消息:---{}---消息ID---{}", new String(message.getBody()),headers.get("spring_returned_message_correlation"));
		//回复ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

    /**
     * 延时队列消费
     * @param message
     * @param channel
     * @param headers
     * @throws IOException
     */
    @RabbitListener(queues ="demo.delay")
    @RabbitHandler
    public void handleOrderDelayMessage(Message message, Channel channel, @Headers Map<String,Object> headers) throws IOException {

        try {

            String msg=new String(message.getBody(), CharEncoding.UTF_8);
            JSONObject jsonObject = JSON.parseObject(msg);
            log.info("---接受到订单消息---orderId---{}",message.getMessageProperties().getMessageId());
            log.info("---订单信息---order---{}",jsonObject);
            //业务逻辑,根据订单id获取订单信息,如果还未支付,设置关闭状态,如果已支付,不做任何处理
            //手动签收
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }
        catch (Exception e) {
            log.info("handleOrderDelayMessage捕获到异常,重新入队---orderId---{}", headers.get("spring_returned_message_correlation"));
            //异常,ture 重新入队,或者false,进入死信队列
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);

        }
    }

}

2.4、结果

运行结果显示,同一个订单号的消息,发送过后2分钟,消费者才接受到,符合预期

https://www.cnblogs.com/geekdc/p/13550620.html

消息队列RabbitMQ(五):死信队列与延迟队列

rabbitmq的延迟队列和死信队列_死信队列和延时队列的区别_zhuwenaptx的博客-CSDN博客

RabbitMQ的死信队列和延时队列 - 简书

RabbitMQ死信队列与延迟队列_51CTO博客_rabbitmq延迟队列

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

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

相关文章

全球、全国遥感土地利用数据产品下载(1m、10m、30m分辨率,内含链接与详细教程)

土地利用/覆被数据能够获取地表覆被信息&#xff0c;同时也是地球系统科学学科的基础数据&#xff08;如生态、水文、地质等&#xff09;吗&#xff0c;目前&#xff0c;基于遥感生成的土地利用/覆被数据产品比较多样&#xff0c;本文整理了目前应用比较多的7种数据产品进行介绍…

Hazel游戏引擎(007)Premake

文中若有代码、术语等错误&#xff0c;欢迎指正 文章目录 前言操作步骤premake写lua脚本文件执行premake.exe文件效果 前言 此节目的 由于之前配置VS项目各项属性需要根据不同平台手动一个一个设置&#xff0c;很麻烦&#xff0c;缺乏灵活性。 用lua脚本配置项目属性&#xff0…

基于Java+SpringBoot的鞋类商品购物商城系统设计与实现

博主介绍&#xff1a;✌擅长Java、微信小程序、Python、Android等&#xff0c;专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不然下次找不到哟 Java项目精品实战案…

业务部门的通病:想搞了大而全的软件

业务部门的通病&#xff1a;想搞个大而全的软件 怎么样评价软件功能的价值重要性&#xff1f; 软件的消耗成本是惊人的 中小企业一定要约束需求 做SaaS的香港上市公司有赞&#xff08;做商城软件&#xff09; 10年了还在亏损 趣讲大白话&#xff1a;大而全的功能是陷阱 【趣讲信…

Geek-PC项目 文档

一款后台管理项目 - React-geek-PC 项目介绍 ● 项目功能演示 - 登录、退出 - 首页 - 内容&#xff08;文章&#xff09;管理&#xff1a;文章列表、发布文章、修改文章● 技术 - React 官方脚手架 create-react-app - react hooks - 状态管理&#xff1a;mobx - UI 组件库…

ESP32网络应用 -- ESP32-S3在STA模式下创建TCP-CLIENT应用程序

在ESP32-S3初始化为Station模式并且成功获取IP地址后,说明ESP32-S3芯片的底层设施已经具备Wi-Fi网络通信能力,但在实际的应用场景里面,仅仅建立数据链路层,还是不能够满足应用程序的数据通信需求。 TCP/IP是一种使用广泛的网络传输协议,网络上并不缺乏关于TCP/IP的具体原…

Atcoder Beginner Contest 304——A-D题讲解

蒟蒻来讲题&#xff0c;还望大家喜。若哪有问题&#xff0c;大家尽可提&#xff01; Hello, 大家好哇&#xff01;本初中生蒟蒻讲解一下AtCoder Beginner Contest 304这场比赛的A-D题&#xff01; A - First Player 题目描述 Problem Statement There are N N N people nu…

sequence2sequence

1. 基本模型 所谓的Seq2seq模型从字面上理解很简单&#xff0c;就是由一个序列到另一个序列的过程(比如翻译、语音等方面的应用)&#xff1a; 那么既然是序列模型&#xff0c;就需要搭建一个RNN模型(神经单元可以是GRU模型或者是LSTM模型) 下面两篇文章提出了这样的seq2seq的模…

NVM安装教程

我是小荣&#xff0c;给个赞鼓励下吧&#xff01; NVM安装教程 简介 nvm 是node.js的版本管理器&#xff0c;设计为按用户安装&#xff0c;并按 shell 调用。nvm适用于任何符合 POSIX 的 shell&#xff08;sh、dash、ksh、zsh、bash&#xff09;&#xff0c;特别是在这些平台…

ChatGPT 五个写论文的神技巧,让你的老师对你刮目相看!

导读&#xff1a;ChatGPT这款AI工具在推出两个月内就累积了超过1亿用户。我们向您展示如何使用ChatGPT进行写作辅助&#xff0c;以及其他一些有用的写作技巧。 本文字数&#xff1a;2000&#xff0c;阅读时长大约&#xff1a;12分钟 ChatGPT这款AI工具在推出两个月内就累积了超…

【Java|golang】2460. 对数组执行操作

给你一个下标从 0 开始的数组 nums &#xff0c;数组大小为 n &#xff0c;且由 非负 整数组成。 你需要对数组执行 n - 1 步操作&#xff0c;其中第 i 步操作&#xff08;从 0 开始计数&#xff09;要求对 nums 中第 i 个元素执行下述指令&#xff1a; 如果 nums[i] nums[i…

物联网Lora模块从入门到精通(三)按键的读取与使用

一、前言 在Lora例程中&#xff0c;为我们提供了三个按键标志位&#xff0c;我们不需要手动再次初始化按键&#xff0c;即可完成按键的读取。 二、代码实现 首先&#xff0c;我们一起来阅读hal_key.c中的代码&#xff1a; /* Includes -------------------------------------…

点云地面滤波--patchwork++

文章目录 1前言2 反射噪声去除RNR3区域垂直平面拟合 (R-VPF)4自适应地面似然估计(A-GLE)5时序地面恢复TGR总结 1前言 patchwork是在patchwork的基础上进行改进的&#xff0c;主要有2个贡献&#xff1a; 提出了自适应地面似然估计(adaptive ground likelihood estimation (A-G…

Java实训日记第一天——2023.6.6

这里写目录标题 一、关于数据库的增删改查总结&#xff1a;五步法1.增2.删3.改4.查 二、设计数据库的步骤第一步&#xff1a;收集信息第二步&#xff1a;标识对象第三步&#xff1a;标识每个实体的属性第四步&#xff1a;标识对象之间的关系 一、关于数据库的增删改查 总结&am…

Java框架学习--Spring

1.Spring概念【托管很多对象的框架】 一个包含了众多工具方法的IoC容器。 1.1 什么是容器? 容器是用来容纳各种物品的&#xff08;基本&#xff09;装置。--来自百度百科 之前常见的容器有&#xff1a; List/Map-》数据存储容器 Tomcat-》Web容器 1.2什么是IoC? IoCInve…

【Docker】Docker对用户的应用程序使用容器技术遵循的五个步骤和GRSEC详细讲解(文末赠书)

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 &#x1f4d5;作者简介&#xff1a;热…

序列的有关知识

&#x1f4e2;博客主页&#xff1a;盾山狂热粉的博客_CSDN博客-C、C语言,机器视觉领域博主&#x1f4e2;努力努力再努力嗷~~~✨ &#x1f4a1;大纲 ⭕列表、元组、字符串都是序列&#xff0c;列表是可变序列&#xff0c;元组和字符串是不可变序列 一、跟序列相关的操作符 &am…

Hazel游戏引擎(008-009)事件系统

文中若有代码、术语等错误&#xff0c;欢迎指正 文章目录 008、事件系统-设计009、事件系统-自定义事件前言自定义事件类与使用声明与定义类代码包含头文件使用事件 事件调度器代码 C知识&#xff1a;FunctionBind用法function基本使用 012、事件系统-DemoLayer用EventDispache…

7-3 sdut-oop-6 计算各种图形的周长(多态)

定义接口或类 Shape&#xff0c;定义求周长的方法length()。 定义如下类&#xff0c;实现接口Shape或父类Shape的方法。 &#xff08;1&#xff09;三角形类Triangle &#xff08;2&#xff09;长方形类Rectangle &#xff08;3&#xff09;圆形类Circle等。 定义测试类Shap…

QT基础教程之一创建Qt项目

QT基础教程1创建Qt项目 根据模板创建 打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项 弹出New Project对话框&#xff0c;选择Qt Widgets Application 选择【Choose】按钮&#xff0c;弹出如下对话框 设置项目名称和路径&#xff0c;…