04- 基于SpringAMQP封装RabbitMQ,消息队列的Work模型和发布订阅模型

SpringAMQP

概述

使用RabbitMQ原生API在代码中设置连接MQ的参数比较繁琐,我们更希望把连接参数写在yml文件中来简化开发

SpringAMQP是基于AMQP协议定义的一套API规范,将RabbitMQ封装成一套模板用来发送和接收消息

  • AMQP(Advanced Message Queuing Portocol)是用于在应用程序之间传递业务消息的开放标准, 该协议与语言和平台无关更符合微服务中独立性的要求
  • SpringAMQP由两部分组成: spring-AMQP是基础抽象,spring-Rabbit是底层的默认实现,并且利用SpringBoot对其实现了自动装配

SpringAMQP提供的三个功能

  • 自动声明队列、交换机及其绑定关系
  • 基于注解的监听模式(监听器容器),用于异步异步处理入站消息
  • 封装了RabbitTemplate工具,用于发送消息(之前在Redis中我们也接触过RedisTemplate)
方法名功能
convertAndSend(队列名称, Object类型的消息对象)发送消息到队列
convertAndSend(交换机名称, 路由key, Object类型的消息对象)发送消息到交换机,交换机再根据路由规则发送到对应的队列
注解名功能(声明交换机和队列时也可以基于配置类的方式注入到容器中)
@RabbitListener注解声明要监听/绑定的队列
@Exchange声明交换机
@Queue声明队列
@QueueBinding声明交换机和队列的绑定关系

Work模型

Wrok模型的使用: 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理, 可以设置prefetch来控制消费者预取的消息数量

Basic Queue

第一步: 在父工程mq-demo中引入AMQP依赖,这样子模块也继承了该依赖就可以基于RabbitTemplate模板实现消息的发送和接收

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

第二步:在publisher服务的application.yml中配置MQ地址,Spring已经帮我们跟MQ建立了连接,剩下只需要指定发送什么消息到哪个队列

spring:
  rabbitmq:
    host: 192.168.88.128 # 主机名
    port: 5672 #端口
    username: root # 用户名
    password: 123456 # 密码
    virtual-host: / # 虚拟主机

第三步: 在publisher服务中编写测试类SpringAmqpTest利用RabbitTemplate发送消息到指定队列(如果没有创建simple.queue可以在RabbitMQ管理平台创建)

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        String queueName = "simple.queue";
        String message = "Hello, SpringAMQP! ";
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

第四步:在consumer服务的application.yml中同样配置MQ地址,这样Spring已经帮我们跟MQ建立了连接,剩下只需要关心要监听哪个队列以及监听到要干什么事儿

spring:
  rabbitmq:
    host: 192.168.88.128 # 主机名
    port: 5672 #端口
    username: root # 用户名
    password: 123456 # 密码
    virtual-host: / # 虚拟主机

第五步: 在consumer服务新建一个类用于编写消费逻辑,使用@Component注解将该类声明为一个Bean,使用@RabbitListener注解声明要监听/绑定的队列

@Component
public class SpringRabbitListener {
    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueueMessage(String msg) {
        // msg参数就是接收到的消息,Spring已经帮我们二进制数据转成了字符串
        System.out.println("Spring 消费者接收到消息:【" + msg + "】");
    }
}

第六步: 启动Consumer服务查看控制台接收到的消息,如果多次使用publisher服务发送消息consumer服务也会接收多次消息

  • 消息一旦被消费就会从队列删除,RabbitMQ没有消息回溯功能
Spring 消费者接收到消息:【Hello, SpringAMQP! 】

Wrok Queue(Task queues)

Work Queue也被称为任务模型,简单来说就是让多个消费者绑定到一个队列让它们共同处理队列中的信息提高消息的处理速度(同一条消息只会被一个消费者处理)

  • 如果消息处理比较耗时那么生产消息的速度会远远大于消息的消费速度,就会导致消息堆积的越来越多无法及时处理

在这里插入图片描述

第一步: 在publisher服务中的SpringAmqpTest类测试方法中循环发送消息模拟大量消息堆积的场景

