当前位置: 首页 > news >正文

根据源码,模拟实现 RabbitMQ - 虚拟主机 + Consume设计 (7)

目录

一、虚拟主机 + Consume设计

1.1、承接问题

1.2、具体实现

1.2.1、消费者订阅消息实现思路

1.2.2、消费者描述自己执行任务方式实现思路

1.2.3、消息推送给消费者实现思路

1.2.4、消息确认


一、虚拟主机 + Consume设计


1.1、承接问题

前面已经实现了虚拟主机大部分功能以及转发规则的判定,也就是说,现在消息已经可以通过 转换机 根据对应的转发规则发送给对应的 队列 了.

那么接下来要解决的问题就是,消费者该如何订阅消息(队列),如何把消息推送给消费者,以及消费者如何描述自己怎么执行任务~

1.2、具体实现

1.2.1、消费者订阅消息实现思路

消费者是以队列为维度订阅消息的,并且一个队列可以被多个消费者订阅,那么一旦队列中有消息,这个消息到底因该给谁呢?此处就约定,消费者之间按照 “轮询” 的方式来进行消费.

这里我们就需要定义一个类(ConsumerEnv),用来描述一个消费者,如下

public class ConsumerEnv {private String consumerTag;private String queueName;private boolean autoAck;//通过这个回调来处理收到的消息private Consumer consumer;public ConsumerEnv(String consumerTag, String queueName, boolean autoAck, Consumer consumer) {this.consumerTag = consumerTag;this.queueName = queueName;this.autoAck = autoAck;this.consumer = consumer;}public String getConsumerTag() {return consumerTag;}public void setConsumerTag(String consumerTag) {this.consumerTag = consumerTag;}public String getQueueName() {return queueName;}public void setQueueName(String queueName) {this.queueName = queueName;}public boolean isAutoAck() {return autoAck;}public void setAutoAck(boolean autoAck) {this.autoAck = autoAck;}public Consumer getConsumer() {return consumer;}public void setConsumer(Consumer consumer) {this.consumer = consumer;}
}

 

再给每个队列对象(MSGQueue 对象)添加一个属性 List,用来包含若干个上述消费者(有哪些消费者订阅了当前队列),如下图:

    //当前队列都有哪些消费者订阅了private List<ConsumerEnv> consumerEnvList = new ArrayList<>();//记录当取到了第几个消费者(AtomicInteger 是线程安全的)private AtomicInteger consumerSeq = new AtomicInteger(0);/*** 添加一个新的订阅者* @param consumerEnv*/public void addConsumerEnv(ConsumerEnv consumerEnv) {consumerEnvList.add(consumerEnv);}/*** 删除订阅者暂时先不考虑*//*** 挑选一个订阅者,来处理当前的消息(按照轮询的方式)* @return*/public ConsumerEnv chooseConsumer() {if(consumerEnvList.size() == 0) {//该队列暂时没有人订阅return null;}//计算当前要取的下标int index = consumerSeq.get() % consumerEnvList.size();consumerSeq.getAndIncrement();// 自增return consumerEnvList.get(index);}

