使用java代码操作rabbitMQ收发消息
SpringAMQP
将来我们开发业务功能的时候,肯定不会在控制台收发消息,而是应该基于编程的方式。由于RabbitMQ采用了AMQP协议,因此它具备跨语言的特性。任何语言只要遵循AMQP协议收发消息,都可以与RabbitMQ交互。并且RabbitMQ官方也提供了各种不同语言的客户端。
但是,RabbitMQ官方提供的Java客户端编码相对复杂,一般生产环境下我们更多会结合Spring来使用。而Spring的官方刚好基于RabbitMQ提供了这样一套消息收发的模板工具:SpringAMQP。并且还基于SpringBoot对其实现了自动装配,使用起来非常方便。
SpringAmqp的官方地址:
Spring AMQP
SpringAMQP提供了三个功能:
- 自动声明队列、交换机及其绑定关系
- 基于注解的监听器模式,异步接收消息
- 封装了RabbitTemplate工具,用于发送消息
快速入门
别忘了在我们的项目中,引入spring amqp的依赖。
<dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--AMQP依赖,包含RabbitMQ--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!--单元测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency>
</dependencies>
在之前的案例中,我们都是经过交换机发送消息到队列,不过有时候为了测试方便,我们也可以直接向队列发送消息,跳过交换机。
在入门案例中,我们就演示这样的简单模型,如图:
也就是
- publisher直接发送消息到队列
- 消费者监听并处理队列中的消息
注意:这种模式一般测试使用,很少在生产中使用。
为了方便测试,我们在rabbitMQ控制台,创建名为 simple.queue 的队列。
添加队列后查看
接下来,我们就可以利用Java代码收发消息了。
消息发送
在我们的项目application.yml 中 添加关于rabbitmq的配置信息。
spring:rabbitmq:host: 123.56.247.70 # 你的虚拟机IPport: 5672 # rabbitMQ端口virtual-host: /sde # 虚拟机名称username: sundaoen # 用户名password: 8888888888 # 密码
编写测试类
在我们项目的publisher中创建测试类,并且利用 RabbitTemplate 发送消息。
@SpringBootTest
public class TestSendMessage {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSimpleQueue(){// 1 队列名称String queueName = "simple.queue";// 2 消息String message = "hello simple.queue";// 3 发送消息rabbitTemplate.convertAndSend(queueName,message);}
}
打开控制台,可以看到消息已经发送到队列中:
看看消息内容
接下来,我们再来实现消息接收。
消息接收
同样的道理,也是先配置MQ地址。在application.yml 中
spring:rabbitmq:host: 123.56.247.70 # 你的虚拟机IPport: 5672 # rabbitMQ端口virtual-host: /sde # 虚拟机名称username: sundaoen # 用户名password: 8888888888 # 密码
在consumer服务中编写监听器类,并利用@RabbitListener实现消息的接收。
@Slf4j
@Component
public class SimpleQueueListener {/*利用@RabbitListener注解,可以监听到对应队列的消息一旦监听的队列有消息,就会回调当前方法,在方法中接收消息并消费处理消息*/@RabbitListener(queues = "simple.queue")public void listenerSimpleQueue(String msg){System.out.println("SpringRabbitListener 监听到 simple.queue 队列中的消息是:" + msg);}
}
WorkQueue模型
Work queues,任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。
当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。
此时就可以使用work 模型,多个消费者共同处理消息处理,消息处理的速度就能大大提高了。
接下来,我们就来模拟这样的场景。
首先,我们在控制台创建一个新的队列,命名为work.queue:
添加后的效果
消息发送
这次我们循环发送,模拟大量消息堆积现象。
在publisher服务中的WorkQueueSendTest类中添加一个测试方法:
@SpringBootTest
public class WorkQueueSendTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendWorkQueue() throws InterruptedException {// 1 队列名称String queueName = "work.queue";// 2 消息String message = "hello work.queue-";// 3 发送消息for (int i = 1; i <= 50; i++) {// 每隔20毫秒发送一条消息,相当于一秒发送50条消息。rabbitTemplate.convertAndSend(queueName,message + i);Thread.sleep(20);}}
}
可以看到在work.queue 队列中有50条消息。
消息接收
要模拟多个消费者绑定同一个队列,我们在consumer服务中的,listener包中。新增WorkQueueListener类并添加2个新的方法:
@Slf4j
@Component
public class WorkQueueListener {/*实现两个消费 work.queue的监听消费消息的方法;一个方法消费后沉睡 20毫秒;一个消息消费后沉睡200毫秒;*/@RabbitListener(queues = "work.queue")public void listenerWorkQueue1(String msg){System.out.println("消费者1接收到消息" + msg +" 时间:"+ LocalDateTime.now());try {Thread.sleep(20); // 沉睡20毫秒 1秒是1000毫秒等于1秒处理50条消息} catch (InterruptedException e) {e.printStackTrace();}}@RabbitListener(queues = "work.queue")public void listenerWorkQueue2(String msg){System.out.println("***消费者2接收到消息" + msg +" 时间:"+ LocalDateTime.now());try {Thread.sleep(200); // 沉睡200毫秒 1秒是1000毫秒等于1秒处理5条消息} catch (InterruptedException e) {e.printStackTrace();}}}
注意到这两消费者,都设置了Thead.sleep,模拟任务耗时:
- 消费者1 sleep了20毫秒,相当于每秒钟处理50个消息
- 消费者2 sleep了200毫秒,相当于每秒处理5个消息
测试看结果
消费者1接收到消息hello work.queue-1 时间:2025-02-05T14:53:00.928905400
***消费者2接收到消息hello work.queue-2 时间:2025-02-05T14:53:00.947629900
消费者1接收到消息hello work.queue-3 时间:2025-02-05T14:53:00.977764800
消费者1接收到消息hello work.queue-5 时间:2025-02-05T14:53:01.039608
消费者1接收到消息hello work.queue-7 时间:2025-02-05T14:53:01.101242200
消费者1接收到消息hello work.queue-9 时间:2025-02-05T14:53:01.160396600
***消费者2接收到消息hello work.queue-4 时间:2025-02-05T14:53:01.161396900
消费者1接收到消息hello work.queue-11 时间:2025-02-05T14:53:01.231704200
消费者1接收到消息hello work.queue-13 时间:2025-02-05T14:53:01.281879300
消费者1接收到消息hello work.queue-15 时间:2025-02-05T14:53:01.347333400
***消费者2接收到消息hello work.queue-6 时间:2025-02-05T14:53:01.376528100
消费者1接收到消息hello work.queue-17 时间:2025-02-05T14:53:01.407569700
消费者1接收到消息hello work.queue-19 时间:2025-02-05T14:53:01.464497900
消费者1接收到消息hello work.queue-21 时间:2025-02-05T14:53:01.525121200
消费者1接收到消息hello work.queue-23 时间:2025-02-05T14:53:01.587589500
***消费者2接收到消息hello work.queue-8 时间:2025-02-05T14:53:01.589591300
消费者1接收到消息hello work.queue-25 时间:2025-02-05T14:53:01.647549500
消费者1接收到消息hello work.queue-27 时间:2025-02-05T14:53:01.709757900
消费者1接收到消息hello work.queue-29 时间:2025-02-05T14:53:01.768879300
***消费者2接收到消息hello work.queue-10 时间:2025-02-05T14:53:01.801437800
消费者1接收到消息hello work.queue-31 时间:2025-02-05T14:53:01.829539900
消费者1接收到消息hello work.queue-33 时间:2025-02-05T14:53:01.895907400
消费者1接收到消息hello work.queue-35 时间:2025-02-05T14:53:01.950810
消费者1接收到消息hello work.queue-37 时间:2025-02-05T14:53:02.011575
***消费者2接收到消息hello work.queue-12 时间:2025-02-05T14:53:02.014526300
消费者1接收到消息hello work.queue-39 时间:2025-02-05T14:53:02.073814400
消费者1接收到消息hello work.queue-41 时间:2025-02-05T14:53:02.142812400
消费者1接收到消息hello work.queue-43 时间:2025-02-05T14:53:02.199522100
***消费者2接收到消息hello work.queue-14 时间:2025-02-05T14:53:02.228114600
消费者1接收到消息hello work.queue-45 时间:2025-02-05T14:53:02.255591100
消费者1接收到消息hello work.queue-47 时间:2025-02-05T14:53:02.315954800
消费者1接收到消息hello work.queue-49 时间:2025-02-05T14:53:02.377632900
***消费者2接收到消息hello work.queue-16 时间:2025-02-05T14:53:02.440855300
***消费者2接收到消息hello work.queue-18 时间:2025-02-05T14:53:02.654015100
***消费者2接收到消息hello work.queue-20 时间:2025-02-05T14:53:02.867783300
***消费者2接收到消息hello work.queue-22 时间:2025-02-05T14:53:03.080905400
***消费者2接收到消息hello work.queue-24 时间:2025-02-05T14:53:03.296731200
***消费者2接收到消息hello work.queue-26 时间:2025-02-05T14:53:03.512099400
***消费者2接收到消息hello work.queue-28 时间:2025-02-05T14:53:03.725353500
***消费者2接收到消息hello work.queue-30 时间:2025-02-05T14:53:03.939706400
***消费者2接收到消息hello work.queue-32 时间:2025-02-05T14:53:04.152588100
***消费者2接收到消息hello work.queue-34 时间:2025-02-05T14:53:04.367337200
***消费者2接收到消息hello work.queue-36 时间:2025-02-05T14:53:04.581549200
***消费者2接收到消息hello work.queue-38 时间:2025-02-05T14:53:04.793774100
***消费者2接收到消息hello work.queue-40 时间:2025-02-05T14:53:05.006103400
***消费者2接收到消息hello work.queue-42 时间:2025-02-05T14:53:05.220121400
***消费者2接收到消息hello work.queue-44 时间:2025-02-05T14:53:05.433498300
***消费者2接收到消息hello work.queue-46 时间:2025-02-05T14:53:05.645486500
***消费者2接收到消息hello work.queue-48 时间:2025-02-05T14:53:05.856447600
***消费者2接收到消息hello work.queue-50 时间:2025-02-05T14:53:06.065771700
可以看到消费者1和消费者2竟然每人消费了25条消息:
- 消费者1很快完成了自己的25条消息
- 消费者2却在缓慢的处理自己的25条消息。
也就是说消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。导致1个消费者空闲,另一个消费者忙的不可开交。没有充分利用每一个消费者的能力,最终消息处理的耗时远远超过了1秒。这样显然是有问题的。
能者多劳
更改一下我们的配置文件,就好了。更改的是consumer消费者服务 application.yml 配置文件。
spring:rabbitmq:listener:simple:prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
重启项目,再次测试看结果。
消费者1接收到消息hello work.queue-1 时间:2025-02-05T16:19:40.610672600
***消费者2接收到消息hello work.queue-2 时间:2025-02-05T16:19:40.635078900
消费者1接收到消息hello work.queue-3 时间:2025-02-05T16:19:40.668399800
消费者1接收到消息hello work.queue-4 时间:2025-02-05T16:19:40.733468200
消费者1接收到消息hello work.queue-5 时间:2025-02-05T16:19:40.789432700
消费者1接收到消息hello work.queue-6 时间:2025-02-05T16:19:40.849740
***消费者2接收到消息hello work.queue-7 时间:2025-02-05T16:19:40.865255600
消费者1接收到消息hello work.queue-8 时间:2025-02-05T16:19:40.915186600
消费者1接收到消息hello work.queue-9 时间:2025-02-05T16:19:40.975302
消费者1接收到消息hello work.queue-10 时间:2025-02-05T16:19:41.035238100
消费者1接收到消息hello work.queue-11 时间:2025-02-05T16:19:41.098149900
***消费者2接收到消息hello work.queue-12 时间:2025-02-05T16:19:41.110162300
消费者1接收到消息hello work.queue-13 时间:2025-02-05T16:19:41.158752
消费者1接收到消息hello work.queue-14 时间:2025-02-05T16:19:41.214050800
消费者1接收到消息hello work.queue-15 时间:2025-02-05T16:19:41.275456500
消费者1接收到消息hello work.queue-16 时间:2025-02-05T16:19:41.338280900
***消费者2接收到消息hello work.queue-17 时间:2025-02-05T16:19:41.354040400
消费者1接收到消息hello work.queue-18 时间:2025-02-05T16:19:41.397333900
消费者1接收到消息hello work.queue-19 时间:2025-02-05T16:19:41.459536100
消费者1接收到消息hello work.queue-20 时间:2025-02-05T16:19:41.522984800
消费者1接收到消息hello work.queue-21 时间:2025-02-05T16:19:41.589369900
***消费者2接收到消息hello work.queue-22 时间:2025-02-05T16:19:41.595472400
消费者1接收到消息hello work.queue-23 时间:2025-02-05T16:19:41.639076100
消费者1接收到消息hello work.queue-24 时间:2025-02-05T16:19:41.702762100
消费者1接收到消息hello work.queue-25 时间:2025-02-05T16:19:41.761438700
消费者1接收到消息hello work.queue-26 时间:2025-02-05T16:19:41.823348300
***消费者2接收到消息hello work.queue-27 时间:2025-02-05T16:19:41.836398700
消费者1接收到消息hello work.queue-28 时间:2025-02-05T16:19:41.894946600
消费者1接收到消息hello work.queue-29 时间:2025-02-05T16:19:41.962451900
消费者1接收到消息hello work.queue-30 时间:2025-02-05T16:19:42.020227900
***消费者2接收到消息hello work.queue-31 时间:2025-02-05T16:19:42.066749100
消费者1接收到消息hello work.queue-32 时间:2025-02-05T16:19:42.080599800
消费者1接收到消息hello work.queue-33 时间:2025-02-05T16:19:42.143280700
消费者1接收到消息hello work.queue-34 时间:2025-02-05T16:19:42.204272700
消费者1接收到消息hello work.queue-35 时间:2025-02-05T16:19:42.270407300
***消费者2接收到消息hello work.queue-36 时间:2025-02-05T16:19:42.309818400
消费者1接收到消息hello work.queue-37 时间:2025-02-05T16:19:42.332003100
消费者1接收到消息hello work.queue-38 时间:2025-02-05T16:19:42.391974600
消费者1接收到消息hello work.queue-39 时间:2025-02-05T16:19:42.454012300
消费者1接收到消息hello work.queue-40 时间:2025-02-05T16:19:42.509398500
***消费者2接收到消息hello work.queue-41 时间:2025-02-05T16:19:42.555230800
消费者1接收到消息hello work.queue-42 时间:2025-02-05T16:19:42.570220
消费者1接收到消息hello work.queue-43 时间:2025-02-05T16:19:42.629378200
消费者1接收到消息hello work.queue-44 时间:2025-02-05T16:19:42.690519600
消费者1接收到消息hello work.queue-45 时间:2025-02-05T16:19:42.756214500
***消费者2接收到消息hello work.queue-46 时间:2025-02-05T16:19:42.797371400
消费者1接收到消息hello work.queue-47 时间:2025-02-05T16:19:42.813034800
消费者1接收到消息hello work.queue-48 时间:2025-02-05T16:19:42.876228100
消费者1接收到消息hello work.queue-49 时间:2025-02-05T16:19:42.939391
消费者1接收到消息hello work.queue-50 时间:2025-02-05T16:19:42.998590500
可以发现,由于消费者1处理速度较快,所以处理了更多的消息;消费者2处理速度较慢,只处理了7条消息。而最终总的执行耗时也在1秒左右,大大提升。
正所谓能者多劳,这样充分利用了每一个消费者的处理能力,可以有效避免消息积压问题。
总结
Work模型的使用:
- 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
- 通过设置prefetch来控制消费者预取的消息数量
交换机类型
在之前的两个测试案例中,都没有交换机Exchange,生产者直接发送消息到队列。而一旦引入交换机,消息发送的模式会有很大变化:
可以看到,在订阅模型中,多了一个exchange角色,而且过程略有变化:
- Publisher:生产者,不再发送消息到队列中,而是发给交换机
- Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
- Queue:消息队列也与以前一样,接收消息、缓存消息。不过队列一定要与交换机绑定。
- Consumer:消费者,与以前一样,订阅队列,没有变化
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
交换机的类型有四种:
- Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机
- Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列
- Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符
- Headers:头匹配,基于MQ的消息头匹配,用的较少
文档中,我们讲解前面的三种交换机模式。
Fanout交换机
Fanout,英文翻译是扇出,我觉得在MQ中叫广播更合适。
在广播模式下,消息发送流程是这样的:
- 1) 可以有多个队列
- 2) 每个队列都要绑定到Exchange(交换机)
- 3) 生产者发送的消息,只能发送到交换机
- 4) 交换机把消息发送给绑定过的所有队列
- 5) 订阅队列的消费者都能拿到消息
我们的计划是这样的:
- 创建一个名为test.fanout的交换机,类型是Fanout
- 创建两个队列fanout.queue1和fanout.queue2,绑定到交换机test.fanout
声明交换机和队列
在控制台创建 fanout.queue1 和 fanout.queue2 两个队列。
然后在创建一个交换机
绑定两个队列到交换机
消息发送
在publisher服务的FanoutExchangeTest类中添加测试方法:
@SpringBootTest
public class FanoutExchangeTest {@Autowiredprivate RabbitTemplate rabbitTemplate;/*测试 fanout exchange;向 test.fanout 交换机发送消息,消息内容为 hello everyone!,会发送到所有绑定到该交换机的队列*/@Testpublic void testSendFanoutExchange(){// 1 交换机名称String exchangeName = "test.fanout";// 2 消息String msg = "hello everyone!";// 3 发送消息rabbitTemplate.convertAndSend(exchangeName,"",msg);}
}
注意:上述的 convertAndSend 方法的第2个参数:路由key 因为没有绑定,所以可以指定为空
看看rabbitMQ的控制台
消息接收
在consumer服务中添加FanoutQueueListener类,并新增两个方法,监听队列中的消息 作为消费者。
@Slf4j
@Component
public class FanoutQueueListener {/*** 监听fanout.queue1队列*/@RabbitListener(queues = "fanout.queue1")public void listenFanoutQueue1(String msg){System.out.println("【消费者1】 接收到消息:" + msg);}/*** 监听fanout.queue2队列*/@RabbitListener(queues = "fanout.queue2")public void listenFanoutQueue2(String msg){System.out.println("【消费者2】 接收到消息:" + msg);}
}
总结
交换机的作用是什么?
- 接收publisher发送的消息
- 将消息按照规则路由到与之绑定的队列
- 不能缓存消息,路由失败,消息丢失
- FanoutExchange的会将消息路由到每个绑定的队列
Direct交换机
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
在Direct模型下:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
- 消息的发送方在向 Exchange发送消息时,也必须指定消息的 RoutingKey。
- Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息。
案例需求如图:
- 声明一个名为test.direct的交换机
- 声明队列direct.queue1,绑定hmall.direct,bindingKey为blud和red
- 声明队列direct.queue2,绑定hmall.direct,bindingKey为yellow和red
- 在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2
- 在publisher中编写测试方法,向test.direct发送消息
声明队列和交换机
首先在控制台声明两个队列direct.queue1和direct.queue2,这里不再展示过程:
然后声明一个direct类型的交换机,命名为test.direct:
然后使用red和blue作为key,绑定direct.queue1到test.direct:
绑定diretc.queue2
看看最后的绑定关系
消息发送
在publish服务中,新增 DirectExchangeTest 类发送消息。
@SpringBootTest
public class DirectExchangeTest {@Autowiredprivate RabbitTemplate rabbitTemplate;/*测试 direct exchange;向 test.direct 交换机发送消息,会根据路由key发送到所有绑定到该交换机的队列*/@Testpublic void testSendDirectExchange(){// 1 交换机String exchangeName = "test.direct";// 2 消息String msg = "这是一条消息,并且路由key是red 红色。";// 3 发送消息 路由key为redrabbitTemplate.convertAndSend(exchangeName,"red",msg);//改变下消息msg = "这是一条消息,并且路由key是blue 蓝色。";rabbitTemplate.convertAndSend(exchangeName,"blue",msg);}}
看看rabbitMQ控制台,查看消息是否成功发送。
消息接收
在consumer服务中,添加 DirectQueueListener 类,并在里面编写两个方法。
@Slf4j
@Component
public class DirectQueueListener {/*** 监听direct.queue1队列*/@RabbitListener(queues = "direct.queue1")public void listenDirectQueue1(String msg){log.info("【消费者1】接收到消息:{}",msg);}/*** 监听direct.queue2队列* @param msg*/@RabbitListener(queues = "direct.queue2")public void listenDirectQueue2(String msg){log.info("【消费者2】接收到消息:{}",msg);}
}
由于 test.redirect 交换机绑定的两个队列的路由key有red;所以指定了路由key为red的消息能被两个消费者都收到。
而路由key为 blue 的队列只有direct.queue1;所以只有监听这个队列的 消费者1 能够接收到消息:
总结
描述下Direct交换机与Fanout交换机的差异?
- Fanout交换机将消息路由给每一个与之绑定的队列
- Direct交换机根据RoutingKey判断路由给哪个队列
- 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
Topic交换机
Topic类型交换机
Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。
只不过Topic类型Exchange可以让队列在绑定RoutingKey 的时候使用通配符!
RoutingKey 一般都是有一个或多个单词组成,多个单词之间以.分割,例如: item.insert
通配符规则:
- #:匹配一个或多个词
- *:匹配不多不少恰好1个词
举例:
- item.#:能够匹配item.spu.insert 或者 item.spu
- item.*:只能匹配item.spu
图示:
假如此时publisher发送的消息使用的RoutingKey共有四种:
- china.news代表有中国的新闻消息;
- china.weather 代表中国的天气消息;
- japan.news 则代表日本新闻
- japan.weather 代表日本的天气消息;
解释:
- topic.queue1:绑定的是china.# ,凡是以 china.开头的routing key 都会被匹配到,包括:
-
- china.news
- china.weather
- topic.queue2:绑定的是#.news ,凡是以 .news结尾的 routing key 都会被匹配。包括:
-
- china.news
- japan.news
接下来,我们就按照上图所示,来演示一下Topic交换机的用法。
首先,在控制台按照图示例子创建队列、交换机,并利用通配符绑定队列和交换机。此处步骤略。最终结果如下:
创建交换机和队列
创建test.topic 交换机
看看效果
给test.topic 交换机绑定两个队列
消息发送
在consumer服务中,新增 TopicExchangeTest类 发送消息。
@SpringBootTest
public class TopicExchangeTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendTopicExchange(){// 1 交换机String exchangeName = "test.topic";// 2 消息String msg = "我是TopicExchange交换机的消息,路由key是 china.news";// 3 发送路由key为 china.news 的消息rabbitTemplate.convertAndSend(exchangeName,"china.news",msg);}
}
消息接收
在consumer服务中,添加 TopicExchangeListener 类,编写两个方法监听消息。
@Slf4j
@Component
public class TopicExchangeListener {/*** 监听topic.queue1队列*/@RabbitListener(queues = "topic.queue1")public void listenTopicQueue1(String msg) {log.info("【消费者1】监听到消息:{}", msg);}/*** 监听topic.queue2队列*/@RabbitListener(queues ="topic.queue2")public void listenTopicQueue2(String msg) {log.info("【消费者2】监听到消息:{}", msg);}}
总结
描述下Direct交换机与Topic交换机的差异?
- Topic交换机接收的消息RoutingKey必须是多个单词,以 . 分割
- Topic交换机与队列绑定时的RoutingKey可以指定通配符
- #:代表0个或多个词
- *:代表1个词
代码声明交换机和队列
在之前我们都是基于RabbitMQ控制台来创建队列、交换机。但是在实际开发时,队列和交换机是程序员定义的,将来项目上线,又要交给运维去创建。那么程序员就需要把程序中运行的所有队列和交换机都写下来,交给运维。在这个过程中是很容易出现错误的。
因此推荐的做法是由程序启动时检查队列和交换机是否存在,如果不存在自动创建。
基本API
SpringAMQP提供了一个Queue类,用来创建队列:
SpringAMQP还提供了一个Exchange接口,来表示所有不同类型的交换机:
我们可以自己创建队列和交换机,不过SpringAMQP还提供了ExchangeBuilder来简化这个过程:
而在绑定队列和交换机时,则需要使用BindingBuilder来创建Binding对象:
把之前创建的队列和交换机删除
删除后的队列
删除后的交换机
Ideal控制台报错
这是因为我们的队列和交换机都删除了,里面写的 RabbitListener 还在监听队列中的消息,但是队列没有了,所以报错。
fanout示例
在consumer服务中,新建config包。并创建FanoutConfig 类 在里面编写代码,创建test.fanout 交换机和fanout.queue1 和fanout.queue2 队列。 并启动consumer服务
@Configuration
public class FanoutConfig {// 声明 Fanout 类型的交换机@Beanpublic FanoutExchange fanoutExchange(){return new FanoutExchange("test.fanout");}//声明队列,名称为 fanout.queue1@Beanpublic Queue fanoutQueue1(){return new Queue("fanout.queue1");}//绑定队列和交换机@Beanpublic Binding fanoutBinding1(FanoutExchange fanoutExchange,Queue fanoutQueue1){return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}//声明队列,名称为 fanout.queue2@Beanpublic Queue fanoutQueue2(){return new Queue("fanout.queue2");}//绑定队列和交换机@Beanpublic Binding fanoutBinding2(FanoutExchange fanoutExchange,Queue fanoutQueue2){return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);}
}
看看rbbitMQ控制台效果
看看交换机
Direct示例
在consumer 服务中的 config包中,新建 DirectConfig 类,编写代码创建交换机和队列。direct模式由于要绑定多个key,会比较麻烦一点,因为每一个key都要写一个binding方法。
@Configuration
public class DirectConfig {//声明 test.direct 交换机@Beanpublic DirectExchange directExchange(){return new DirectExchange("test.direct");}//声明 direct.queue1 队列@Beanpublic Queue directQueue1(){return new Queue("direct.queue1");}//绑定 direct.queue1 队列到 test.direct 交换机上 路由key是 red@Beanpublic Binding directBindingQueue1Red(DirectExchange directExchange,Queue directQueue1){return BindingBuilder.bind(directQueue1).to(directExchange).with("red");}//绑定 direct.queue1 队列到 test.direct 交换机上 路由key是 blue@Beanpublic Binding directBindingQueue1Blue(DirectExchange directExchange,Queue directQueue1){return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");}//声明 direct.queue2 队列@Beanpublic Queue directQueue2(){return new Queue("direct.queue2");}//绑定 direct.queue2 队列到 test.direct 交换机上 路由key是 red@Beanpublic Binding directBindingQueue2Red(DirectExchange directExchange, Queue directQueue2){return BindingBuilder.bind(directQueue2).to(directExchange).with("red");}//绑定 direct.queue2 队列到 test.direct 交换机上 路由key是 yellow@Beanpublic Binding directBindingQueue2Yellow(DirectExchange directExchange,Queue directQueue2){return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");}}
看看rabbitMQ控制台
看看交换机和绑定关系
Topic示例
在consumer 服务中的config包里面,新创建TopicConfig类,编写代码创建交换机和队列。
@Configuration
public class TopicConfig {//声明 test.topic 交换机@Beanpublic TopicExchange topicExchange(){return new TopicExchange("test.topic");}//声明 topic.queue1 队列@Beanpublic Queue topicQueue1(){return new Queue("topic.queue1");}//绑定队列和交换机 路由key是 china.#@Beanpublic Binding topicBinding1(TopicExchange topicExchange,Queue topicQueue1){return BindingBuilder.bind(topicQueue1).to(topicExchange).with("china.#");}//声明 topic.queue2 队列@Beanpublic Queue topicQueue2(){return new Queue("topic.queue2");}//绑定队列和交换机 路由key是 #.news@Beanpublic Binding topicBinding2(TopicExchange topicExchange,Queue topicQueue2){return BindingBuilder.bind(topicQueue2).to(topicExchange).with("#.news");}
}
看看控制台效果
交换机
基于注解声明
基于@Bean的方式声明队列和交换机比较麻烦,Spring还提供了基于注解方式来声明。不过是在消息监听的时候基于注解的方式来声明。
例如,我们同样声明Direct模式的交换机和队列;用注解的方式声明下。
先把之前创建的 交换机和队列删除。
删除后的效果
Fanout示例
@Configuration
public class FanoutRabbitListener {// 监听fanout.queue1 队列的消息@RabbitListener(bindings = @QueueBinding(value = @Queue("fanout.queue1"),exchange = @Exchange(value = "test.fanout",type = ExchangeTypes.FANOUT),key = ""))public void listenFanoutQueue1(String msg){System.out.println("【消费者1】 监听到消息" + msg);}// 监听fanout.queue2 队列的消息@RabbitListener(bindings = @QueueBinding(value = @Queue("fanout.queue2"),exchange = @Exchange(value = "test.fanout",type = ExchangeTypes.FANOUT),key = ""))public void listenFanoutQueue2(String msg){System.out.println("【消费者2】 监听到消息" + msg);}
}
启动consumer服务看效果
交换机和绑定关系
Direct示例
新建 DirectRabbitListener 类,并在里面编写代码进行测试。
@Configuration
public class DirectRabbitListener {// 声明 direct.queue1@RabbitListener(bindings = @QueueBinding(value = @Queue("direct.queue1"),exchange = @Exchange(value = "test.direct",type = ExchangeTypes.DIRECT),key = {"red","blue"}))public void listenDirectQueue1(String msg){System.out.println("【消费者1】 接收到消息:" + msg);}// 声明direct.queue2@RabbitListener(bindings =@QueueBinding(value = @Queue("direct.queue2"),exchange = @Exchange(value = "test.direct",type = ExchangeTypes.DIRECT),key = {"red","yellow"}))public void listenDirectQueue2(String msg){System.out.println("【消费者2】 接收到消息:" + msg);}
}
看看效果
交换机和绑定关系
Topic示例
在consumer服务中的 config包里面,创建TopicRabbitListener类。编写代码进行测试
@Configuration
public class TopicRabbitListener {//声明topic.queue1 队列@RabbitListener(bindings = @QueueBinding(value = @Queue("topic.queue1"),exchange = @Exchange(value = "test.topic",type = ExchangeTypes.TOPIC),key = {"china.#"}))public void listenTopicQueue1(String msg){System.out.println("【消费者1】接收到消息:"+msg);}//声明 topic.queue2 队列@RabbitListener(bindings = @QueueBinding(value = @Queue("topic.queue2"),exchange = @Exchange(value = "test.topic",type = ExchangeTypes.TOPIC),key = {"#.news"}))public void listenTopicQueue2(String msg){System.out.println("【消费者2】接收到消息:"+msg);}
}
看看效果
交换机和绑定关系
相关文章:
使用java代码操作rabbitMQ收发消息
SpringAMQP 将来我们开发业务功能的时候,肯定不会在控制台收发消息,而是应该基于编程的方式。由于RabbitMQ采用了AMQP协议,因此它具备跨语言的特性。任何语言只要遵循AMQP协议收发消息,都可以与RabbitMQ交互。并且RabbitMQ官方也…...
LeetCode 128: 最长连续序列
LeetCode 128: 最长连续序列 题目: 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输入:nums […...
Shapefile格式文件解析和显示
Java实现GIS SHP文件格式的解析和显示,JDK19下编译,awt图形系统显示。 SHP文件对应的属性存储在DBF格式数据库中,解析见:DBASE DBF数据库文件解析_数据库文件在线解析-CSDN博客 解析SHP文件代码: public static Shap…...
华为昇腾Altas产品查询——常用命令汇总记录
参考链接: 【2024第一期CANN训练营】Altas产品查询CANN软件包版本等信息npu-smi Atlas 中心训练服务器 6.0.0 NPU驱动和固件安装指南 06 Ascend Extension for PyTorch插件软件版本配套表 以下操作适用于查询npu设备的基本信息。 #查询所有设备的基本信息 npu-smi…...
韶音科技:消费电子行业售后服务实现数字化转型,重塑客户服务体系
韶音科技:消费电子行业售后服务实现数字化转型,重塑客户服务体系 在当今这个科技日新月异的时代,企业之间的竞争早已超越了单纯的产品质量比拼,**售后服务成为了衡量消费电子行业各品牌实力与客户满意度的关键一环。**深圳市韶音…...
【R语言】plyr包和dplyr包
一、plyr包 plyr扩展包主要是实现数据处理中的“分割-应用-组合”(split-apply-combine)策略。此策略是指将一个问题分割成更容易操作的部分,再对每一部分进行独立的操作,最后将各部分的操作结果组合起来。 plyr扩展包中的主要函…...
开发一款类似《王者荣耀》的游戏是一个复杂的系统工程,涉及多个领域的知识和技术。以下是从多个角度详细阐述如何开发的思维。
一、明确游戏定位与核心玩法 游戏类型 MOBA(Multiplayer Online Battle Arena):强调团队合作、策略性和即时战斗。确定游戏模式(如5v5、3v3等)和地图设计。 核心玩法 角色设计:英雄技能、属性、成长曲线。…...
harmonyOS生命周期详述
harmonyOS的生命周期分为app(应用)的生命周期和页面的生命周期函数两部分 应用的生命周期-app应用 在app.js中写逻辑,具体有哪些生命周期函数呢,请看下图: onCreated()、onShow()、onHide()、onDestroy()这五部分 页面及组件生命周期 着重说下onShow和onHide,分别代表是不是…...
Deepseek本地部署指南:在linux服务器部署,在mac远程web-ui访问
1. 在Linux服务器上部署DeepSeek模型 要在 Linux 上通过 Ollama 安装和使用模型,您可以按照以下步骤进行操作: 步骤 1:安装 Ollama 安装 Ollama: 使用以下命令安装 Ollama: curl -sSfL https://ollama.com/download.…...
ESP8266+STM32+阿里云保姆级教程(AT指令+MQTT)
前言:在开发过程中,几乎踩便了所有大坑小坑总结出的文章,我是把坑踩满了,帮助更过小白快速上手,如有错误之处,还麻烦各位大佬帮忙指正、 目录 一、ESP-01s介绍 1、ESP-01s管脚功能: 模组启动模…...
【蓝桥杯嵌入式】4_key:单击+长按+双击
全部代码网盘自取 链接:https://pan.baidu.com/s/1PX2NCQxnADxYBQx5CsOgPA?pwd3ii2 提取码:3ii2 1、电路图 将4个按键的引脚设置为input,并将初始状态设置为Pull-up(上拉输入) 为解决按键抖动的问题,我们…...
TCP队头阻塞问题以及QUIC解决方案
TCP队头阻塞(Head-of-Line Blocking)问题 问题描述 TCP是面向字节流的可靠传输协议,要求数据按严格顺序到达接收端。若某个数据包在传输过程中丢失、延迟或乱序,会导致以下问题: 后续数据被阻塞:接收端必须等待丢失/延迟的包重传并正确接收后,才能将后续已到达的数据交…...
idea启动报错# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffccf76e433
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc0x00007ffccf76e433, pid17288, tid6696 # # JRE version: (11.0.248) (build ) # Java VM: OpenJDK 64-Bit Server VM (11.0.248-LTS, mixed mode, sharing, tiered, compressed oops, g1 gc, windows-amd64) 不知道为什么…...
如何利用Python爬虫获取商品销量详情:应对eBay反爬策略的实战指南与代码示例
在当今数据驱动的商业环境中,获取商品销量数据对于市场分析、竞品研究和商业决策至关重要。然而,像eBay这样的大型电商平台通常会部署多种反爬虫机制来保护其数据。本文将详细介绍如何利用Python编写爬虫程序,获取eBay商品的销量详情…...
激活函数篇 03 —— ReLU、LeakyReLU、RandomizedLeakkyReLU、PReLU、ELU
本篇文章收录于专栏【机器学习】 以下是激活函数系列的相关的所有内容: 一文搞懂激活函数在神经网络中的关键作用 逻辑回归:Sigmoid函数在分类问题中的应用 整流线性单位函数(Rectified Linear Unit, ReLU),又称修正线性单元&a…...
算法基础之八大排序
文章目录 概要1. 冒泡排序(Bubble Sort)2. 选择排序(Selection Sort)3. 插入排序(Insertion Sort)4. 希尔排序(Shell Sort)5. 归并排序(Merge Sort)6. 快速排…...
通达OA /mysql/index.php 未授权访问漏洞
通达OA /mysql/index.php 未授权访问漏洞 漏洞描述 通达OA 未授权访问phpmyadmin漏洞,攻击者无需帐号密码可直接访问phpmyadmin,造成数据库泄漏。攻击者可操作数据库执行sql语句,执行恶意操作,进行一步攻击。 威胁等级: 高危 …...
每日学习 设计模式 五种不同的单例模式
狮子大佬原文 https://blog.csdn.net/weixin_40461281/article/details/135050977 第一种 饿汉式 为什么叫饿汉,指的是"饿" 也就是说对象实例在程序启动时就已经被创建好,不管你是否需要,它都会在类加载时立即实例化,也就是说 实例化是在类加载时候完成的,早早的吃…...
C++类和对象
目录 一、类的定义 1.1、类定义格式 1.2、访问限定符 1.3、类域 二、实例化 2.1、实例化概念 2.2、对象大小 三、this指针 四、类的默认成员 4.1、构造函数 4.2、析构函数 4.3、拷贝构造 4.4、赋值运算符重载 4.4.1、运算符重载 4.4.2、赋值运算符重载 4.5、日…...
AI知识库和全文检索的区别
1、AI知识库的作用 AI知识库是基于人工智能技术构建的智能系统,能够理解、推理和生成信息。它的核心作用包括: 1.1 语义理解 自然语言处理(NLP):AI知识库能够理解用户查询的语义,而不仅仅是关键词匹配。 …...
docker常用命令及案例
以下是 Docker 的所有常用命令及其案例说明,按功能分类整理: 1. 镜像管理 1.1 拉取镜像 命令: docker pull <镜像名>:<标签>案例: 拉取官方的 nginx 镜像docker pull nginx:latest1.2 列出本地镜像 命令: docker images案例: 查看本地所有…...
webpack【初体验】使用 webpack 打包一个程序
打包前 共 3 个文件 dist\index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Webpack 示例&…...
让office集成deepseek,支持office和WPS办公软件!(体验感受)
导读 AIGC:AIGC是一种新的人工智能技术,它的全称是Artificial Intelligence Generative Content,即人工智能生成内容。 它是一种基于机器学习和自然语言处理的技术,能够自动产生文本、图像、音频等多种类型的内容。这些内容可以是新闻文章、…...
初始数据结构☞复杂度与泛式
一.时间复杂度 定义: 算法的时间复杂度是一个数学函数,算法中的基本操作的执行次数,为算法的时间复杂度。 O渐进表示方法: 原因: 计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而…...
理解UML中的四种关系:依赖、关联、泛化和实现
在软件工程中,统一建模语言(UML)是一种广泛使用的工具,用于可视化、设计、构造和文档化软件系统。UML提供了多种图表类型,如类图、用例图、序列图等,帮助开发者和设计师更好地理解系统的结构和行为。在UML中…...
go语言中的反射
为什么会引入反射 有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射。 空接口可…...
【前端】打造自己的hexo博客_hexo一本通
今日更新完毕,建议关注收藏点赞! 目录 打造自己的hexo blog挂载到自己的github主页设计自己的theme 打造自己的hexo blog #需要安装git node.js 这里略#安装hexo npm install -g hexo-cli npm install hexo hexo help#<folder>必须是空的 hexo in…...
剪辑学习整理
文章目录 1. 剪辑介绍 1. 剪辑介绍 剪辑可以干什么?剪辑分为哪些种类? https://www.bilibili.com/video/BV15r421p7aF/?spm_id_from333.337.search-card.all.click&vd_source5534adbd427e3b01c725714cd93961af 学完剪辑之后如何找工作or兼职&#…...
储能系统-系统架构
已更新系列文章包括104、61850、modbus 、单片机等,欢迎关注 IEC61850实现方案和测试-1-CSDN博客 快速了解104协议-CSDN博客 104调试工具2_104协议调试工具-CSDN博客 1 电池储能系统(BESS) 架构 电池储能系统主要包括、电池、pcs、本地控制…...
程序员也可以这样赚钱
最近有朋友和我交流了关于程序员副业的想法,我想借这个机会对目前软件开发常用的兼职平台做一个梳理。 以下是程序员接副业的靠谱平台推荐,结合政策合规性、平台口碑及实际操作性整理,覆盖国内外主流选择: 一、国内综合型平台 程序…...
STM32启动过程概述
1. STM32启动过程概述 STM32 微控制器的启动过程是指从上电或复位开始,到系统开始执行用户程序的整个过程。这个过程包括了硬件初始化、引导加载程序 (Bootloader) 执行、系统时钟配置、外设初始化等步骤。 2. STM32 启动的基本流程 上电或复位 STM32 芯片的启动过…...
Elasticsearch去分析目标服务器的日志,需要在目标服务器上面安装Elasticsearch 软件吗
Elasticsearch 本身并不直接收集目标服务器的日志,它主要用于存储、搜索和分析数据。要收集目标服务器的日志,通常会借助其他工具,并且一般不需要在目标服务器上安装 Elasticsearch 软件,常见的日志收集方案: Filebeat…...
Linux:软硬链接和动静态库
hello,各位小伙伴,本篇文章跟大家一起学习《Linux:软硬链接和动静态库》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 ! 如果本篇文章对你有帮助,还请各位点点赞࿰…...
C# 比较两个List集合内容是否相同
在 C# 中,要比较两个 List<T> 集合的内容是否相同,可以通过以下几种方法: 一、非自定义类的元素比较 1. 使用 SequenceEqual 方法(顺序和内容都相等) 顺序和内容都相等:使用 SequenceEqual。 usin…...
IDEA中Resolving Maven dependencies卡着不动解决方案
一、修改settings.xml Maven配置阿里云仓库主要通过修改Maven的settings.xml文件来实现。以下是具体步骤: 1、找到settings.xml文件: 通常位于Maven安装目录下的conf文件夹中,或者在用户目录下的.m2文件夹中(如果用户自定义了settings.xml的位置)。 2、编辑se…...
重生之我要当云原生大师(十一)访问Linux文件系统
目录 一、解释下文件系统、块设备、挂载点、逻辑卷。 二、简述文件系统、块设备、挂载点、逻辑卷之间的关系? 三、如何检查文件系统? 四、挂载和卸载文件系统的流程? 五、find命令都可以根据什么查找文件。 一、解释下文件系统、块设备、…...
驱动开发系列34 - Linux Graphics Intel 动态显存技术的实现
一:概述 动态显存技术(Dynamic Video Memory Technology, DVMT)是一种由 Intel 提出的内存分配技术,主要用于整合显卡(集成显卡)系统中,以便动态地调整显存大小,从而在不同的负载场景下优化内存使用和系统性能。 动态显存技术的核心在于共享系统内存。集成显卡没有独立…...
feign 远程调用详解
在平常的开发工作中,我们经常需要跟其他系统交互,比如调用用户系统的用户信息接口、调用支付系统的支付接口等。那么,我们应该通过什么方式进行系统之间的交互呢?今天,简单来总结下 feign 的用法。 1:引入依…...
HTML5 应用程序缓存
HTML5 应用程序缓存 引言 随着互联网技术的飞速发展,移动设备的普及,用户对于网页的加载速度和离线访问的需求日益增长。HTML5 应用程序缓存(Application Cache,简称AppCache)应运而生,它允许开发者将网页资源存储在本地,从而实现网页的快速加载和离线访问。本文将详细…...
7.PPT:“中国梦”学习实践活动【20】
目录 NO1234 NO5678 NO9\10\11 NO1234 考生文件夹下创建一个名为“PPT.pptx”的新演示文稿Word素材文档的文字:复制/挪动→“PPT.pptx”的新演示文稿(蓝色、黑色、红色) 视图→幻灯片母版→重命名:“中国梦母版1”→背景样…...
利用NestJS构建高效的RESTful API接口
1. 引言 项目背景与目标 随着互联网应用的快速发展,RESTful API已成为前后端分离架构中的重要组成部分。本文将介绍如何使用NestJS构建一个高效且可维护的RESTful API接口。目标是通过NestJS的模块化和依赖注入特性,实现一个易于扩展和维护的API系统。 RESTful API的重要性…...
YOLOv11实时目标检测 | 摄像头视频图片文件检测
在上篇文章中YOLO11环境部署 || 从检测到训练https://blog.csdn.net/2301_79442295/article/details/145414103#comments_36164492,我们详细探讨了YOLO11的部署以及推理训练,但是评论区的观众老爷就说了:“博主博主,你这个只能推理…...
Spring基于文心一言API使用的大模型
有时做项目我们可能会遇到要在项目中对接AI大模型 本篇文章是对使用文心一言大模型的使用总结 前置任务 在百度智能云开放平台中注册成为开发者 百度智能云开放平台 进入百度智能云官网进行登录,点击立即体验 点击千帆大模型平台 向下滑动,进入到模型…...
目前推荐的优秀编程学习网站与资源平台,涵盖不同学习方式和受众需求
一、综合教程与互动学习平台 菜鸟教程 特点:适合零基础新手,提供免费编程语言教程(Python、Java、C/C++、前端等),页面简洁且包含大量代码示例,支持快速上手。适用人群:编程入门者、需要快速查阅语法基础的学习者。W3Schools 特点:专注于Web开发技术(HTML、CSS、JavaS…...
kafka 3.5.0 raft协议安装
前言 最近做项目,需要使用kafka进行通信,且只能使用kafka,笔者没有测试集群,就自己搭建了kafka集群,实际上笔者在很早之前就搭建了,因为当时还是zookeeper(简称ZK)注册元数据&#…...
正则表达式的简单介绍 + regex_match使用
正则表达式 正则表达式(Regular Expression,简称 regex)是一种用于匹配字符串的模式。它由一系列字符和特殊符号组成,用于描述、匹配一系列符合某个句法规则的字符串。正则表达式广泛应用于文本搜索、替换、验证等场景。 它的主…...
kafka服务端之延时操作前传--时间轮
文章目录 背景时间轮层级时间轮时间轮降级kafka中的时间轮kafka如何进行时间轮运行 背景 Kafka中存在大量的延时操作,比如延时生产、延时拉取和延时删除等。Kafka并没有使用JDK自带的Timer或DelayQueue来实现延时的功能,而是基于时间轮的概念自定义实现…...
鸿蒙harmony 手势密码
1.效果图 2.设置手势页面代码 /*** 手势密码设置页面*/ Entry Component struct SettingGesturePage {/*** PatternLock组件控制器*/private patternLockController: PatternLockController new PatternLockController()/*** 用来保存提示文本信息*/State message: string …...
如何修复WordPress连接超时显示curl-error-28的错误
许多WordPress用户都会遇到这样的问题:网站加载变慢或数据传输失败,后台提示‘cURL错误28:连接超时’。这其实是一个常见的问题,通常是由于数据传输时间过长造成的。虽然这个错误听起来复杂,但解决起来并不算困难。本文…...
CSS 相关知识
1、高度已知,三栏布局,左右宽度 200,中间自适应,如何实现? <body><div class"box"><div class"box1">高度已知</div><div class"box2">左右宽度 200&…...