@Test
public void testWorkQueue() throws InterruptedException {
    String queueName = "simple.queue";
    String message = "Hello, SpringAMQP! __ ";
    for (int i = 1; i <= 50; i++) {
        // 循环发送50条消息,带上消息编号
        rabbitTemplate.convertAndSend(queueName, message + i);
        // 休眠20ms,模拟在1s内发送完
        Thread.sleep(20);
    }
}

第二步: 在consumer服务的SpringRabbitListener类中添加两个方法模拟多个消费者绑定同一个队列(一个方法对应一个消费者)

@RabbitListener(queues = "simple.queue")
public void listenWorkQueueMessage1(String msg) throws InterruptedException {
    System.out.println("消费者1 接收到消息:【" + msg + "】" + LocalDateTime.now());
    // 休眠20ms,1s大致能处理50条消息
    Thread.sleep(20);
}

@RabbitListener(queues = "simple.queue")
public void listenWorkQueueMessage2(String msg) throws InterruptedException {
    System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalDateTime.now());
    // 休眠200ms,1s大概能处理5条消息
    Thread.sleep(200);
}

第三步: 执行publisher服务中刚编写的测试方法发送50条消息,启动consumer服务查看控制台输出

  • 消费者1很快就完成了自己的25条消息,消费者2却在缓慢的处理自己的25条消息,即当前的处理方式是平均分配给每个消费者
消费者2........接收到消息:【Hello, SpringAMQP! __ 1】2022-12-23T13:16:41.407
消费者1 接收到消息:【Hello, SpringAMQP! __ 2】2022-12-23T13:16:41.414
消费者1 接收到消息:【Hello, SpringAMQP! __ 4】2022-12-23T13:16:41.461
消费者1 接收到消息:【Hello, SpringAMQP! __ 6】2022-12-23T13:16:41.502
消费者1 接收到消息:【Hello, SpringAMQP! __ 8】2022-12-23T13:16:41.549
消费者1 接收到消息:【Hello, SpringAMQP! __ 10】2022-12-23T13:16:41.592
消费者2........接收到消息:【Hello, SpringAMQP! __ 3】2022-12-23T13:16:41.609
消费者1 接收到消息:【Hello, SpringAMQP! __ 12】2022-12-23T13:16:41.635
消费者1 接收到消息:【Hello, SpringAMQP! __ 14】2022-12-23T13:16:41.680
消费者1 接收到消息:【Hello, SpringAMQP! __ 16】2022-12-23T13:16:41.722
消费者1 接收到消息:【Hello, SpringAMQP! __ 18】2022-12-23T13:16:41.767
....
消费者2........接收到消息:【Hello, SpringAMQP! __ 35】2022-12-23T13:16:44.837
消费者2........接收到消息:【Hello, SpringAMQP! __ 37】2022-12-23T13:16:45.040
消费者2........接收到消息:【Hello, SpringAMQP! __ 39】2022-12-23T13:16:45.240
消费者2........接收到消息:【Hello, SpringAMQP! __ 41】2022-12-23T13:16:45.444
消费者2........接收到消息:【Hello, SpringAMQP! __ 43】2022-12-23T13:16:45.646
消费者2........接收到消息:【Hello, SpringAMQP! __ 45】2022-12-23T13:16:45.846
消费者2........接收到消息:【Hello, SpringAMQP! __ 47】2022-12-23T13:16:46.048
消费者2........接收到消息:【Hello, SpringAMQP! __ 49】2022-12-23T13:16:46.250

我们希望按照服务器的处理能力来处理消息,避免出现消息积压的风险,在consumer服务中的application.yml文件添加prefetch配置控制预取消息的上限

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

发布订阅模型

在订阅模型中多了一个exchange角色,发送消息的流程也发生了变化

  • Publisher(生产者): 消息不再发送到队列中而是发送给exchange(交换机)
  • Exchange(交换机): 一方面负责接收生产者发送的消息,另一方面负责将消息按照规则路由到与之绑定的队列(如递交给某个特定队列、递交给所有队列)
  • Consumer(消费者): 订阅队列的消息
  • Queue(消息队列): 接收消息并缓存消息