VirtualHost 中订阅消息实现

    /*** 订阅消息* 添加一个队列的订阅者,当队列收到消息之后,就要把消息推送给对应的订阅者* @param consumerTag 消费者的身份标识* @param queueName* @param autoAck 消息被消费之后,应答的方式,true 标识自动应答,false 标识手动应答* @param consumer 是一个回调函数,此处设定成函数式接口,这样后续调用 basicConsume 并且传实参的时候,就可以写作 lambda 样子了* @return*/public boolean basicConsume(String consumerTag, String queueName, boolean autoAck, Consumer consumer) {//构造一个 ConsumerEnv 对象,把这个对应的队列找到,再把 Consumer 对象添加到队列中queueName = virtualHostName + queueName;try {consumerManager.addConsumer(consumerTag, queueName, autoAck, consumer);System.out.println("[VirtualHost] basicConsume 成功! queueName=" + queueName);return true;} catch (Exception e) {e.printStackTrace();System.out.println("[VirtualHost] basicConsume 失败! queueName=" + queueName);return false;}}

1.2.2、消费者描述自己执行任务方式实现思路

当执行订阅消息的时候,我们就让消费者自己去实现处理消息的操作(消息的内容通过参数传递,具体要干啥,取决于消费者自己的业务路基),最后再让线程池来执行回调函数.

这里我们使用函数式接口(回调函数)的方式(lambda 表达式),让消费者在订阅消息的时候,就可以实现未来收到消息后如何去处理消息的操作.

@FunctionalInterface
public interface Consumer {/*** Delivery 的意思是 ”投递“,这个方法预期是在服务器收到消息之后来调用* 通过这个方法,把消息推送给对应的消费者* (注意,这里的方法名和参数,也都是参考 RabbitMQ 来展开的)* @param consumerTag* @param basicProperties* @param body*/void handlerDelivery(String consumerTag, BasicProperties basicProperties, byte[] body);}

为什么要这样实现?

一方面,这种思路也是参考 RabbitMQ。

另一方面,这是由于Java 的函数是不能脱离类存在的,为了实现这种 lambda,java 曲线救国,引入 函数式接口.

对于函数式接口来说:

  1. 首先是 interface 类型
  2. 只能有一个方法
  3. 添加 @FunctionalInterface 注解.

实际上,这也是 lambda 的底层实现(本质)

1.2.3、消息推送给消费者实现思路

这里我们可以添加一个扫描线程,让他来去队列中拿任务.

为什么用了扫描线程还需要用线程池?

如果就一个扫描线程,既要获取消息,又要执行回调,这一个线程可能会忙不过来,因为消费者给出的回调,具体干什么的,咱们是不知道的.

扫描线程怎么知道哪个队列来了新的消息?

  1. 一个简单粗暴的办法,就是直接让扫描线程不停的循环遍历所有队列,发现有元素就立即处理。
  2. 另一个更优雅的办法(我采取的办法),就是用一个阻塞队列,队列中的元素就是接收消息的队列的名字,扫描线程只需要盯住这一个阻塞对垒即可,此时阻塞队列中传递的队列名,就相当于 “令牌”

每次拿到令牌,才能调动一次军队,也就是从对应的队列中取一个消息.

具体的,实现一个 ConsumerManager 类,用来管理消费者的上述行为.

public class ConsumerManager {// 持有上层的 VirtualHost 对象的引用,用来操作数据private VirtualHost parent;// 指定一个线程池,负责取执行具体的回调任务private ExecutorService workerPool = Executors.newFixedThreadPool(4);//存放令牌的队列private BlockingQueue<String> tokenQueue = new LinkedBlockingQueue<>();//扫描线程private Thread scannerThread = null;/*** 初始化* @param parent*/public ConsumerManager(VirtualHost parent) {this.parent = parent;//创建扫描线程,取队列中消费消息scannerThread = new Thread(() -> {while(true) {try {//1.拿到令牌String queueName = tokenQueue.take();//2.根据令牌,找到队列MSGQueue queue = parent.getMemoryDataCenter().getQueue(queueName);if(queue == null) {throw new MqException("[ConsumerManager] 取到令牌后发现,该队列名不存在!queueName=" + queueName);}//3.从这个队列中消费一个消息synchronized (queue) {consumeMessage(queue);}} catch (InterruptedException | MqException e) {throw new RuntimeException(e);}}});//设置为后台线程scannerThread.setDaemon(true);scannerThread.start();}public void notifyConsume(String queueName) throws InterruptedException {tokenQueue.put(queueName);}/*** 添加消费者* 找到对应队列的 List 列表, 把消费者添加进去,最后判断,如果有消息,就立刻消费* @param consumerTag 消费者身份标识* @param queueName* @param autoAck 消息被消费之后,应答的方式,true 标识自动应答,false 标识手动应答* @param consumer 是一个回调函数,此处设定成函数式接口,这样后续调用 basicConsume 并且传实参的时候,就可以写作 lambda 样子了* @throws MqException*/public void addConsumer(String consumerTag, String queueName, boolean autoAck, Consumer consumer) throws MqException {//找到对应的队列MSGQueue queue = parent.getMemoryDataCenter().getQueue(queueName);if(queue == null) {throw new MqException("[ConsumerManager] 队列不存在! queueName=" + queueName);}ConsumerEnv consumerEnv = new ConsumerEnv(consumerTag, queueName, autoAck, consumer);synchronized (queue) {queue.addConsumerEnv(consumerEnv);//如果当前队列中已经有一些消息了,需要立即消费掉int n = parent.getMemoryDataCenter().getMessageCount(queueName);for(int i = 0; i < n; i++) {//这个方法调用一次就消费一条消息consumeMessage(queue);}}}/*** 扫描线程:找到对应的队列后,消费者从队列中拿出消息并消费* @param queue*/private void consumeMessage(MSGQueue queue) {//1.按照轮询的方式,找个消费者出来ConsumerEnv luckDog = queue.chooseConsumer();if(luckDog == null) {//当前队列中没有消费者,暂时不用消费,等后面有消费者了再说return;}//2.从队列中取出一个消息Message message = parent.getMemoryDataCenter().pollMessage(queue.getName());if(message == null) {//当前队列中还没有消息,也不需要消费return;}//3.把消息带入到消费者的回调方法中,丢给线程池执行workerPool.submit(() -> {try {//1.把消息放到待确认的集合当中,这个操作一定要在执行回调之前(防止执行回调过程中出现异常,导致消息丢失)parent.getMemoryDataCenter().addMessageWaitAck(luckDog.getQueueName(), message);//2.真正执行回调操作luckDog.getConsumer().handlerDelivery(luckDog.getConsumerTag(), message.getBasicProperties(),message.getBody());//3.如果当前是 ”自动应答“ ,就可以直接把消息删除了//  如果当前是 ”手动应答“ ,则先不处理,交给后续消费者调用 basicAck 方法来处理if(luckDog.isAutoAck()) {//1) 删除硬盘上的消息if(message.getDeliverMode() == 2) {parent.getDiskDataCenter().deleteMessage(queue, message);}//2) 删除上面的待确认集合中的消息parent.getMemoryDataCenter().removeMessageWaitAck(queue.getName(), message.getMessageId());//3) 删除内存上的消息中心的消息parent.getMemoryDataCenter().removeMessage(message.getMessageId());System.out.println("[ConsumerManager] 消息被成功消费!queueName=" + queue.getName());}} catch (Exception e) {e.printStackTrace();}});}
}

1.2.4、消息确认

消息确认,就是保证消息被正确消费~~ 

正确消费就是指消费者的回调方法顺利执行完了(没有抛异常之类的),这条消息的使命就完成了,此时就可以删除了。

为了达成消息不丢失这样的效果,具体步骤如下:

  1. 在真正执行回调之前,把消息放到 “待确认的集合” 中,避免应为回调失败,导致消息丢失.
  2. 执行回调
  3. 当去消费者采取的是 autoAck=true ,就认为回调执行完毕不抛异常,就算消费成功,然后就可以删除消息了
    1. 硬盘
    2. 内存中的消息中心
    3. 待确认的消息集合
  4. 当前消费者若采取的是 autoAck=false,手动应答,需要消费者这边,在自己的回调方法内部,显式调用 basicAck 这个核心 API 表示应答.

 basicAck 完成主动应答

    /*** 确认消息* 各个维度删除消息即可* @param queueName* @param messageId* @return*/public boolean basicAck(String queueName, String messageId) {queueName = virtualHostName + queueName;try {//1.获取消息和队列MSGQueue queue = memoryDataCenter.getQueue(queueName);if(queue == null) {throw new MqException("[VirtualHost] 要确认的队列不存在!queueName=" + queueName);}Message message = memoryDataCenter.getMessage(messageId);if(message == null) {throw new MqException("[VirtualHost] 要确认的消息不存在!messageId=" + messageId);}//2.各个维度删除消息if(message.getDeliverMode() == 2) {diskDataCenter.deleteMessage(queue, message);}memoryDataCenter.removeMessage(messageId);memoryDataCenter.removeMessageWaitAck(queueName, messageId);System.out.println("[VirtualHost] basicAck 成功,消息确认成功!queueName=" + queueName +", messageId=" + messageId);return true;} catch (Exception e) {System.out.println("[VirtualHost] basicAck 失败,消息确认失败!queueName=" + queueName +", messageId=" + messageId);e.printStackTrace();return false;}}

扫描线程完成自动应答

    /*** 扫描线程:找到对应的队列后,消费者从队列中拿出消息并消费* @param queue*/private void consumeMessage(MSGQueue queue) {//1.按照轮询的方式,找个消费者出来ConsumerEnv luckDog = queue.chooseConsumer();if(luckDog == null) {//当前队列中没有消费者,暂时不用消费,等后面有消费者了再说return;}//2.从队列中取出一个消息Message message = parent.getMemoryDataCenter().pollMessage(queue.getName());if(message == null) {//当前队列中还没有消息,也不需要消费return;}//3.把消息带入到消费者的回调方法中,丢给线程池执行workerPool.submit(() -> {try {//1.把消息放到待确认的集合当中,这个操作一定要在执行回调之前(防止执行回调过程中出现异常,导致消息丢失)parent.getMemoryDataCenter().addMessageWaitAck(luckDog.getQueueName(), message);//2.真正执行回调操作luckDog.getConsumer().handlerDelivery(luckDog.getConsumerTag(), message.getBasicProperties(),message.getBody());//3.如果当前是 ”自动应答“ ,就可以直接把消息删除了//  如果当前是 ”手动应答“ ,则先不处理,交给后续消费者调用 basicAck 方法来处理if(luckDog.isAutoAck()) {//1) 删除硬盘上的消息if(message.getDeliverMode() == 2) {parent.getDiskDataCenter().deleteMessage(queue, message);}//2) 删除上面的待确认集合中的消息parent.getMemoryDataCenter().removeMessageWaitAck(queue.getName(), message.getMessageId());//3) 删除内存上的消息中心的消息parent.getMemoryDataCenter().removeMessage(message.getMessageId());System.out.println("[ConsumerManager] 消息被成功消费!queueName=" + queue.getName());}} catch (Exception e) {e.printStackTrace();}});}

如果在回调方法中抛异常了?

回调方法中抛异常了,后续逻辑执行不到,这个消息就会始终呆在待确认的集合中, RabbitMQ 的做法是另外搞一个扫描线程(其实 RabbitMQ 中不叫线程,人家是叫进程,但是注意,这个进程不是操作系统中的进程,而是 erlang 中的概念),负责关注这个 待确认集合中,每个消息待了多久了,如果超出了一定的时间范围,就会把这个消息放到一个特定的队列 —— “死信队列”(这里就不展示了,需要的可以私聊我)

如果在执行回调过程中,broker server 崩了,内存数据全没了?

此时硬盘的数据还在,broker server 重启之后,这个消息就又被加载回内存了,就像从来没有被消费过一样,消费者就又机会重新拿到这个消息,重新消费(重复消费的问题,是由消费者的业务代码负责保证的,broker server 管不了).

 

相关文章:

根据源码,模拟实现 RabbitMQ - 虚拟主机 + Consume设计 (7)

目录 一、虚拟主机 Consume设计 1.1、承接问题 1.2、具体实现 1.2.1、消费者订阅消息实现思路 1.2.2、消费者描述自己执行任务方式实现思路 1.2.3、消息推送给消费者实现思路 1.2.4、消息确认 一、虚拟主机 Consume设计 1.1、承接问题 前面已经实现了虚拟主机大部分功…...

docker中bridge、host、container、none四种网络模式简介

目录 一.bridge模式 1.简介 2.演示 &#xff08;1&#xff09;运行两个容器&#xff0c;不指定网络模式情况下默认是bridge模式 &#xff08;2&#xff09;在主机中自动生成了两个veth设备 &#xff08;3&#xff09;查看两个容器的IP地址 &#xff08;4&#xff09;可以…...

排序算法之详解冒泡排序

引入 冒泡排序顾名思义&#xff0c;就是像冒泡一样&#xff0c;泡泡在水里慢慢升上来&#xff0c;由小变大。虽然冒泡排序和冒泡并不完全一样&#xff0c;但却可以帮助我们理解冒泡排序。 思路 一组无序的数组&#xff0c;要求我们从小到大排列 我们可以先将最大的元素放在数组…...

el-upload组件调用后端接口上传文件实践

要点说明&#xff1a; 使用:http-request覆盖默认的上传行为&#xff0c;可以添加除文件外的其他参数&#xff0c;注意此时仍需保留action属性&#xff0c;action可以传个空串给http-request属性绑定的函数&#xff0c;函数入参必须为param调用接口请求&#xff0c;注意 heade…...

深度学习-实验1

一、Pytorch基本操作考察&#xff08;平台课专业课&#xff09; 使用&#x1d413;&#x1d41e;&#x1d427;&#x1d42c;&#x1d428;&#x1d42b;初始化一个 &#x1d7cf;&#x1d7d1;的矩阵 &#x1d474;和一个 &#x1d7d0;&#x1d7cf;的矩阵 &#x1d475;&am…...

互联网医院开发|医院叫号系统提升就医效率

在这个数字化时代&#xff0c;互联网医院不仅改变了我们的生活方式&#xff0c;也深刻影响着医疗行业。医院叫号系统应运而生&#xff0c;它能够有效解决患者管理和服务方面的难题。不再浪费大量时间在排队上&#xff0c;避免患者错过重要信息。同时&#xff0c;医护工作效率得…...

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)

这里写目录标题 前言温馨提示手把手带你解析 MapperScan 源码手把手带你解析 MapperScan 源码细节剖析工厂模式Jdk 代理手撕脚手架&#xff0c;复刻 BeanDefinitionRegistryPostProcessor手撕 FactoryBean代理 Mapper 在 Spring 源码中的生成流程手撕 MapperProxyFactory手撕增…...

【C++11算法】iota算法

文章目录 前言一、iota函数1.1 iota是什么&#xff1f;1.2 函数原型1.3 参数和返回值1.4 示例代码1.5 示例代码21.6 示例代码3 总结 前言 C标准库提供了丰富的算法&#xff0c;其中之一就是iota算法。iota算法用于填充一个区间&#xff0c;以递增的方式给每个元素赋予一个值。…...

付费加密音乐格式转换Mp3、Flac工具

一、工具介绍 这是一款免费的将付费加密音乐等多种格式转换Mp3 Flac工具,现在大部分云音乐公司,比如QQ音乐、酷我音乐、酷狗音乐、网易云音乐、虾米音乐(RIP🙏)等,都推出了自己专属的云音乐格式,这些格式一般只能在制定的播放器里播放,其它的播放软件并不支持,在很多情…...

React前端开发架构:构建现代响应式用户界面

在当今的Web应用开发中&#xff0c;React已经成为最受欢迎的前端框架之一。它的出色性能、灵活性和组件化开发模式&#xff0c;使得它成为构建现代响应式用户界面的理想选择。在这篇文章中&#xff0c;我们将探讨React前端开发架构的核心概念和最佳实践&#xff0c;以帮助您构建…...

Azure Bastion的简单使用

什么是Azure Bastion Azure Bastion 是一个提供安全远程连接到 Azure 虚拟机&#xff08;VM&#xff09;的服务。传统上&#xff0c;访问 VM 需要使用公共 IP 或者设立 VPN 连接&#xff0c;这可能存在一些安全风险。Azure Bastion 提供了一种更安全的方式&#xff0c;它是一个…...

深入理解高并发编程 - 深度解析ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor 并实现了 ScheduledExecutorService 接口&#xff0c;这使得它可以同时充当线程池和定时任务调度器。 构造方法 public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, …...

Android---- 一个完整的小项目(消防app)

前言&#xff1a; 针对不同群体的需求&#xff0c;想着应该拓展写方向。医疗app很受大家喜欢&#xff0c;就打算顺手写个消防app&#xff0c;里面基础框架还是挺简洁 规整的。登陆注册和本地数据库写的便于大家理解。是广大学子的毕设首选啊&#xff01; 此app主要为了传递 消防…...

XXX程序 详细说明

用于记录理解PC程序的程序逻辑 1、程序的作用 根据原作者的说明&#xff08;文件说明.txt&#xff09;&#xff0c;该程序 (PC.py) 的主要作用是提取某一个文件夹中的某个设备 (通过config中的信息看出来是Ag_T_8) 产生的日志文件&#xff0c;然后提取其中某些需要的数据&…...

perl下载与安装教程【工具使用】

Perl是一个高阶程式语言&#xff0c;由 Larry Wall和其他许多人所写&#xff0c;融合了许多语言的特性。它主要是由无所不在的 C语言&#xff0c;其次由 sed、awk&#xff0c;UNIX shell 和至少十数种其他的工具和语言所演化而来。Perl对 process、档案&#xff0c;和文字有很强…...

Chrome谷歌浏览器修改输入框自动填充样式

Chrome谷歌浏览器修改输入框自动填充样式 背景字体 背景 input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #fff inset !important; }字体 input:-internal-autofill-selected {-webkit-text-fill-color: #000 !important; }...

Azure CLI 进行磁盘加密

什么是磁盘加密 磁盘加密是指在Azure中对虚拟机的磁盘进行加密保护的一种机制。它使用Azure Key Vault来保护磁盘上的数据&#xff0c;以防止未经授权的访问和数据泄露。使用磁盘加密&#xff0c;可以保护磁盘上的数据以满足安全和合规性要求。 参考文档&#xff1a;https://l…...

Java“牵手”根据关键词搜索(分类搜索)速卖通商品列表页面数据获取方法,速卖通API实现批量商品数据抓取示例

速卖通商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取速卖通商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问速卖通商城的网页来获取商品详情信息。以下是两种常用方法的介…...

商城-学习整理-高级-消息队列(十七)

目录 一、RabbitMQ简介(消息中间件)1、RabbitMQ简介&#xff1a;2、核心概念1、Message2、Publisher3、Exchange4、Queue5、Binding6、Connection7、Channel8、Consumer9、Virtual Host10、Broker 二、一些概念1、异步处理2、应用解耦3、流量控制5、概述 三、Docker安装RabbitM…...

Android Camere开发入门(1):初识Camera

Android Camere开发入门(1):初识Camera 初步了解 在Android开发中,相机(Camera)是一个常见而重要的功能模块。它允许我们通过设备的摄像头捕捉照片和录制视频,为我们的应用程序增加图像处理和视觉交互的能力。 随着Android系统的不断发展和更新,相机功能也不断改进和增…...

hive表的全关联full join用法

背景&#xff1a;实际开发中需要用到全关联的用法&#xff0c;之前没遇到过&#xff0c;现在记录一下。需求是找到两张表的并集。 全关联的解释如下&#xff1b; 下面建两张表进行测试 test_a表的数据如下 test_b表的数据如下&#xff1b; 写第一个full join 的SQL进行查询…...

PMP串讲

&#xff01;5种冲突解决策略 &#xff01;敏捷3355。 &#xff1f;PMP项目管理132种工具技术合集&#xff1a; 参考2&#xff1a;项目管理的132种工具 - 水之座 ?质量管理,有多少种图&#xff1a; ?风险管理,有多少种图&#xff1a; --参考&#xff1a;PMP相关的十八种…...

最长回文子序列——力扣516

动态规划 int longestPalindromeSubseq(string s){int n=s.length();vector<vector<int>>...

从零实现深度学习框架——Transformer从菜鸟到高手(二)

引言 &#x1f4a1;本文为&#x1f517;[从零实现深度学习框架]系列文章内部限免文章&#xff0c;更多限免文章见 &#x1f517;专栏目录。 本着“凡我不能创造的&#xff0c;我就不能理解”的思想&#xff0c;系列文章会基于纯Python和NumPy从零创建自己的类PyTorch深度学习框…...

docker监控平台FAST OS DOCKER --1

感觉这个是目前好用的中文平台&#xff0c;暂为v1吧 拉取镜像 docker pull wangbinxingkong/fast运行镜像 docker run --name fastos --restart always -p 18091:8081 -p 18092:8082 -e TZ"Asia/Shanghai" -d -v /var/run/docker.sock:/var/run/docker.sock -v /e…...

SpringBoot2.0集成WebSocket

<!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency> 新建配置类 import org.springframework.boot.autoconfigure.condition.Cond…...

Vue的Ajax请求-axios、前后端分离练习

Vue的Ajax请求 axios简介 ​ Axios&#xff0c;是Web数据交互方式&#xff0c;是一个基于promise [5]的网络请求库&#xff0c;作用于node.js和浏览器中&#xff0c;它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生node.js http模块, 而在…...

Spring源码深度解析三 (MVC)

书接上回 10.MVC 流程&源码剖析 * 问题1&#xff1a;Spring和SpringMVC整合使用时&#xff0c;会创建一个容器还是两个容器&#xff08;父子容器&#xff1f;&#xff09; * 问题2&#xff1a;DispatcherServlet初始化过程中做了什么&#xff1f; * 问题3&#xff1a;请求…...

API接口漏洞利用及防御

API是不同软件系统之间进行数据交互和通信的一种方式。API接口漏洞指的是在API的设计、开发或实现过程中存在的安全漏洞&#xff0c;可能导致恶意攻击者利用这些漏洞来获取未授权的访问、篡改数据、拒绝服务等恶意行为。 1.API接口漏洞简介 API&#xff08;Application Progr…...

解决Spring mvc + JDK17@Resource无法使用的情况

问题描述 我在使用jdk17进行Spring mvc开发时发现 Resource用不了了。 原因 因为JDK版本升级的改动&#xff0c;在Jdk9~17环境下&#xff0c;搭建Springboot项目&#xff0c;会出现原有Resource&#xff08;javax.annotation.Resource&#xff09;不存在的问题&#xff0c;导…...

页面禁用鼠标右键,禁用F12打开开发者工具!!!

文章目录 问题分析方法一方法二方法二问题 今天在浏览博主文章时发现无法复制页面上的内容,也无法F12打开开发者工具,更用不了鼠标右键,于是上网找了原因并亲测可用 分析 方法一 将 <body> 改成 <body oncontextmenu=self.event.returnValue=false>方法二 …...

Android中使用JT808协议进行车载终端通信的实现和优化

JT808是一种在中国广泛应用的车载终端通信协议&#xff0c;用于车辆与监控中心之间的数据通信。下面是关于Android平台上使用JT808协议进行通信的一般步骤和注意事项&#xff1a; 协议了解&#xff1a;首先&#xff0c;您需要详细了解JT808协议的规范和定义。该协议包含了通信消…...

导出pdf

该方法导出的pdf大小是A4纸的尺寸&#xff0c;如果大于1页需要根据元素高度进行截断的话&#xff0c;页面元素需要加 class ergodic-dom&#xff0c;方法里面会获取ergodic-dom元素&#xff0c;对元素高度和A4高度做比较&#xff0c;如果大于A4高度&#xff0c;会塞一个空白元素…...

【考研数学】线形代数第三章——向量 | 基本概念、向量组的相关性与线性表示

文章目录 引言一、向量的概念与运算1.1 基本概念1.2 向量运算的性质 二、向量组的相关性与线性表示2.1 理论背景2.2 相关性与线性表示基本概念2.3 向量组相关性与线性表示的性质 引言 向量是线性代数的重点和难点。向量是矩阵&#xff0c;同时矩阵又是由向量构成的&#xff0c…...

温故知新之:接口和抽象类有什么区别?

本文以下内容基于 JDK 8 版本。 1、接口介绍 接口是 Java 语言中的一个抽象类型&#xff0c;用于定义对象的公共行为。它的创建关键字是 interface&#xff0c;在接口的实现中可以定义方法和常量&#xff0c;其普通方法是不能有具体的代码实现的&#xff0c;而在 JDK 8 之后&…...

回归预测 | MATLAB实现SSA-RF麻雀搜索优化算法优化随机森林算法多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现SSA-RF麻雀搜索优化算法优化随机森林算法多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现SSA-RF麻雀搜索优化算法优化随机森林算法多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;…...

文旅景区vr体验馆游乐场vr项目是什么

我们知道现在很多的景区或者游玩的地方&#xff0c;以及学校、科技馆、科普馆、商场或公园或街镇&#xff0c;都会建一些关于游玩以及科普学习的项目。从而增加学习氛围或者带动人流量等等。这样的形式&#xff0c;还是有很好的效果呈现。 普乐蛙VR体验馆案例 下面是普乐蛙做的…...

Vue Element upload组件和Iview upload 组件上传文件

今天要分享的是使用这俩个UI组件库的upload组件分别实现调用组件本身的上传方法实现和后台交互。接下来就是开车的时间&#xff0c;请坐稳扶好~ 一、element upload组件传送门 1、html文件 <el-upload ref"uploadRef" :action"uploadUrl" :data"…...

无涯教程-PHP - File 函数

文件系统功能用于访问和操纵文件系统&#xff0c;PHP为您提供了操纵文件的所有功能。 运行时配置 这些功能的行为受php.ini中的设置影响。 NameDefaultChangeableChangelogallow_url_fopen"1"PHP_INI_ALLPHP_INI_ALL in PHP < 4.3.4. PHP_INI_SYSTEM in PHP &l…...

elasticsearch 常用查询 7.4 版本

Elasticsearch 常用查询 match&#xff1a;全文查询exists&#xff1a;查询存在的字段must_not&#xff1a;查询不存在的字段ids&#xff1a;跟据id查询prefix&#xff1a;前缀查询range: 查询范围term&#xff1a;精准查询terms&#xff1a;多术语查询 本文基于es 7.4版本文档…...

ChatGpt 从入门到精通

相关资源下载地址: 基于ChatGPT的国际中文语法教学辅助应用的探讨.pdf 生成式人工智能技术对教育领域的影响-关于ChatGPT的专访.pdf 电子-从ChatGPT热议看大模型潜力.pdf 从图灵测试到ChatGPT——人机对话的里程碑及启示.pdf 正文 ChatGPT 是一种强大的自然语言处理模型&…...

vscode远程调试

安装ssh 在vscode扩展插件搜索remote-ssh安装 如果连接失败&#xff0c;出现 Resolver error: Error: XHR failedscode 报错&#xff0c;可以看这篇帖子vscode ssh: Resolver error: Error: XHR failedscode错误_阿伟跑呀的博客-CSDN博客 添加好后点击左上角的加号&#xff0…...

Vue3 数据响应式原理

核心&#xff1a; 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等… 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作 const userData {name: "John",age: 12 };let proxyUser new Proxy(use…...

2023.08.20 学习周报

文章目录 摘要文献阅读1.题目2.现有问题3.解决方案4.本文贡献5.方法5.1 利用长短期记忆网络学习时空演化特征5.2 构建用于气象辅助信息编码的堆叠自编码器5.3 使用多任务学习发现全市通用模式5.4 模型 6. 实验6.1 数据集6.2 实验设置6.3 实验结果 7.结论8.展望 大气污染物传输总…...

软件测试技术之单元测试—工程师 Style 的测试方法(2)

怎么写单元测试&#xff1f; JUnit 简介 基本上每种语言和框架都有不错的单元测试框架和工具&#xff0c;例如 Java 的 JUnit、Scala 的 ScalaTest、Python的 unittest、JavaScript 的 Jest 等。上面的例子都是基于 JUnit 的&#xff0c;我们下面就简单介绍下 JUnit。 JUnit…...

项目中超图 for openlayer和超图for cesium同时引入的问题

一个项目中同时用到了超图的openlayer和cesium版本&#xff0c;首先我是外部引入的超图的开发包&#xff0c;你要是通过npm导入的那就没关系了。 <script type"text/javascript" src"/static/openlayer/supermap/ol/iclient-ol.min.js"></script&…...

3D与沉浸式技术,如何助力企业数字化转型?

说起3D&#xff0c;估计许多读者朋友会在第一时间想起《阿凡达》系列和《侏罗纪公园》系列电影大作。每一帧细节纤毫毕现的逼真画面&#xff0c;让观众几乎分不清虚拟与现实&#xff0c;完全沉浸在导演打造的视觉盛宴中。 事实上&#xff0c;除了大家所熟知的3D影视动画之外&am…...

excel vba 将多张数据表的内容合并到一张数据表

功能描述&#xff1a; 一个Excel文件有很多个 样式相同 的数据表&#xff0c; 需要将多张数据表的内容合并到一张数据表里。 vba实现代码如下&#xff1a; Attribute VB_Name "NewMacros" Option Explicit Public Const Const_OutSheetName As String "V…...

接口和抽象类的区别?解析接口和抽象类的特点和用法

接口和抽象类的区别&#xff1f;解析接口和抽象类的特点和用法 引言 在面向对象编程中&#xff0c;接口和抽象类是两个非常重要的概念。它们都可以用于定义一组相关的方法&#xff0c;但在实际使用中有一些差异。本文将探讨接口和抽象类的区别&#xff0c;并通过示例代码和测…...

vscode-vue项目格式化

一、插件要求 Prettier Vetur 二、配置文件 {"workbench.startupEditor": "newUntitledFile","files.autoSave": "off", // 关闭文件自动保存&#xff0c;避免开发时候页面变化"editor.tabSize": 2, // tab距离"ve…...