Spring Boot 集成 RabbitMQ

依赖与配置

在 pom.xml 中引入 RabbitMQ 相关依赖

<!-- AMQP 依赖, RabbitMq -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId><version>3.2.7</version>
</dependency>

在 application.yml 中添加配置

spring:rabbitmq:host: 127.0.0.1port: 5672username: guestpassword: guest

RabbitMQ使用

扇形交换机

扇形交换机,顾名思义,就是像一把扇子一样,一个交换器可以绑定多个队列,只要交换机接收到消息就会发送给所有和它绑定的队列。

假设扇形交换机 fanoutExchange 绑定了队列 fanoutQueue1 和 fanoutQueue2 ,那么我们往 fanoutExchange 发送一条消息,fanoutQueue1 和 fanoutQueue2 都会收到一条相同的消息,如果消息未被消费我们可以在 RabbitMQ 管理端看到这两个队列和队列内积压的一条相同的消息。

@Configuration
public class FanoutExchangeConfig {public static final String FANOUT_QUEUE1 = "fanout.queue1";public static final String FANOUT_QUEUE2 = "fanout.queue2";public static final String FANOUT_EXCHANGE = "fanout.exchange";//声明队列Q1@Bean("fanoutQ1")public Queue fanoutQ1() {return new Queue(FANOUT_QUEUE1);}//声明队列Q1@Bean("fanoutQ2")public Queue fanoutQ2() {return new Queue(FANOUT_QUEUE2);}//声明扇形交换机@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange(FANOUT_EXCHANGE);}//队列Q1绑定扇形交换机@Bean("bindFanoutQ1")public Binding bindFanoutQ1() {return BindingBuilder.bind(fanoutQ1()).to(fanoutExchange());}//队列Q2绑定扇形交换机@Bean("bindFanoutQ2")public Binding bindFanoutQ2() {return BindingBuilder.bind(fanoutQ2()).to(fanoutExchange());}}

编写 junit 测试, 发送消息

@Test
public void fanoutTest() {rabbitTemplate.convertAndSend(FanoutExchangeConfig.FANOUT_EXCHANGE, "", "fantoutTest");
}

发送消息后由于没有消费者,可以在管理端看到积压在队列中的消息。

在这里插入图片描述

在这里插入图片描述

直连交换机

一个直连交换机可以有多个队列,但每个队列都有一个路由一一匹配,交换机根据路由将消息投递到对应队列中。当同一个队列有多个消费者时,消息不会被重复消费,直连交换机能够轮询公平的将消息分发给每个消费者。

假设直连交换机 directExchange 与队列 directQueue1 通过路由 directRoute1 绑定, 与directQueue2 通过路由 directRoute2 绑定。当生产者发送路由为 directRoute1 的消息给 directExchange 时,消息会被投递到 directQueue1 ,directQueue2 则接收不到消息。

@Configuration
public class DirectExchangeConfig {public static final String DIRECT_QUEUE1 = "direct.queue1";public static final String DIRECT_QUEUE2 = "direct.queue2";public static final String DIRECT_EXCHANGE = "direct.exchange";public static final String DIRECT_ROUTE_KEY1 = "direct.route.key1";//声明队列Q1@Bean("directQ1")public Queue directQ1() {return new Queue(DIRECT_QUEUE1);}//声明队列Q1@Bean("directQ2")public Queue directQ2() {return new Queue(DIRECT_QUEUE2);}//声明直连交换机@Beanpublic DirectExchange directExchange() {return new DirectExchange(DIRECT_EXCHANGE);}//队列Q1绑定直连交换机@Bean("bindDirectQ1")public Binding bindDirectQ1() {return BindingBuilder.bind(directQ1()).to(directExchange()).with(DIRECT_ROUTE_KEY1);}//队列Q2绑定直连交换机@Bean("bindDirectQ2")public Binding bindDirectQ2() {return BindingBuilder.bind(directQ2()).to(directExchange()).with("");}}

编写 junit 测试,投递消息给交换机。

@Test
public void directTest() {//消息被投递给 bindDirectQ2rabbitTemplate.convertAndSend(DirectExchangeConfig.DIRECT_EXCHANGE, "", "directTest");//消息被投递给 bindDirectQ1rabbitTemplate.convertAndSend(DirectExchangeConfig.DIRECT_EXCHANGE, DIRECT_ROUTE_KEY1, "directTest-key1");//没有匹配的路由,bindDirectQ1 和 bindDirectQ2 都无法接收rabbitTemplate.convertAndSend(DirectExchangeConfig.DIRECT_EXCHANGE, "test", "directTest-test");
}

主题交换机

一个主题交换机可以有多个绑定队列,支持路由模糊匹配,可以使用星号(*)和井号(#)作为通配符进行匹配。其中,* 可以代替一个单词,# 可以代替任意个单词。

假设主题交换机 topicExchange 通过路由 topic.route.* 绑定队列 topicQueue1 , 通过路由 topic.route.# 绑定队列 topicQueue2。当生产者通过路由 topic.route.1 和 topic.route.1.1 投递消息给 topicExchange 时, topicQueue2 会接收到两条不同路由的消息, 而 topicQueue1 仅能接收到路由为 topic.route.1 的消息。

@Configuration
public class TopicExchangeConfig {public static final String TOPIC_QUEUE1 = "topic.queue1";public static final String TOPIC_QUEUE2 = "topic.queue2";public static final String TOPIC_EXCHANGE = "topic.exchange";public static final String TOPIC_ROUTE_KEY1 = "topic.route.*";public static final String TOPIC_ROUTE_KEY2 = "topic.route.#";//声明队列Q1@Bean("topicQ1")public Queue topicQ1() {return new Queue(TOPIC_QUEUE1);}//声明队列Q1@Bean("topicQ2")public Queue topicQ2() {return new Queue(TOPIC_QUEUE2);}//声明主题交换机@Beanpublic TopicExchange topicExchange() {return new TopicExchange(TOPIC_EXCHANGE);}//队列Q1绑定主题交换机@Bean("bindTopicQ1")public Binding bindTopicQ1() {return BindingBuilder.bind(topicQ1()).to(topicExchange()).with(TOPIC_ROUTE_KEY1);}//队列Q2绑定主题交换机@Bean("bindTopicQ2")public Binding bindTopicQ2() {return BindingBuilder.bind(topicQ2()).to(topicExchange()).with(TOPIC_ROUTE_KEY2);}}

编写 junit 测试,投递两条不同路由的消息给主题交换机

@Test
public void topicTest(){//消息被投递给 topicQ1 和 topicQ2rabbitTemplate.convertAndSend(TopicExchangeConfig.TOPIC_EXCHANGE, "topic.route.1", "topicTest-*");//消息仅投递给 topicQ2rabbitTemplate.convertAndSend(TopicExchangeConfig.TOPIC_EXCHANGE, "topic.route.1.1", "topicTest-#");
}

可以从管理端看到 topicQueue2 由于绑定的是通配符#,故此两条消息都有被投递到队列中,topicQueue1 由于绑定的是通配符* 只匹配到一条消息,故此只有一条消息被投递到队列中。

在这里插入图片描述

首部交换机

首部交换机通过设置消息的头部信息来进行绑定队列的分发,它不依赖于路由键的匹配规则来分发消息,而是根据发送的消息内容中的headers属性进行匹配。当消息投递到首部交换器时,RabbitMQ会获取到该消息的headers(一个键值对的形式),并且对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对。如果完全匹配,则消息会路由到该队列,否则不会路由到该队列。

@Configuration
public class HeadExchangeConfig {public static final String HEADER_QUEUE1 = "header.queue1";public static final String HEADER_QUEUE2 = "header.queue2";public static final String HEADER_QUEUE3 = "header.queue3";public static final String HEADER_EXCHANGE = "header.exchange";public static final String HEADER_KEY1 = "headerKey1";public static final String HEADER_KEY2 = "headerKey2";//声明queue@Bean("headerQueue1")public Queue headerQueue1() {return new Queue(HEADER_QUEUE1);}@Bean("headerQueue2")public Queue headerQueue2() {return new Queue(HEADER_QUEUE2);}@Bean("headerQueue3")public Queue headerQueue3() {return new Queue(HEADER_QUEUE3);}//声明首部交换机@Beanpublic HeadersExchange headerExchange() {return new HeadersExchange(HEADER_EXCHANGE);}//声明Binding,绑定Header(消息头部参数)中 HEADER_KEY1 = a的队列。header的队列匹配可以用mathces和exisits@Beanpublic Binding bindHeaderQueue1() {return BindingBuilder.bind(headerQueue1()).to(headerExchange()).where(HEADER_KEY1).matches("a");}//绑定Header中 HEADER_KEY2 =1的队列。@Beanpublic Binding bindHeaderBusTyp1() {return BindingBuilder.bind(headerQueue2()).to(headerExchange()).where(HEADER_KEY2).matches("b");}//绑定Header中 HEADER_KEY1 = a 或者 HEADER_KEY2 = b 的队列。@Beanpublic Binding bindHeaderTxBusTyp1() {Map<String, Object> condMap = new HashMap<>();condMap.put(HEADER_KEY1, "a");condMap.put(HEADER_KEY2, "b");return BindingBuilder.bind(headerQueue3()).to(headerExchange()).whereAny(condMap).match();}}

编写 junit 测试, 往首部交换机投递信息

@Test
public void headerTest(){MessageProperties properties = new MessageProperties();properties.setHeader(HeadExchangeConfig.HEADER_KEY1, "a");//消息被投递到 headerQueue1 和 headerQueue3rabbitTemplate.convertAndSend(HeadExchangeConfig.HEADER_EXCHANGE, "", new Message("headerTest-a".getBytes(), properties));properties.setHeader(HeadExchangeConfig.HEADER_KEY1, "");properties.setHeader(HeadExchangeConfig.HEADER_KEY2, "b");//消息被投递到 headerQueue2 和 headerQueue3rabbitTemplate.convertAndSend(HeadExchangeConfig.HEADER_EXCHANGE, "", new Message("headerTest-b".getBytes(), properties));}

备份交换机

通过设置交换机的alternate-exchange的参数设置备份交换机,当消息路由无法在当前交换机匹配到合适的队列投递时,将消息转到备份交换机,分发到其绑定的备份队列中。

@Configuration
public class BackupExchangeConfig {public static final String BACKUP_QUEUE = "backup.queue";public static final String BACKUP_EXCHANGE = "backup.exchange";public static final String BACKUP_ROUTE_KEY = "backup.key";public static final String NON_BACKUP_QUEUE = "nonbackup.queue";public static final String NON_BACKUP_EXCHANGE = "nonbackup.exchange";public static final String NON_BACKUP_ROUTE_KEY = "nonbackup.key";@Bean("backupQueue")public Queue backupQueue(){return new Queue(BACKUP_QUEUE, true, false, false);}@Bean("nonBackupQueue")public Queue nonBackupQueue(){return new Queue(NON_BACKUP_QUEUE, true, false, false);}@Bean("nonBackupExchange")public DirectExchange nonBackupExchange(){Map<String, Object> args = new HashMap<>(2);args.put("alternate-exchange", BACKUP_EXCHANGE);return new DirectExchange(NON_BACKUP_EXCHANGE,true,false, args);}@Bean("backupExchange")public FanoutExchange backupExchange(){return new FanoutExchange(BACKUP_EXCHANGE,true,false);}@Bean("bindNonBackupQueue")public Binding bindNonBackupQueue(){return BindingBuilder.bind(nonBackupQueue()).to(nonBackupExchange()).with(NON_BACKUP_ROUTE_KEY);}@Bean("bindBackupQueue")public Binding bindBackupQueue(){return BindingBuilder.bind(backupQueue()).to(backupExchange());}}

编写测试用例, 投递消息给备份交换机

@Test
public void backupTest() {//路由正确匹配,消息投递到非备份队列中rabbitTemplate.convertAndSend(BackupExchangeConfig.NON_BACKUP_EXCHANGE, BackupExchangeConfig.NON_BACKUP_ROUTE_KEY, "nonBackupTest");//路由无法匹配,消息投递到备份队列中rabbitTemplate.convertAndSend(BackupExchangeConfig.NON_BACKUP_EXCHANGE, BackupExchangeConfig.NON_BACKUP_ROUTE_KEY + "123", "backupTest");
}

可以看到备份队列和非备份队列中各有一条消息。

在这里插入图片描述

死信交换机

死信交换机其实可以理解成一个拥有特殊意义的直连交换机,正常队列通过设置队列中的x-dead-letter-exchangex-dead-letter-routing-key 参数来设置绑定死信交换机,当消费者拒绝消费、消息积压队列达到最大长度或者消息过期时,消息从正常队列转到死信队列。

死信在转移到死信队列时,它的路由也会保存下来。但是如果配置了x-dead-letter-routing-key参数的话,路由就会被替换为配置的这个值。另外,死信在转移到死信队列的过程中,是没有经过消息发送者确认的,所以并不能保证消息的安全性。

消息被作为死信转移到死信队列后,会在Header当中增加一些消息。比如时间、原因(rejected,expired,maxlen)、队列等。另外header中还会加上第一次成为死信的三个属性(x-first-death-reason x-first-death-queuex-first-death-exchange),并且这三个属性在以后的传递过程中都不会更改。

死信队列也可以向其它队列一样被消费者正常订阅消费。

@Configuration
public class DeadLetterExchangeConfig {public static final String DEAD_QUEUE = "dead.queue";public static final String DEAD_EXCHANGE = "dead.exchange";public static final String DEAD_ROUTE_KEY = "dead.key";public static final String NON_DEAD_QUEUE = "nondead.queue";public static final String NON_DEAD_EXCHANGE = "nondead.exchange";public static final String NON_DEAD_ROUTE_KEY = "nondead.key";@Bean("deadQueue")public Queue deadQueue(){return new Queue(DEAD_QUEUE, true, false, false);}@Bean("nonDeadQueue")public Queue nonDeadQueue(){Map<String, Object> args = new HashMap<>(2);args.put("x-message-ttl",10000);args.put("x-dead-letter-exchange",DEAD_EXCHANGE);args.put("x-dead-letter-routing-key",DEAD_ROUTE_KEY);return new Queue(NON_DEAD_QUEUE, true, false, false, args);}@Bean("deadExchange")public DirectExchange deadExchange(){return new DirectExchange(DEAD_EXCHANGE,false,false);}@Bean("nonDeadExchange")public DirectExchange nonDeadExchange(){return new DirectExchange(NON_DEAD_EXCHANGE,true,false);}@Bean("bindDeadQueue")public Binding bindDeadQueue(){return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(DEAD_ROUTE_KEY);}@Bean("bindNonDeadQueue")public Binding bindNonDeadQueue(){return BindingBuilder.bind(nonDeadQueue()).to(nonDeadExchange()).with(NON_DEAD_ROUTE_KEY);}}

编写 junit 测试, 投递一条10秒过期的消息,刚投递时消息存在于正常队列,10秒过期后转到死信队列。 投递消息和队列同时设置过期时间时,以时间更短的为准。

@Test
public void deadTest() {rabbitTemplate.convertAndSend(DeadLetterExchangeConfig.NON_DEAD_EXCHANGE, DeadLetterExchangeConfig.NON_DEAD_ROUTE_KEY, "deadTest 1");
}

延时队列

通过TTL+死信队列实现延时队列,与上述死信交换机使用大同小异,核心就是创建队列的时候设置如下三个参数:

  • x-message-ttl (必要) :当前队列消息多久未消费进入死信队列
  • x-dead-letter-exchange (必要):消息过期后进入的死信队列交换机
  • x-dead-letter-routing-key (非必要):消息的路由, 未设置时保留原队列的路由

TTL 消息可以通过以下方式创建

方式一:在队列中设置 x-message-ttl 参数

@Bean("nonDeadQueue")
public Queue nonDeadQueue(){Map<String, Object> args = new HashMap<>(2);args.put("x-message-ttl",10000);args.put("x-dead-letter-exchange",DEAD_EXCHANGE);args.put("x-dead-letter-routing-key",DEAD_ROUTE_KEY);return new Queue(NON_DEAD_QUEUE, true, false, false, args);
}

方式二: 在投递消息时设置消息的过期时间

MessageProperties properties = new MessageProperties();
properties.setExpiration("10000");
rabbitTemplate.convertAndSend(DeadLetterExchangeConfig.NON_DEAD_EXCHANGE, DeadLetterExchangeConfig.NON_DEAD_ROUTE_KEY, new Message("ttl test".getBytes(), properties));

两种方式同时都有设置时,时间短的设置生效。

动态创建队列、交换机及绑定关系

Spring Boot 封装了一些类用于对 RabbitMQ 的管理

  • AmqpAdmin
    用于管理队列、交换机及绑定关系 。

  • RabbitTemplate
    对消息操作的一些封装。

@Autowired
private AmqpAdmin amqpAdmin;public void createComponents(){String queueName = "amqp.queue";String exchangeName = "amqp.exchange";//声明(创建)队列Queue queue = new Queue(queueName, false, false, false, null);amqpAdmin.declareQueue(queue);//声明交换机FanoutExchange fanoutExchange = new FanoutExchange(exchangeName, false, false, null);amqpAdmin.declareExchange(fanoutExchange);//声明绑定amqpAdmin.declareBinding(new Binding(queueName, Binding.DestinationType.QUEUE, exchangeName, "", null));
}

消息监听

启动类添加@EnableRabbit注解启用RabbitMQ ,通过注解 @RabbitListener@RabbitHandler 进行消息的消费

@Component
@RabbitListener(queues = "fanout.queue1")
public class FanoutQueueRecever {@RabbitHandlerpublic void handle(String msg){System.out.println("收到来自 fanout.queue1 的消息 :" + msg);}
}

消息确认

保证发送方消息不丢失

开启生产端确认, 消息发送成功后回调,获得预期结果后才认为消息发送成功。

  • 交换机收到消息进行回调,ConfirmCallback

    spring.rabbitmq.publisher-confirm-type: correlated (高版本Spring使用)
    spring.rabbitmq.publisher-confirms: true(低版本Spring使用)

  • 消息正确抵达队列进行回调,ReturnsCallback

    spring.rabbitmq.publisher-returns: true
    spring.rabbitmq.template.mandatory: true, 只要抵达队列,以异步形式优先发送回调 ReturnCallback

保证消费者消息不丢失

开启消费端确认(保证每个消息都被正确消费,此时才可以删除这个消息)

  • 手动ack消息
    spring.rabbitmq.listener.simple.acknowledge-mode: manual

    • AcknowledgeMode.NONE:自动确认
      自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。
      如果消息已经被处理,但后续出现异常导致事务回滚,也同样造成了实际意义的消息丢失。

    • AcknowledgeMode.AUTO:根据情况确认

    • AcknowledgeMode.MANUAL:手动确认
      如果手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者。
      如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限,不足以处理后续投递的消息。

  • ACK的几种方法

    • channel.basicNack(deliveryTag, multiple, requeue); 拒绝消费。
      deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel。

      multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息。

      requeue: 是否重新入队

    • channel.basicAck(deliveryTag, multiple); 确认消费,参数解释同上。

    • channel.basicReject(deliveryTag, requeue); 拒绝消费,不支持批量操作,用法与basicNack()类似。

代码实现

yaml 文件配置

spring:rabbitmq:host: 127.0.0.1port: 5672username: guestpassword: guest# 开启发送端确认#publisher-confirms: truepublisher-confirm-type: correlated#开启发送端消息抵达确认publisher-returns: true#只要抵达队列。以异步发送优先回调returnconfirmtemplate:mandatory: true# 手动ack消息listener:simple:acknowledge-mode: manual

配置RabbitMQ, 设置发送者消息确认逻辑

@Configuration
public class RabbitMqConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback{@AutowiredRabbitTemplate rabbitTemplate;@PostConstructpublic void init(){rabbitTemplate.setReturnsCallback(this);rabbitTemplate.setConfirmCallback(this);}/*** 发送消息触发confirmCallback回调, 无论是否到达队列,只要有到达交换机都会触发这个回调* @param correlationData:当前消息的唯一关联数据(如果发送消息时未指定此值,则回调时返回null)* @param ack:消息是否成功收到(ack=true,消息抵达Broker)* @param cause:失败的原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {System.out.println("发送消息触发confirmCallback回调");System.out.println(String.format("correlationData:%s\nack:%s\ncause:%s", correlationData, ack, cause));}/*** 消息未到达队列触发returnCallback回调,只要消息没有投递给指定的队列,就触发这个失败回调* @param returnedMessage 返回的消息,包含*                        message:投递失败的消息详细信息*                        replyCode:回复的状态码*                        replyText:回复的文本内容*                        exchange:接收消息的交换机*                        routingKey:接收消息的路由键*/@Overridepublic void returnedMessage(ReturnedMessage returnedMessage) {System.out.println("消息未到达队列触发returnCallback回调");System.out.println(returnedMessage.toString());}
}

实现消费者确认

@Component
@RabbitListener(queues = "fanout.queue1")
public class FanoutQueueRecever {@RabbitHandlerpublic void handle(String msg, Channel channel, @Headers Map<String, Object> headers){System.out.println("收到来自 fanout.queue1 的消息 :" + msg);System.out.println("tag:" + headers.get(AmqpHeaders.DELIVERY_TAG));try {if("ack".equalsIgnoreCase(msg)){channel.basicAck((long)headers.get(AmqpHeaders.DELIVERY_TAG), false);}if("nack".equalsIgnoreCase(msg)){channel.basicNack((long)headers.get(AmqpHeaders.DELIVERY_TAG), false, true);}if("reject".equalsIgnoreCase(msg)){channel.basicReject((long)headers.get(AmqpHeaders.DELIVERY_TAG), false);}} catch (IOException e) {e.printStackTrace();}}/*** @Payload 注解的对象需要实现序列化* @Headers 获取所有头部信息* @Header  获取单个头部信息*/@RabbitHandler(isDefault = true)public void handleMap(@Payload MyMessage message, Channel channel, @Headers Map<String, Object> headers){System.out.println("收到来自 fanout.queue1 的消息 :" + message.toString());System.out.println("tag:" + headers.get(AmqpHeaders.DELIVERY_TAG));try {channel.basicAck((long)headers.get(AmqpHeaders.DELIVERY_TAG), false);} catch (IOException e) {e.printStackTrace();}}}

编写 junit 测试, 发送测试消息

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class MyMessage implements Serializable {private String id;private String name;}
@Test
public void confirmTest(){//未知交换机, 触发 confirmCallback 回调//rabbitTemplate.convertAndSend("unknow", "unknow", "confirmTest");//未知路由, 消息到达交换机但是无法到达队列, 触发 confirmCallback 回调和 returnCallback 回调//rabbitTemplate.convertAndSend(TopicExchangeConfig.TOPIC_EXCHANGE, "topic", "topicTest-*");//正常到达队列,触发confirmCallback回调rabbitTemplate.convertAndSend(FanoutExchangeConfig.FANOUT_EXCHANGE, "", "fantoutTest", new CorrelationData("1"));rabbitTemplate.convertAndSend(FanoutExchangeConfig.FANOUT_EXCHANGE, "", new MyMessage("1", "张三"), new CorrelationData("2"));}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ElementUI el-select 组件动态设置disabled后,高度变更的问题解决办法

问题描述 Vue2 项目在使用 el-select 组件时&#xff0c;动态将disabled变更为了 true&#xff0c;元素的高度发生了变化。 问题原因 通过浏览器开发人员工具面板&#xff0c;发现&#xff0c;组件内的 input 元素被动态设置了height的样式&#xff1a; 在项目中检查后并…

微软Office PLUS办公插件下载安装指南

微软OfficePLUS插件下载安装指南 简介&#xff1a; OfficePLUS微软官方出品的Office插件 &#xff0c;OfficePLUS拥有30万高质量模板素材&#xff0c;能帮助Word、Excel、Powerpoint、PDF等多种办公软件提升效率&#xff0c;具有智能化、模板质量高、运行快、稳定性强等优点。…

prompt第二讲-langchain实现中英翻译助手

文章目录 prompt模板 (prompt template)langchain 中的prompt模板 (prompt template)langchain实现中英翻译助手 prompt模板 (prompt template) 开篇我介绍了在llm中&#xff0c;通常输入的那个字符串会被我们称之为prompt&#xff0c;下面就是一个中英文翻译助手的prompt例子…

探索智能合约在金融科技中的前沿应用与挑战

随着区块链技术的发展和普及&#xff0c;智能合约作为其核心应用之一&#xff0c;在金融科技&#xff08;FinTech&#xff09;领域中展现出了巨大的潜力和挑战。本文将深入探讨智能合约的基本概念、前沿应用案例&#xff0c;以及面临的技术挑战和发展趋势&#xff0c;旨在帮助读…

解决QT creator中文乱码问题

1.首先设置文本编辑器为UTF-8 先在工具-选项-文本编辑器-behavior部分选择文件编码为UTF-8&#xff0c;紧接着是选择“如果编码是UTF-8则添加”&#xff0c;如下图 2.设置ext code for tools 为system 具体解决办法是 工具-选项-环境-interfaces这一栏有一个“Text code for to…

基于R语言的水文、水环境模型优化技术及快速率定方法与多模型案例

在水利、环境、生态、机械以及航天等领域中&#xff0c;数学模型已经成为一种常用的技术手段。同时&#xff0c;为了提高模型的性能&#xff0c;减小模型误用带来的风险&#xff1b;模型的优化技术也被广泛用于模型的使用过程。模型参数的快速优化技术不但涉及到优化本身而且涉…

MySQL篇:事务

1.四大特性 首先&#xff0c;事务的四大特性&#xff1a;ACID&#xff08;原子性&#xff0c;一致性&#xff0c;隔离性&#xff0c;持久性&#xff09; 在InnoDB引擎中&#xff0c;是怎么来保证这四个特性的呢&#xff1f; 持久性是通过 redo log &#xff08;重做日志&…

Andriod Stdio新建Kotlin的Jetpack Compose简单项目

1.选择 No Activity 2.选择kotlin 4.右键选择 在目录MyApplication下 New->Compose->Empty Project 出现下面的画面 Finish 完成

聊聊常见的分布式ID解决方案

highlight: xcode theme: vuepress 为什么要使用分布式ID&#xff1f; 随着 Web 开发技术的不断发展&#xff0c;单体的系统逐步走向分布式系统。在分布式系统中&#xff0c;使用分布式 ID(Distributed IDs)主要是为了在没有单点故障的情况下生成唯一标识符。这些唯一标识符在很…

【Linux】进程间通信——命名管道和共享内存

目录 命名管道&#xff08;named pipe&#xff09; 命令行中使用 代码中使用 共享内存&#xff08;shared memory&#xff09; shmget ipcs命令 shmctl shmat/shmdt 简单通信 命名管道&#xff08;named pipe&#xff09; 之前我们说了匿名管道&#xff0c;但是匿名管道…

为什么要使用加密软件?

一、保护数据安全&#xff1a;加密软件通过复杂的加密算法对敏感数据进行加密处理&#xff0c;使得未经授权的人员即使获取了加密数据&#xff0c;也无法轻易解密和获取其中的内容。这极大地提高了数据在存储、传输和使用过程中的安全性。 二、遵守法律法规&#xff1a;在许多国…

无人机图像目标检测

本仓库是人工智能课程的课程作业仓库&#xff0c;主要是完成无人机图像目标检测的任务&#xff0c;我们对visdrone数据集进行了处理&#xff0c;在yolo和ssd两种框架下进行了训练和测试&#xff0c;并编写demo用于实时的无人机图像目标检测。 requirements依赖&#xff1a; ss…

ubuntu虚拟机安装ssh时报错 正在等待缓存锁

问题&#xff1a; 连接vm ubuntu虚拟机安装ssh时报错 正在等待缓存锁。 sudo apt install openssh-server 处理办法 sudo rm /var/lib/dpkg/lock-frontend sudo rm /var/cache/apt/archives/lock sudo rm /var/lib/dpkg/lock

K8S ingress 初体验 - ingress-ngnix 的安装与使用

准备环境 先把 google 的vm 跑起来… gatemanMoreFine-S500:~/projects/coding/k8s-s/service-case/cloud-user$ kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane,master 124d v1.23.6 k8s-no…

python数据可视化(5)——绘制饼图

课程学习来源&#xff1a;b站up&#xff1a;【蚂蚁学python】 【课程链接&#xff1a;【【数据可视化】Python数据图表可视化入门到实战】】 【课程资料链接&#xff1a;【链接】】 Python绘制饼图分析北京天气 饼图&#xff0c;是一个划分为几个扇形的圆形统计图表&#xff…

爬虫-requests和Selenium

1、了解requests的功能 1.1 使用post和get发送请求 HTTP中常见发送网络请求的方式有两种&#xff0c;GET和POST。GET是从指定的资源请求数据&#xff0c;POST是向指定的资源提交要被处理的数据。 GET的用法&#xff1a; import requestsr requests.get("https://www.…

Milvus 核心设计(5)--- scalar indexwork mechanism

目录 背景 Scalar index 简介 属性过滤 扫描数据段 相似性搜索 返回结果 举例说明 1. 属性过滤 2. 扫描数据段 3. 相似性搜索 实际应用中的考虑 Scalar Index 方式 Auto indexing Inverted indexing 背景 继续Milvus的很细设计&#xff0c;前面主要阐述了Milvu…

Swift网络爬虫与数据可视化的结合 (1)

前言 在当今数字化时代&#xff0c;数据的重要性不言而喻。Swift&#xff0c;作为一种现代的编程语言&#xff0c;以其高性能、易用性和安全性&#xff0c;成为了开发iOS和macOS应用的首选。本文将探讨如何使用Swift来开发一个网络爬虫&#xff0c;以及如何将爬取的数据进行可…

图像边缘检测中Sobel算子的原理,并附OpenCV和Matlab的示例代码

Sobel算子是一种用于图像边缘检测的离散微分算子。它结合了图像的平滑处理和微分计算&#xff0c;旨在强调图像中强度变化显著的区域&#xff0c;即边缘。Sobel算子在图像处理中被广泛使用&#xff0c;特别是在计算机视觉和图像分析领域。 Sobel算子的原理 Sobel算子主要用于计…

【数学建模与优化】:解析与实践

目录 数学建模概述 1. 什么是数学模型 2. 数学模型的分类 2.1 按应用领域分类 2.2 按建模方法分类 2.3 按是否考虑随机因素分类 2.4 按变量的连续性分类 2.5 按对对象内部规律了解程度分类 2.6 按变量的基本关系分类 2.7 按是否考虑时间变化分类 3. 数学规划及优化模…