交换机如何处理接收到的消息取决于Exchange的类型

  • Fanout(广播): 将消息交给所有绑定到交换机的队列
  • Direct(定向): 把消息交给符合指定routing key的队列
  • Topic(通配符): 把消息交给符合routing pattern(路由模式)的队列
  • 交换机只负责转发消息不具备存储消息的能力,因此如果没有任何队列与Exchange绑定或者没有符合路由规则的队列,那么消息会丢失

Spring提供了一个接口Exchange来表示所有不同类型的交换机(路由规则不同),Queue和Binding也是Springframework提供的类
在这里插入图片描述

Fanout(广播)

在广播模式下消息发送的流程

  • 广播模式下可以声明多个队列,但每个队列都要绑定一个Exchange(交换机)
  • 生产者发送的消息只能发送到交换机,由交换机决定要发给哪个队列
  • FanoutExchange类型的交换机会把消息发送给每一个跟其绑定的队列,然后订阅队列的消费者都能拿到同一条消息

在这里插入图片描述

第一步: 在consumer服务创建一个配置类config/FanoutConfig用来声明队列(Queue)和交换机(FanoutExchange)以及队列和交换机的绑定关系(Binding)

@Configuration
public class FanoutConfig {
    /**
     * 声明交换机
     * @return Fanout类型交换机
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("itcast.fanout");
    }

    /**
     * 第1个队列,方法的名称就是bean的名称
     */
    @Bean
    public Queue fanoutQueue1() {
        return new Queue("fanout.queue1");
    }

    /**
     * 绑定第1个队列和交换机,根据方法形参的名称自动注入容器中对应的bean(队列和交换机)
     */
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    /**
     * 第2个队列
     */
    @Bean
    Queue fanoutQueue2() {
        return new Queue("fanout.queue2");
    }

    /**
     * 绑定第2个队列和交换机,根据方法形参的名称中自动注入容器中对应的bean(队列和交换机)
     */
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}

第二步: 在publisher服务的SpringAmqpTest类中添加测试方法向交换机发送一次消息

@Test
public void testFanoutExchange() {
    String exchangeName = "itcast.fanout";
    String message = "Hello Everyone~";
    // 交换机名称,路由key,消息
    rabbitTemplate.convertAndSend(exchangeName, "", message);
}

第三步: 在consumer服务的SpringRabbitListener中添加两个方法作为两个消费者分别绑定fanout.queue1fanout.queue2接收消息

@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
    System.out.println("消费者1收到广播消息:【" + msg + "】");
}

@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
    System.out.println("消费者2收到广播消息:【" + msg + "】");
}

第四步: 重启consumer服务然后运行publisher中新编写的测试方法查看控制台输出

消费者1收到广播消息:【Hello Everyone~】
消费者2收到广播消息:【Hello Everyone~】

Direct(基于注解方式声明)

在Fanout广播模式中一条消息会发给所有与交换机绑定队列,但是在某些场景下我们更希望不同的消息发送给不同的队列,这时就要用到Direct类型的Exchange

Direct类型的Exchange不再把接收的消息交给每一个与其绑定的队列,只有当队列的BindingKey消息的RoutingKey完全一致时该队列才会收到消息

  • 每一个队列与虚拟机绑定时需要指定一个BindingKey(路由key)
  • 发布者向Exchange发送消息时也必须指定消息的RoutingKey(路由规则)
  • Exchange将消息路由到BindingKey和消息的RoutingKey一致的队列

在这里插入图片描述

Direct交换机与Fanout交换机的差异

  • Fanout交换机会将消息路由给每一个与之绑定的队列
  • Direct交换机根据RoutingKey判断路由给哪个队列,如果多个队列具有相同的RoutingKey,则与Fanout功能类似,也是把消息路由给每一个匹配的队列

