205、使用消息队列实现 RPC(远程过程调用)模型的 服务器端 和 客户端
目录
- ★ RPC模型(远程过程调用通信模型)
- ▲ 完整过程:
- 代码演示
- 总体流程解释:
- ConstantUtil 常量工具类
- ConnectionUtil RabbitMQ连接工具类
- Server 服务端
- Client 客户端
- 测试结果
- 服务端
- 客户端
- 完整代码
- ConstantUtil 常量工具类
- ConnectionUtil RabbitMQ连接工具类
- Server 服务端
- Client 客户端
- pom.xml
★ RPC模型(远程过程调用通信模型)
PRC 模型相当于一对一,跟调用方法一样,能拿到方法返回的结果,这就是典型的RPC模型(不仅要传参数,还需要拿到返回值)
reply:回答、答复
correlation :关联、相互关联
▲ 通过使用两个独享队列,可以让RabbitMQ实现RPC(远程过程调用)通信模型,
其通信过程其实也很简单:客户端向服务器消费的独享队列发送一条消息,服务器收到该消息后,对该消息进行处理,然后将处理结果发送给客户端消费的独享队列。
▲ 服务器端消费的独享队列负责保存调用参数,客户端消费的独享队列负责保存调用的返回值。
▲ 使用独享队列可以避免其他连接来读取队列的消息、只有当前连接才能读取该队列的消息,这样才能保证服务器能读到客户端发送的每条消息,客户端也能读到服务器返回的每条消息。
▲ 为了让服务器知道客户端所消费的独享队列,客户端发送消息时,应该将自己监听的队列名以 reply_to属性 发送给服务器
▲为了能准确识别服务器应答消息(返回值)与客户端请求消息(调用参数)之间的对应关系,
还需要为每条消息都增加一个 correlation_id 属性,两条具有相同 correlation_id 属性值的消息可认为是配对的两条消息。
【备注】:客户端送出的消息要包含2个属性:
-
reply_to:该属性指定了服务器要将返回的消息送回到哪个队列。
-
correlation_id:该属性指定了服务器返回的消息也要添加相同的correlation_id属性。
▲ 完整过程:
(1)服务器启动时,它会创建一个名为“rpc_queue”的独享队列(名称可以随意),并使用服务器端的消费者监听该独享队列的消息。(所有的RPC 调用,一定都是先从服务器端的启动开始的。)(2)客户端启动时,它会创建一个匿名(默认)(由RabbitMQ命名)的 独享队列,并使用客户端的消费者监听该独享队列的消息。(这个独享队列的名字也是 reply_to 属性的属性值)(3)客户端发送带有两个属性的消息:一个是代表应答队列名的 reply_to属性(该属性值就是第2步客户端所创建的独享队列名),另一个是代表消息标识的 correlation_id 属性。(4)将消息发送到服务器监听的rpc_queue队列中。(5)服务器从rpc_queue队列中读取消息,服务器调用处理程序对该消息进行计算,将计算结果以消息发送给 reply_to属性 指定的队列,并为消息添加相同的 correlation_id属性。(6)客户端从 reply_to 对应的队列中读取消息,当消息出现时,它会检查消息的 correlation_id属性。如果此属性的值与请求消息的 correlation_id 属性值匹配,将它返回给应用。————上面过程,其实就是对P2P模型的应用,因此无需使用自己的Exchange,而是使用系统自动创建的默认Exchange即可。
代码演示
需求:客户端发送个消息到服务端,服务端处理完再返回结果给客户端。
如图:需要有两个消息队列,一个是服务端
总体流程解释:
(仅作为自己梳理代码流程的记录,大佬请直接忽略)
Server 类是服务端 , Client 是客户端。
rpc_queue 是自己在服务端声明创建的消息队列,服务端监听着这个消息队列
amq.gen-3Nl6GNjR5BzPJ4N-By4p1g 是客户端声明创建的一个默认生成的消息队列。
(就是调用 Channel 的 queueDeclare() 方法声明队列时,不指定具体的消息队列的参数,全凭默认生成),客户端监听着这个默认的消息队列。
replyTo 的值是 amq.gen-3xxx 这个默认消息队列,作用是指定了服务器要将返回的消息送回到这个默认队列
correlationId 只是一个单纯的消息标识,可以给个1、2、3、4…作为消息标识
上面这些就是涉及到的一些点,下面就是流程:
首先,客户端会发送几个消息,Exchange时默认的,路由key 是 rpc_queue , 每个消息都携带者 replyTo 和 correlationId 这两个属性值;
(解释:如果消息发布者指定默认的Exchange,那么Exchange就会根据消息发布者发来的消息中携带的路由key(假如路由key叫 aaa) ,去找是否有同样名字叫aaa的消息队列,有的话就把消息分发给消息队列,没有的话该消息就会被丢弃);
这些消息会被默认的Exchange分发给 rpc_queue 这个消息队列。
服务端在声明 rpc_queue 这个消息队列的时候,把这个消息队列设置为独享类型(exclusive:true),那么 rpc_queue 这个消息队列里面的消息就只能被这个服务端消费,不能被其他消费者获取到消息。
因为服务端监听这个 rpc_queue 这个消息队列,所以服务端拿到这个消息队列的消息之后,就会把消息中的 replyTo 和 correlationId 先拿出来,然后同时对消息进行业务逻辑处理。
业务逻辑处理完消息后,服务端需要把这些处理后的消息返回给客户端。
重点就是,每个消息在客户端发来之后,都有一个 correlationId 标识,所以服务端在返回回去时,需要把处理好的消息的原本的correlationId 标识对应的设置回去。
(比如:客户端发来消息 A , A 携带的 correlationId 为 1 ,那么服务端在处理完 A 消息后,需要把 correlationId = 1 再设置回 这个消息 A (就是拿出来,处理完消息,再放回去),这样客户端在接收服务端返回来的处理过后的A消息时,才能根据 correlationId = 1 这个标识,得到想要的被处理过的A消息数据。
因为客户端发的消息可能有成千上万条,需要有这个 correlationId 作为消息的标识,才能准确拿到被处理过的想要的那条A消息)
服务端返回处理过的消息给客户端,也是一个发送消息的过程,所以发送消息指定的消息队列就是这个 replyTo(就是amq.gen-3xxx 这个默认消息队列),这个replyTo 也是从客户端发送来的消息中获取获取一个属性,作用是指定了服务端要将返回的消息送回到这个默认队列。
服务端把处理后的消息返回到 amq.gen-3xxx 这个默认消息队列,因为 客户端就是在监听amq.gen-3xxx 这个默认消息队列,所以客户端就能得到自己一开始发送给服务端,然后服务端处理完成后返回来的消息。
然后客户端就能根据 correlationId 这个标识,准确找到每个被处理修改过后的消息,而不至于找混。再根据需求去对处理过的消息进行业务操作。
(以上仅作为自己梳理代码流程的记录,大佬请直接忽略)
更简单点来说,就是
客户端声明并监听着默认队列 amq.gen-3xxx,然后发送消息到客户端,消息携带有correlationId 和 replyto 两个属性,路由key是rpc_queue,exchange是默认的。
服务端声明并监听 rpc_queue 消息队列,从该队列得到消息(messageA)后,从每个消息中获取该消息对应的 correlationId 和 replyto 两个属性的属性值,然后处理消息,对于处理完的消息(resultMessageA),需要把correlationId 和 replyto 两个属性的属性值重新设置回给resultMessageA,在通过Exchange分发回给 replyto 属性值中指定的消息队列(amq.gen-3xxx)。
然后客户端再从 amq.gen-3xxx 默认的消息队列中获取服务端处理并返回回来的消息,进行对应的消费。
ConstantUtil 常量工具类
ConnectionUtil RabbitMQ连接工具类
Server 服务端
Client 客户端
测试结果
启动测试的时候,一定要先启动服务端,再启动客户端
服务端
客户端
QUEUE
完整代码
ConstantUtil 常量工具类
package cn.ljh.rabbitmq.util;//常量
public class ConstantUtil
{//消息队列实现 RPC(远程过程调用)模型 之 服务器端----------------//消息队列public final static String RPC_QUEUE = "rpc_queue";// ------------topic类型的Exchange,需要的相关常量----------public final static String QUEUET01 = "qt_01";public final static String QUEUET02 = "qt_02";// topic 通配符类型的 Exchangepublic static final String EXCHANGE_NAME_TOPIC = "myex03.topic";// Exchange 绑定 Queue 队列的路由key ,通配符类型 *:匹配一个单词。#:匹配零个或多个单词。public static final String[] ROUTING_TOPIC_PATTERNS = {"*.crazyit.*", "*.org", "edu.#"};// 生产者发送消息给Excahnge携带的路由keypublic static final String[] ROUTING_TOPIC_KEYS = { "www.crazyit.org", "www.crazyit.cn","edu.crazyit.org", "crazyit.org", "fkjava.org", "edu.fkjava.org", "edu.fkjava", "edu.org"};//-------------------------------------------------------// 消息队列的名称public final static String QUEUE01 = "queue_01";public final static String QUEUE02 = "queue_02";// Exchange的名称public static final String EXCHANGE_NAME = "myex02.direct";// 三个路由key定义成一个数组的名称public static final String[] ROUTING_KEYS = {"info", "error", "warning"};}
ConnectionUtil RabbitMQ连接工具类
package cn.ljh.rabbitmq.util;import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;import java.io.IOException;
import java.util.concurrent.TimeoutException;//连接工具
public class ConnectionUtil
{//获取连接的方法public static Connection getConnection() throws IOException, TimeoutException{//创建连接工厂----这个ConnectionFactory源码可以看出有构造器,所以直接new一个出来ConnectionFactory connectionFactory = new ConnectionFactory();//设置连接信息connectionFactory.setHost("localhost");connectionFactory.setPort(5672);connectionFactory.setUsername("ljh");connectionFactory.setPassword("123456");connectionFactory.setVirtualHost("/"); //连接虚拟主机//从连接工厂获取连接Connection connection = connectionFactory.newConnection();//返回连接return connection;}
}
Server 服务端
package cn.ljh.rabbitmq.producer;import cn.ljh.rabbitmq.util.ConnectionUtil;
import cn.ljh.rabbitmq.util.ConstantUtil;
import com.rabbitmq.client.*;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.concurrent.TimeoutException;//消息队列实现 RPC(远程过程调用)模型 之 服务器端
public class Server
{public static void main(String[] args) throws IOException, TimeoutException{//1、创建连接Connection conn = ConnectionUtil.getConnection();//2、通过Connection获取Channel。Channel channel = conn.createChannel();//3、使用系统自动创建的默认Exchange,无需声明Exchange//消息队列设置为独占(exclusive:true)--------------------------------------------------------------------------------------声明消息队列channel.queueDeclare(ConstantUtil.RPC_QUEUE,true, /* 是否持久化 */true, /* 是否只允许只有这个消息队列的消息消费者才可以消费这个消息队列的消息 */false, /* 是否自动删除 */null); /* 指定这个消息队列的额外参数属性 *///不需要关闭资源,因为它也要监听自己消费消息的队列//4、调用Channel 的 basicConsume()方法开始消费消息----------------------------------------------------------------------------1、服务端监听并消费消息channel.basicConsume(ConstantUtil.RPC_QUEUE, /* 消费这个名字的消费队列里面的消息 */true,new DefaultConsumer(channel){//处理消息:当这个 ConstantUtil.RPC_QUEUE 消息队列收到消息的时候,这个方法就会被触发。重写这个方法:@Overridepublic void handleDelivery(String consumerTag,Envelope envelope /*消息所在的信封,存放消息的exchange、路由key这些*/,AMQP.BasicProperties properties /* 消息的那些属性 */,byte[] body /*body:消息的消息体*/) throws IOException{//把消息体中的消息拿出来,此处读取到的消息,就相当于调用参数-------------------------------------------------------2、服务端获取消息队列的消息String param = new String(body, StandardCharsets.UTF_8);//之前只需要用到消息,现在需要额外读取消息里面携带的两个属性:reply_to 和 correlation_id//消息的属性都存放在 AMQP.BasicProperties 这个属性里面,从这个属性获取 reply_to 和 correlation_idString replyTo = properties.getReplyTo();System.err.println("replyTo: " + replyTo);String correlationId = properties.getCorrelationId();System.err.println("correlationId: " + correlationId);//调用服务器的处理消息的方法,最终得到处理后的结果。该方法可以是任意的业务处理,该方法的返回值result是要被送回客户端的。------3、服务端处理消费消息String result = format(param);//printf:格式化输出函数 %s:输出字符串 %n:换行System.err.printf("服务端 收到来自Exchange为【%s】、路由key为【%s】的消息,消息内容为%s%n",envelope.getExchange(), envelope.getRoutingKey(), param);//发送消息的方法,需要把返回值result发送回客户端-------------------------------------------------------4、服务端处理消费完的消息返回客户端的操作channel.basicPublish("", /* 使用默认的Exchange */replyTo,/* 此处的routing key 应该填 reply_to 属性; reply_to: 该属性指定了服务器要将返回的消息送回到哪个队列 *///把从客户端的 AMQP.BasicProperties 属性获取到的correlationId,再作为参数传回去,用于客户端和服务器的匹配。new AMQP.BasicProperties().builder().correlationId(correlationId) /* 也需要返回额外的 correlation_id,要与从客户端消息中读取的 correlation_id 完全一样 */.deliveryMode(2) /* 设置这个消息是持久化类型的 */.build(), /*这个.build()的作用就是构建得到这个 BasicProperties 对象,这个对象就包含了 correlationId 属性因为服务器端返回的消息一定要有这个correlationId。 */result.getBytes(StandardCharsets.UTF_8));}});}//模拟服务器端消费消息要做的处理业务逻辑操作public static String format(String name){//此处模拟让服务器处理这里的业务有快有慢的情况,看correlation_id 能不能还是把数据对应上int rand = (new Random().nextInt(40) + 20) * 30;try{Thread.sleep(rand);} catch (InterruptedException e){e.printStackTrace();}return "《" + name + "》";}}
Client 客户端
package cn.ljh.rabbitmq.consumer;import cn.ljh.rabbitmq.util.ConnectionUtil;
import cn.ljh.rabbitmq.util.ConstantUtil;
import com.rabbitmq.client.*;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;//消息队列实现 RPC(远程过程调用)模型 之 客户端
public class Client
{// paramMap 保存了 correlationid 与 参数(消息)之间的对应关系public static Map<String, String> paramMap = new ConcurrentHashMap<>();//客户端发送的消息(参数)public static String[] params = new String[]{"火影忍者", "七龙珠", "哆啦A梦", "蜡笔小新"};public static void main(String[] args) throws IOException, TimeoutException{//1、创建连接工厂,设置连接信息,然后再通过连接工厂获取连接Connection conn = ConnectionUtil.getConnection();//2、通过Connection获取Channel 消息通道Channel channel = conn.createChannel();//3、调用 Channel 的 queueDeclare() 方法声明队列,声明一个有 RabbitMQ 自动创建的、自动命名的、持久化的、独享的、会自动删除的【默认队列】AMQP.Queue.DeclareOk declareOk = channel.queueDeclare();System.out.println("declareOk "+declareOk);//.getQueue() 用于得到默认队列的返回值,也就是默认队列的名字,之前声明是我们自己设置队列名,这里用默认的队列,就用.getQueue() 得到队列名。String queueName = declareOk.getQueue();System.out.println("queueName: "+queueName);//4、调用Channel 的 basicConsume()方法开始处理消费消息-----------------------------------------------------------------2、客户端监听服务端处理完消息后返回来的消息channel.basicConsume(queueName /*消费这个消费队列里面的消息*/,true /*消息的确认模式:是否自动确认该消息已经被消费完成并返回确认消息给消息队列*/,new DefaultConsumer(channel){//处理消息:当这个消息队列收到消息的时候,这个方法就会被触发。重写这个方法:@Overridepublic void handleDelivery(String consumerTag,Envelope envelope /*消息所在的信封,存放消息的exchange、路由key这些*/,AMQP.BasicProperties properties /*消息的那些属性*/,byte[] body /*body:消息的消息体*/) throws IOException{//把消息体中的消息拿出来String resultMessage = new String(body, StandardCharsets.UTF_8);//此处,需要指定每个返回值对应的是哪个参数,靠的就是correlation_idString correlationId = properties.getCorrelationId();//根据服务器端返回的消息中的correlation_id 获取对应的参数String param = paramMap.get(correlationId);System.err.println("客户端发出去的消息内容:"+param +" , 服务端处理后返回来的消息内容:"+resultMessage);//printf:格式化输出函数 %s:输出字符串 %n:换行System.out.printf("客户端 收到来自Exchange为【%s】、路由key为【%s】的消息,消息内容为%s%n",envelope.getExchange(), envelope.getRoutingKey(), resultMessage);//得到服务器的返回值之后,整个调用过程就完成了,此时就应该从 Map 中删除这组 key-value对了( correlationId 与 参数的对应关系 )。paramMap.remove(correlationId);}});//客户端发送消息---------------------------------------------------------------------------代码运行后先执行这段--------------------1、客户端发送消息for (int i = 0 ; i < params.length ; i++){paramMap.put( i + "" , params[i] );channel.basicPublish("", /* 使用默认的Exchange */ConstantUtil.RPC_QUEUE, /* 客户端发送消息携带的路由key是服务端监听的消息队列的名字,且使用了默认的Exchange,这就意味着消息会被发送给服务器监听的那个消息队列 */new AMQP.BasicProperties().builder().correlationId(i + "") /* 设置 correlation_id 属性; correlation_id:该属性指定了服务器返回的消息也要添加相同的correlation_id属性*/.replyTo(queueName) /* reply_to: 该属性指定了服务器要将返回的消息送回到哪个队列 , 设置 reply_to 属性 */.deliveryMode(2) /* 持久化消息 */.build(), /* 构建这个BasicProperties对象,这个对象主要存这个correlationId属性 */params[i].getBytes(StandardCharsets.UTF_8));}}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ljh</groupId><artifactId>rabbitmq_rpc</artifactId><version>1.0.0</version><name>rabbitmq_rpc</name><!-- 属性 --><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>11</java.version></properties><!-- 依赖 --><dependencies><!-- RabbitMQ 的依赖库 --><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.13.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.16</version><scope>compile</scope></dependency></dependencies></project>
相关文章:

205、使用消息队列实现 RPC(远程过程调用)模型的 服务器端 和 客户端
目录 ★ RPC模型(远程过程调用通信模型)▲ 完整过程:代码演示总体流程解释:ConstantUtil 常量工具类ConnectionUtil RabbitMQ连接工具类Server 服务端Client 客户端测试结果服务端客户端 完整代码ConstantUtil 常量工具类Connecti…...
C++中的函数
在C中,函数是程序的一部分,它执行特定的任务。函数的基本语法如下: type function-name( parameter list ) { body of the function } type 是函数的返回类型,function-name 是函数的名称, parameter list 是传递…...
java操作时间的方式
java操作时间的方式 获取年月日时分秒 public class Test { public static void main(String[] args) { System.out.println("----------使用Calendar--------------------"); Calendar cal Calendar.getInstance(); System.out.println(&q…...

上网冲浪发现多处XSS
突然的发现 今天上网冲浪,突然想起来有一种神器,叫废话生成器,之前是在哪里下了个软件玩了一下,然后就给删除了,因为我觉得这个软件不过就是调用了一个web接口实现的,一个网页能解决的事还要我下一个软件。…...
机器学习的打分方程汇总
机器学习的打分方程集合 受到机器学习(Machine Learning)和深度学习(Deep Learning)等算法模型的创新性冲击,其应用范围涵盖了自然语言处理(Natural Language Processing)、自动驾驶(…...

一文了解数据管理框架以及数据战略制定方法
这一节主要介绍数据管理这一章的另一重要部分,也就是我们在数据管理经常使用到的数据管理框架以及数据战略制定方法。 要制定数据管理框架,或者是组织需要制定数据治理规划或数据管理规划,需要首先制定与业务战略对齐的数据战略。 01、数据…...

智能管家“贾维斯”走进现实?AI Agent或成2023科技领域新风向标
漫威粉们想必都知道《钢铁侠》系列电影中,有一个不可或缺的角色——贾维斯。但就算是没有看过任何一部大电影的路人,只要通过一个词就可以了解“贾维斯”是一个什么样的角色——智能管家。 作为托尼斯塔克的助手,贾维斯的存在让主人的生活更…...

【广州华锐互动】VR高层小区安全疏散演练系统
在今天的高科技时代,虚拟现实(VR)技术已经被广泛应用到各个领域,包括教育和培训。由广州华锐互动定制开发的VR高层小区安全疏散演练系统,开始在房地产行业中崭露头角。这种系统通过模拟真实的紧急情况,帮助…...

用Python做一个文件夹整理工具
文章目录 简介文件夹对话框文件映射组件完整组件 简介 我们的目的是做一个像下面这样的工具,前面两个输入框,用于输入源路径和目标路径,下面的图片、视频、音乐表示在目标路径中创建的文件夹,后面的文件后缀,表示将这…...

Tortoise SVN 察看本地缓存密码
1、打开设置(Settings) 2、查看保存的数据 3、打开鉴权数据 4、查看密码 CTRLSHIFT双击表格,就会出现一列密码列 (我的是Mac PD虚拟Win11,CTRLSHIFTOPTION双击表格) 原文见这里: Recover SVN …...
MSP430F5529晶振配置
MSP430(F5529)相比MSP430(F149)来讲,功能更加强大。 UCS简介 MSP430F5XX/MSP430F6XX系列器件的UCS包含有五种时钟源,依次是:XT1CLK、VLOCLK、REFOCLK、DCOCLK和XT2CLK。这五种时钟的详细介绍请参考该系列芯片的指导手册,其中XT1C…...

[架构之路-237]:目标系统 - 纵向分层 - 网络通信 - DNS的递归查询和迭代查询
目录 一、DNS协议与DNS系统架构 1.1 什么是DNS协议 1.2 为什么需要DNS协议 1.3 DNS系统架构 二、DNS系统的查询方式 2.1 递归与迭代的比较 2.2 DNS递归查询 2.3 DNS迭代查询 一、DNS协议与DNS系统架构 1.1 什么是DNS协议 DNS(Domain Name Systemÿ…...

vue2 集成 Onlyoffice
缘起于进行了一次在线 Office 解决方案的调研,对比了 Office365、可道云、WPS Office、PageOffice 等厂商,最终敲定了使用 Onlyoffice,故整理了一份 Onlyoffice 从零开始系列教程,这是第一篇。 一、Onlyoffice 是什么?…...

天锐绿盾透明加密、半透明加密、智能加密这三种不同加密模式的区别和适用场景——@德人合科技-公司内部核心文件数据、资料防止外泄系统
由于企事业单位海量的内部数据存储情况复杂,且不同公司、不同部门对于文件加密的需求各不相同,单一的加密系统无法满足多样化的加密需求。天锐绿盾企业加密系统提供多种不同的加密模式,包括透明加密、半透明加密和智能加密,用户可…...

六、DHCP实验
拓扑图: DHCP协议,给定一个ip范围使其自动给终端分配IP,提高了IP分配的效率 首先对PC设备选择DHCP分配ip 首先先对路由器的下端配置网关的ip 创建地址池,通过globle的方式实现DHCP ip pool 地址池名称 之后设置地址池的网关地址…...

N沟道场效应管 FDA69N25深度图解 工作原理应用
深力科推荐一款 FDA69N25是高压 MOSFET产品,基于平面条形和 DMOS 技术。 该 MOSFET 产品专用于降低通态电阻,并提供更好的开关性能和更高的雪崩能量强度。 该器件系列适用于开关电源转换器应用,如功率因数校正(PFC)、…...
Python爬虫入门教程
文章目录: 一:Python基础 二:爬虫须知 1.流程 2.遵守规则 三:HTTP请求和响应 1.相关定义 2.HTTP请求响应 2.1 完整的HTTP请求 2.2 完整的HTTP响应 3.Requests库 四:HTML 1.HTML网页结构 2.常用标 参考&…...
使用正则前瞻检查密码强度
使用正则前瞻检查密码强度 题目要求 要求密码必须包含大小写字母,并且至少包含 $,_. 中的一个特殊字符。 在这道题中,我们可以使用正则表达式的前瞻运算来实现。 const reg /^(?.*\d)(?.*[a-z])(?.*[A-Z])(?.*[$,_.])[\da-zA-Z$,_.]{6,12}/;con…...

react+ts手写cron表达式转换组件
前言 最近在写的一个分布式调度系统,后端同学需要让我传入cron表达式,给调度接口传参。我去了学习了解了cron表达式的用法,发现有3个通用的表达式刚好符合我们的需求: 需求 每天 xx 的时间: 0 11 20 * * ? 上面是…...
民安智库(第三方市民健康素养调研)居民健康素养调查的重要性及实施步骤
一、背景和意义 健康素养是衡量一个社区或国家居民对健康知识的理解,以及他们如何将这些知识应用于日常生活中的能力的重要指标。它不仅包括了基本的医学知识,如疾病预防和治疗,也包括了生活方式的改善,如合理饮食和适当运动。因…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
深度解析云存储:概念、架构与应用实践
在数据爆炸式增长的时代,传统本地存储因容量限制、管理复杂等问题,已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性,成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理,云存储正重塑数据存储与…...

解密鸿蒙系统的隐私护城河:从权限动态管控到生物数据加密的全链路防护
摘要 本文以健康管理应用为例,展示鸿蒙系统如何通过细粒度权限控制、动态权限授予、数据隔离和加密存储四大核心机制,实现复杂场景下的用户隐私保护。我们将通过完整的权限请求流程和敏感数据处理代码,演示鸿蒙系统如何平衡功能需求与隐私安…...

STM32 低功耗设计全攻略:PWR 模块原理 + 睡眠 / 停止 / 待机模式实战(串口 + 红外 + RTC 应用全解析)
文章目录 PWRPWR(电源控制模块)核心功能 电源框图上电复位和掉电复位可编程电压监测器低功耗模式模式选择睡眠模式停止模式待机模式 修改主频一、准备工作二、修改主频的核心步骤:宏定义配置三、程序流程:时钟配置函数解析四、注意…...

智能照明系统:具备认知能力的“光神经网络”
智能照明系统是物联网技术与传统照明深度融合的产物,其本质是通过感知环境、解析需求、自主决策的闭环控制,重构光与人、空间、环境的关系。这一系统由智能光源、多维传感器、边缘计算单元及云端管理平台构成,形成具备认知能力的“光神经网络…...