第一步: 在consumer服务SpringRabbitListener类中添加两个消费者监听direct.queue1和direct.queue2同时基于RabbitListener注解来声明队列和交换机

  • 由于基于Bean的方式声明队列与交换机比较麻烦所以Spring还提供了基于注解的方式来声明Exchange(@Exchange),Queue(@Queue),Binding(@QueueBinding)
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "direct.queue1"),
        exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),// 默认type = "direct"
        key = {"red", "blue"}// key就是队列和交换机的BindingKey
))
public void listenDirectQueue1(String msg) {
    System.out.println("消费者收到direct.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "direct.queue2"),
        exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),
        key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg) {
    System.out.println("消费者收到direct.queue2的消息:【" + msg + "】");
}

第二步: 在publisher中编写三个测试方法向directExchange发送三条消息并指定消息的RoutingKey

@Test
public void testDirectExchange() {
    String exchangeName = "directExchange";
    String message = "hello,blue";
    rabbitTemplate.convertAndSend(exchangeName, "blue", message);
}

@Test
public void testDirectExchange() {
    String exchangeName = "directExchange";
    String message = "hello,yellow";
    rabbitTemplate.convertAndSend(exchangeName, "yellow", message);
}

@Test
public void testDirectExchange() {
    String exchangeName = "directExchange";
    String message = "hello,red";
    rabbitTemplate.convertAndSend(exchangeName, "red", message);
}

第三步: 重启consumer服务运行以上三个测试方法查看控制台输出

消费者收到direct.queue1的消息:hello,blue
消费者收到direct.queue2的消息:hello,yellow
消费者收到direct.queue1的消息:hello,red
消费者收到direct.queue2的消息:hello,red

Topic(使用通配符)

Topic类型和Direct类型的Exchange都可以根据RoutingKey把消息路由到不同的队列,只不过Topic类型的Exchange与队列绑定时指定的BindingKey可以使用通配符

  • #: 匹配一个或多个词,如item.#能够匹配item.kyle.violet或者item.kyle
  • *: 仅匹配一个词,如item.*只能匹配item.kyle或者item.violet

Direct交换机与Topic交换机的差异

  • Topic交换机接收的消息Routing Key必须是多个单词,多个单词间以.分割
  • Topic交换机与队列绑定时的Binding key可以指定通配符,其中#表示0个或多个单词,*仅表示1个单词

在这里插入图片描述

第一步: 在publisher服务的SpringAmqpTest类中添加测试方法向交换机发送不同RoutingKey的消息

@Test
public void testTopicExchange() {
    String exchangeName = "topic";
    String message = "如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?";
    rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}

@Test
public void testTopicExchange() {
    String exchangeName = "topic";
    String message = "今 天 也 是 个 emo 的 好 天 气";
    rabbitTemplate.convertAndSend(exchangeName, "china.weather", message);
}

@Test
public void testTopicExchange() {
    String exchangeName = "topic";
    String message = "自由美利坚,枪击每一天";
    rabbitTemplate.convertAndSend(exchangeName, "us.news", message);
}

第二步: 在consumer服务的SpringRabbitListener类中消费方法监听topic.queue1和topic.queue2, 利用@RabbitListener相关的注解声明Exchange,Queue,BindingKey

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "topic.queue1"),
        exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),
        key = "china.#"
))
public void listenTopicQueue1(String msg) {
    System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "topic.queue2"),
        exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),
        key = "#.news"
))
public void listenTopicQueue2(String msg) {
    System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}
消费者接收到topic.queue2的消息:【如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?】
消费者接收到topic.queue1的消息:【如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?】
消费者接收到topic.queue1的消息:【今 天 也 是 个 emo 的 好 天 气】
消费者接收到topic.queue2的消息:【自由美利坚,枪击每一天】

消息转换器

Spring的convertAndSend方法接收消息的类型是Object也就是说它可以把任意对象类型的消息序列化为字节发送给MQ(默认采用JDK的序列化方式)

第一步: 在config.FanoutConfig中声明一个队列

@Bean
public Queue objectQueue(){
    return new Queue("object.queue");
}

第二步: 在测试方法中发送一个Map类型的对象消息到MQ

@Test
public void testSimpleQueue() {
    String queueName = "simple.queue";
    HashMap<String, Object> hashMap = new HashMap<>();
    hashMap.put("名称", "艾尔登法环");
    hashMap.put("价格", 299);
    rabbitTemplate.convertAndSend(queueName, hashMap);
}

JDK序列化的方式存在数据体积过大,有安全漏洞,可读性差的问题,我们更希望消息的体积更小可读性更高,因此可以使用JSON方式来做序列化和反序列化

在这里插入图片描述

第一步: 在publisher和consumer两个服务中引入依赖(或者直接在父工程mq-demo中引入依赖)

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

第二步: 在publisher和consumer的启动类中都添加jsonMessageConverter并注册到容器中

@Bean
publis MessageConverter jsonMessageConverter() {
    return new Jackson2JsonMessageConverter();
}

第三步: 再次执行测试方法发送一个Map类型的对象消息到MQ,修改consumer服务的SpringRabbitListener添加方法并重启服务

  • consumerpublisher的序列化器需保持一致,同时consumer中接收数据的类型也需要和发送数据的类型保持一致,如HashMap<String, Object>
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(HashMap<String, Object> msg) throws InterruptedException {
    System.out.println("消费者接收到消息:【" + msg + "】");
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【危化品泄漏源定位】基于改进哈里斯鹰优化算法的危化品泄漏源定位算法 溯源定位算法【Matlab代码#63】

文章目录 【获取资源请见文章第7节&#xff1a;资源获取】1. 算法概述2. 原始哈里斯鹰算法&#xff08;HHO&#xff09;3. 改进哈里斯鹰算法&#xff08;IHHO&#xff09;3.1 动态自适应逃逸能量3.2 动态扰动策略 4. 构建源强和位置反算模型5. 部分代码展示6. 仿真结果展示7. 资…

牛-迈面试题----答案/类似题/知识点

来源在这里 来源在这里 1.Redis的优势 (1) 速度快&#xff0c;因为数据存在内存中&#xff0c;类似于HashMap&#xff0c;HashMap的优势就是查找和操作的时间复杂度都很低 (2)支持丰富数据类型&#xff0c;支持string&#xff0c;list&#xff0c;set&#xff0c;sorted set&…

Linux之线程互斥

目录 一、问题引入 二、线程互斥 1、相关概念 2、加锁保护 1、静态分配 2、动态分配 3、锁的原理 4、死锁 三、可重入与线程安全 1、概念 2、常见的线程不安全的情况 3、常见的线程安全的情况 4、常见不可重入的情况 5、常见可重入的情况 6、可重入与线程安全联系…

idea 导入项目

idea 导入项目并运行 导入设置设置 jdk查看maven 设置 导入 在项目首页 或者 file 选择 open, 然后选择项目根路径 设置 设置 jdk 查看maven 设置

Linux基础命令[18]-whoami

文章目录 1. whoami 命令说明2. whoami 命令语法3. whoami 命令示例4. 总结 1. whoami 命令说明 whoami&#xff1a;用于显示当前用户名&#xff0c;功能与 id -un 相同。基本信息如下&#xff1a; Usage: whoami [OPTION]... Print the user name associated with the curre…

数码管动态扫描显示

摸鱼记录 Day_16 (&#xff9f;O&#xff9f;) review 前边已经学习了&#xff1a; 串口接收&#xff1a;Vivado 串口接收优化-CSDN博客 1. 今日摸鱼任务 串口接收数据 并用数码管显示 (&#xff9f;O&#xff9f;) 小梅哥视频&#xff1a; 17A 数码管段码显示与动态扫…

ES6(一):let和const、模板字符串、函数默认值、剩余参数、扩展运算符、箭头函数

一、let和const声明变量 1.let没有变量提升&#xff0c;把let放下面打印不出来&#xff0c;放上面可以 <script>console.log(a);let a1;</script> 2.let是一个块级作用域,花括号里面声明的变量外面找不到 <script>console.log(b);if(true){let b1;}//und…

matlab 基操~

MATLAB基本操作 1. 对象定义 使用sym定义单个对象、使用syms定义多个对象 2. 使用limit求极限 $$ \lim_{v \rightarrow a} f(x) $$ limit(f,v,a) % 使用limit(f,v,a,left)可求左极限 3. 导数 使用diff(f,v,n)对$ f(v)v^{t-1} $求 $ n $ 阶导 $ \frac{d^nf}{d^nv} $&#xf…

【蓝桥杯-单片机】基础模块:数码管

文章目录 【蓝桥杯-单片机】基础模块&#xff1a;数码管01 数码管原理图什么是位选和段选共阳极数码管和共阴极数码管的区分&#xff08;1&#xff09;共阳极数码管&#xff08;Common Anode&#xff09;&#xff1a;&#xff08;2&#xff09;共阴极数码管&#xff08;Common …

C语言中大小写字母如何转化

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【Miniconda】基于conda列出当前环境下所有已创建的虚拟环境

【Miniconda】基于conda列出当前环境下所有已创建的虚拟环境 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的…

MS08-067 漏洞利用与安全加固

文章目录 环境说明1 MS08_067 简介2 MS08_067 复现过程3 MS08_067 安全加固 环境说明 渗透机操作系统&#xff1a;2024.1漏洞复现操作系统: Windows XP Professional with Service Pack 2- VL (English)安全加固复现操作系统&#xff1a;Windows XP Professional with Service …

Windows系统搭建Cloudreve结合内网穿透打造可公网访问的私有云盘

目录 ⛳️推荐 1、前言 2、本地网站搭建 2.1 环境使用 2.2 支持组件选择 2.3 网页安装 2.4 测试和使用 2.5 问题解决 3、本地网页发布 3.1 cpolar云端设置 3.2 cpolar本地设置 4、公网访问测试 5、结语 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff…

BUGKU-WEB shell

题目描述 题目截图如下&#xff1a; 描述&#xff1a; $poc "a#s#s#e#r#t";$poc_1 explode("#", $poc);$poc_2 $poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5];$poc_2($_GET[s])进入场景看看&#xff1a;是一个空白的界面 解题思路 …

HTML 学习笔记(十)块和内联

每个HTML元素都有一个默认的显示值&#xff0c;显示值又可以再分为block(块)和inline(内联) 一、块元素 通过F12进入浏览器开发者模式查看该元素会发现其所占宽度为整个网页的宽度 1.div标签 通过div标签将一些元素装进"盒子"&#xff0c;从而对盒子中的全部元素…

OPTIONS请求(跨域预检查)

目录 一、什么是OPTIONS请求&#xff1f;二、简单请求、复杂请求三、特定的请求头、响应头 一、什么是OPTIONS请求&#xff1f; OPTIONS 请求方式是 HTTP 协议中的一种&#xff0c;主要用于 从响应头中获取服务器支持的HTTP请求方式。 OPTIONS 请求方式是 浏览级行为&#xf…

Hubspot 2023年推荐使用的11个AI视频生成器

视频是任何营销活动不可或缺的一部分&#xff1b;然而&#xff0c;如果你不懂编辑或时间紧迫&#xff0c;它们可能会很乏味&#xff0c;很难创建。一只手从电脑里伸出来&#xff0c;拳头碰到另一只手&#xff1b;代表AI视频生成器。 幸运的是&#xff0c;你可以利用许多人工智能…

市场复盘总结 20240314

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 25% 最常用的…

vue 引用百度地图

address.vue <template><div><!-- 地图 --><el-drawer:visible.sync"type1"direction"rtl"size"50%"append-to-bodyclass"map-drawer":before-close"beforeClose"><div style"width: 100%…

Twitter广告投放技巧

明确目标受众&#xff1a; 确定你的目标受众&#xff0c;包括他们的兴趣、地理位置、年龄等。Twitter提供了广告定位选项&#xff0c;确保你的广告被展示给最相关的用户。 使用吸引人的图像和视频&#xff1a; 在Twitter上&#xff0c;图像和视频是引起关注的关键。确保你的广…