003 Springboot操作RabbitMQ

Springboot整合RabbitMQ

文章目录

    • Springboot整合RabbitMQ
      • 1.pom依赖
      • 2.yml配置
      • 3.配置队列、交换机
        • 方式一:直接通过配置类配置bean
        • 方式二:消息监听通过注解配置
      • 4.编写消息监听发送测试
      • 5.其他类型交换机配置
        • 1.FanoutExchange
        • 2.TopicExchange
        • 3.HeadersExchange
      • 6.延迟消息处理(TTL)
        • 方式一:ttl配置
        • 方式二:消息发送设置
      • 7.死信队列

1.pom依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>

    </dependencies>

2.yml配置

#配置使用的配置文件
spring:
  #配置rabbitmq
  rabbitmq:
    host: 47.122.26.28 #主机地址
    port: 5672  #端口号
    username: xxx #用户名
    password: xxx #密码
    virtual-host: my_vhost  #虚拟主机地址
    #开启消息送达提示
    publisher-returns: true
    # springboot.rabbitmq.publisher-confirm 新版本已被弃用,现在使用 spring.rabbitmq.publisher-confirm-type = correlated 实现相同效果
    publisher-confirm-type: correlated
    listener:  #消息监听配置
      type: simple
      simple:
        acknowledge-mode: manual #manual手动确认消息  auto没有异常时 进行自动确认 (异常类型 消息重新入队)
        prefetch: 1 #限制每次发送一条数据。
        concurrency: 3 #同一个队列启动几个消费者
        max-concurrency: 3 #启动消费者最大数量
        #重试策略相关配置
        retry:
          # 开启消费者(程序出现异常)重试机制,默认开启并一直重试
          enabled: true
          # 最大重试次数
          max-attempts: 5
          # 重试间隔时间(毫秒)
          initial-interval: 3000


server:
  port: 18082
  address: 127.0.0.1
  servlet:
    context-path: /

3.配置队列、交换机

方式一:直接通过配置类配置bean

推送消息时不存在创建队列和交换机

/**
 * direct模式声明配置
 */
@Configuration
public class RabbitDirectConfig {

    public static final String EXCHANGE_NAME="direct-exchange";
    public static final String QUEUE_NAME="direct-queue";
    public static final String BINDING_KEY="change:direct";

    /**
     * 声明直连交换机
     * name:交换机的名称
     * durable 队列是否持久化
     * autoDelete:是否自动删除,(当该交换机上绑定的最后一个队列解除绑定后,该交换机自动删除)
     * argument:其他一些参数
     */
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(EXCHANGE_NAME,false,false,null);
    }

    /**
     * 声明队列
     *  queue 队列的名称
     *  durable 队列是否持久化
     *  exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
     *  autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
     *  arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
     */
    @Bean
    public Queue directQueue() {
        return new Queue(QUEUE_NAME,false,false,false,null);
    }

    /**
     * 交换机队列绑定
     */
    @Bean
    public Binding springExchangeBindSpringQueue() {
        return BindingBuilder.bind(directQueue()).to(directExchange()).with(BINDING_KEY);
    }

}

方式二:消息监听通过注解配置

启动时创建队列和交换机

    @RabbitListener(
            bindings = @QueueBinding(
                value = @Queue(value = "direct1-queue",durable = "true"),
                exchange = @Exchange(value = "direct1-exchange",type = ExchangeTypes.DIRECT,durable = "true"),
                key = "change1:direct"))

注意:rabbitmq同名的队列只能创建一个,创建多个会报错,推送消息时需确保队列和交换机已存在,

方式一队列和交换机在第一次推送消息时才会自动创建队列和交换机,方式二注解在启动时就会创建

4.编写消息监听发送测试

监听

@Slf4j
@Component
public class RabbitMQListener {


    @RabbitListener(queues = "direct-queue")
    @RabbitHandler
    public void bootMsg(Channel channel, Message message){
        String message1 = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println(" direct 消费者:'" + message1 + "'");
        //手动确认该消息
        try {
            //消息确认,根据消息序号(false只确认当前一个消息收到,true确认所有比当前序号小的消息(成功消费,消息从队列中删除 ))
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        } catch (IOException e) {
            log.error("执行异常",e);
             // 拒绝消息并重新入队
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); 
        }
    }

    
    @RabbitListener(
            bindings = @QueueBinding(
                value = @Queue(value = "direct1-queue",durable = "true"),
                exchange = @Exchange(value = "direct1-exchange",type = ExchangeTypes.DIRECT,durable = "true"),
                key = "change1:direct"))
    @RabbitHandler
    public void bootMsg1(Channel channel, Message message){
        String message1 = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println(" direct 消费者:'" + message1 + "'");
        //手动确认该消息
        try {
            //消息确认,根据消息序号(false只确认当前一个消息收到,true确认所有比当前序号小的消息(成功消费,消息从队列中删除 ))
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        } catch (IOException e) {
            log.error("执行异常",e);
             // 拒绝消息并重新入队
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }

}

测试

@Slf4j
@SpringBootTest(classes = RabbitProviderApplication.class)
public class RabbitTest {

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Test
    public void directProvider(){
        String message = "direct模式消息推送。。。。。";
        /**
         * 参数分别为,交换机,路由key,消息体
         */
        amqpTemplate.convertAndSend("direct-exchange","change:direct",message);
        System.out.println(" 消息发送 :'" +message + "'");
    }


    @Test
    public void directProvider1(){
        String message = "direct模式消息推送1。。。。。";
        /**
         * 参数分别为,交换机,路由key,消息体
         */
        amqpTemplate.convertAndSend("direct1-exchange","change1:direct",message);
        System.out.println(" 消息发送1 :'" +message + "'");
    }

}

在这里插入图片描述

5.其他类型交换机配置

1.FanoutExchange
/**
 * fanout模式声明配置
 */
@Configuration
public class RabbitFanoutConfig {

    public static final String EXCHANGE_NAME="fanout-exchange";
    public static final String QUEUE_NAME1="fanout-queue1";
    public static final String QUEUE_NAME2="fanout-queue2";

    /**
     * 声明交换机
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(EXCHANGE_NAME,false,false,null);
    }

    /**
     * 声明队列
     */
    @Bean
    public Queue fanoutQueue1() {
        return new Queue(QUEUE_NAME1,false,false,false,null);
    }
    @Bean
    public Queue fanoutQueue2() {
        return new Queue(QUEUE_NAME2,false,false,false,null);
    }

    /**
     * 交换机队列绑定
     */
    @Bean
    public Binding springExchangeBindQueue1() {
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
    }

    /**
     * 交换机队列绑定
     */
    @Bean
    public Binding springExchangeBindQueue2() {
        return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
    }

}

监听

    @RabbitListener(queues = "fanout-queue1")
    public void fanoutMsg1(Channel channel, Message message) {
        String message1 = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println(" fanout-queue1 消费者:'" + message1 + "'");
    }

    @RabbitListener(queues = "fanout-queue2")
    public void fanoutMsg2(Channel channel, Message message) {
        String message1 = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println(" fanout-queue2 消费者:'" + message1 + "'");
    }

测试

    @Test
    public void fanoutProvider(){
        String message = "fanout模式消息推送。。。。。";
        amqpTemplate.convertAndSend("fanout-exchange", "",message);
        System.out.println(" 消息发送 :'" +message + "'");
    }

在这里插入图片描述

2.TopicExchange
/**
 * topic模式声明配置
 */
@Configuration
public class RabbitTopicConfig {

    public static final String EXCHANGE_NAME="topic-exchange";
    public static final String QUEUE_NAME="topic-queue";

    public static final String BINDING_KEY="*.orange.#";

    /**
     * 声明交换机
     */
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(EXCHANGE_NAME,false,false,null);
    }

    /**
     * 声明队列
     */
    @Bean
    public Queue topicQueue() {
        return new Queue(QUEUE_NAME,false,false,false,null);
    }


    /**
     * 交换机队列绑定
     */
    @Bean
    public Binding topicExchangeBindQueue() {
        return BindingBuilder.bind(topicQueue()).to(topicExchange()).with(BINDING_KEY);
    }

}

    @RabbitListener(queues = "topic-queue")
    public void topicMsg2(Channel channel, Message message) {
        String message1 = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println(" topic-queue2 消费者:'" + message1 + "'");
    }

测试

    @Test
    public void topicProvider(){
        String message1 = "topic test模式消息推送。。。。。";
        String message2 = "topic test.aaa模式消息推送。。。。。";
        amqpTemplate.convertAndSend("topic-exchange", "com.orange.test",message1);
        amqpTemplate.convertAndSend("topic-exchange", "com.orange.test.aaa",message2);
        System.out.println(" 消息发送");
    }

在这里插入图片描述

3.HeadersExchange
/**
 * headers模式声明配置
 * 与路由key无关,只需要消息的头参数匹配即可
 * x-match参数代表是全部匹配还是部分匹配
 */
@Configuration
public class RabbitHeadersConfig {

    public static final String EXCHANGE_NAME="headers-exchange";
    public static final String QUEUE_NAME="headers-queue";
    public static final String QUEUE_NAME1="headers-queue1";

    /**
     * 声明交换机
     */
    @Bean
    public HeadersExchange headersExchange() {
        return new HeadersExchange(EXCHANGE_NAME,false,false,null);
    }

    /**
     * 声明队列
     */
    @Bean
    public Queue headersQueue() {
        return new Queue(QUEUE_NAME,false,false,false,null);
    }
    @Bean
    public Queue headersQueue2() {
        return new Queue(QUEUE_NAME1,false,false,false,null);
    }

    /**
     * 交换机队列绑定(任意匹配)
     * whereAny 等同于x-match = any
     */
    @Bean
    public Binding headersExchangeBindSpringQueue() {
        HashMap<String, Object> header = new HashMap<>();
        header.put("test", "111");
        header.put("test1", "222");
        return BindingBuilder.bind(headersQueue()).to(headersExchange()).whereAny(header).match();
    }

    /**
     * 交换机队列绑定(全部匹配)
     * whereAny 等同于x-match = all
     */
    @Bean
    public Binding headersExchangeBindSpringQueue1() {
        HashMap<String, Object> header = new HashMap<>();
        header.put("test", "111");
        header.put("test1", "222");
        return BindingBuilder.bind(headersQueue2()).to(headersExchange()).whereAll(header).match();
    }

}

发送测试

  @Test
    public void headerProvider(){
        String param = "headers 模式消息推送。。。。。";
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text/plain");
        messageProperties.setContentEncoding("utf-8");
        messageProperties.setHeader("test","111");
        Message message = new Message(param.getBytes(), messageProperties);
        amqpTemplate.convertAndSend("headers-exchange", null,message);
        System.out.println(" 消息发送");
    }

在这里插入图片描述

队列queue任意匹配有数据,queue1全部匹配无数据

headers-queue

在这里插入图片描述

headers-queue1

在这里插入图片描述

消息监听

    @RabbitListener(queues = "headers-queue")
    public void headersMsg2(Channel channel, Message message) {
        String message1 = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println(" headers-queue 消费者:'" + message1 + "'");
    }


    @RabbitListener(queues = "headers-queue1")
    public void headers1Msg2(Channel channel, Message message) {
        String message1 = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println(" headers-queue1 消费者:'" + message1 + "'");
    }

在这里插入图片描述

6.延迟消息处理(TTL)

  • 第一种是使用普通队列和死信队列来模拟实现延迟的效果。将消息放入一个没有被监听的队列上,设置TTL(一条消息的最大存活时间)为延迟的时间,时间到了没有被消费,直接成为死信,进入死信队列。后监听私信队列来消息消费

  • 第二种是使用rabbitmq官方提供的delayed插件来真正实现延迟队列。

方式一:ttl配置

超时自动删除

/**
 * rabbitmq的ttl延迟过期时间配置
 */
@Configuration
public class RabbitMQTTLConfig {

    /**
     * 声明交换机
     * @return
     */
    @Bean
    public DirectExchange ttlDirectExchange(){
        return new DirectExchange("ttl-direct-exchange");
    }

    /**
     * 声明队列
     * @return
     */
    @Bean
    public Queue ttlQueue(){
        //设置参数
        Map<String,Object> args = new HashMap<>();
        //设置ttl过期时间,需设置int值
        args.put("x-message-ttl",5000);
        return new Queue("ttl-direct-queue",true,false,false,args);
    }

    /**
     * 绑定队列
     * @return
     */
    @Bean
    public Binding ttlBingQueue(){
        return BindingBuilder.bind(ttlQueue()).to(ttlDirectExchange()).with("direct:ttl:key");
    }

}

测试

    @Test
    public void ttlSendMessageTest(){
        String exchange = "ttl-direct-exchange";
        String routingKey = "direct:ttl:key";
        String msg = UUID.randomUUID().toString();
        //发送并设置
        amqpTemplate.convertAndSend(exchange,routingKey,msg);
        System.out.println("消息发送成功====="+msg);
    }

在这里插入图片描述

方式二:消息发送设置

注释掉x-message-ttl参数,使用普通队列,发送消息时设置过期时间

    @Test
    public void ttlSendMessageTest(){
        String exchange = "ttl-direct-exchange";
        String routingKey = "direct:ttl:key";
        String msg = UUID.randomUUID().toString();
        //设置过期时间
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("5000");
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            }
        };
        //发送并设置
        amqpTemplate.convertAndSend(exchange,routingKey,msg,messagePostProcessor);
        System.out.println("消息发送成功====="+msg);
    }

在这里插入图片描述

注意:如果项目中即使用了ttl配置过期时间,有设置了消息过期时间,则执行时以最小的时间为准,ttl过期队列的消息过期会写到死信,而设置方式的普通队列则不会自动写到死信队列

7.死信队列

死信的情况:消息被拒绝,消息过期,队列达到最大长度

死信队列声明

@Configuration
public class RabbitMQDLXConfig {

    /**
     * 声明死信交换机
     * @return
     */
    @Bean
    public DirectExchange dlxDirectExchange(){
        return new DirectExchange("dlx-direct-exchange");
    }

    /**
     * 声明死信队列
     * @return
     */
    @Bean
    public Queue dlxQueue(){ ;
        return new Queue("dlx-direct-queue",true);
    }

    /**
     * 绑定队列
     * @return
     */
    @Bean
    public Binding dlxBingQueue(){
        return BindingBuilder.bind(dlxQueue()).to(dlxDirectExchange()).with("direct:dlx:key");
    }

}

过期推送到死信设置

   /**
     * 声明ttl队列
     * @return
     */
    @Bean
    public Queue ttlQueue(){
        //设置参数
        Map<String,Object> args = new HashMap<>();
        //设置ttl过期时间,需设置int值
        args.put("x-message-ttl",5000);
        args.put("x-max-length",5);//最大长度
        //消息过期死信队列入队配置
        args.put("x-dead-letter-exchange","dlx-direct-exchange");//设置死信交换机
        args.put("x-dead-letter-routing-key","direct:dlx:key");//死信路由key,fanout模式不需要设置路由key
        return new Queue("ttl-direct-queue",true,false,false,args);
    }

注意:队列参数修改后,不会重新创建覆盖而是会报错,需要手动删除重新创建,生产环境中则可以通过重新创建一个队列,进行转移

测试

在这里插入图片描述

消息过期进死信队列
在这里插入图片描述

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

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

相关文章

【AIGC】寻找ChatGPT最佳推理步骤:CoT思维链技术的探索与应用

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;CoT思维链概述&#x1f4af;CoT思维链在大型语言模型中的应用&#x1f4af;CoT思维链改变对模型推理能力的理解和改进方式多样化应用场景挑战与未来发展总结 &#x1f4a…

力扣 前缀和

找子数组的个数了解前缀和的基础。 前缀和大致理解为到达某个位置&#xff0c;前面几个数的总和&#xff0c;即s[i1]s[i]a[i1]&#xff0c;可以通过一次循环获得。然后几个前缀和作差&#xff0c;即可得到某个位置到某个位置的和&#xff0c;根据map的键值对进行更新次数。 题…

【JavaEE】——回显服务器的实现

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;引入 1&#xff1a;基本概念 二&#xff1a;UDP socket API使用 1&#xff1a;socke…

高频股票期货ETF历史高频数据源

【数据源】 银河金融数据库&#xff08;yinhedata.com&#xff09; 提供金融数据股票、美股、期货以及ETF等高频tick数据&#xff0c;分钟级别数据。 MACD背离是指MACD指标与价格走势之间发生的方向性差异&#xff0c;这通常被视为市场可能发生趋势反转的信号。以下是一个具体…

petalinux 自动登陆 自动启动程序

PetaLinux 自动登陆 (1) cd 到项目工程目录下&#xff1b; (2) 运行命令&#xff1a;petalinux-config -c rootfs (3) 依次选择 Image Features -> serial-autologin-root 保存退出 创建APP petalinux-create apps --template install --name init-app --enable编辑文件 …

【linux】冯诺依曼架构

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;linux笔记仓 目录 01.冯诺依曼体系结构02.操作系统&#xff08;Operator System&#xff09;如何理解“管理”操作系统中实现“管理的先描述再组织” 03.系统调用与库函数系统调用库函数 01.冯诺依…

Win10 IDEA连接虚拟机中的Hadoop(HDFS)

获取虚拟机的ip 虚拟机终端输入 ip a关闭虚拟机防火墙 sudo ufw disable修改Hadoop的core-site.xml文件 将localhost修改为虚拟机局域网IP # 位置可能不一样&#xff0c;和Hadoop安装位置有关 cd /usr/local/hadoop/etc/hadoop vim core-site.xmlIDEA 连接 创建Maven项目…

【图论】(二)图论基础与路径问题

图论基础与路径问题 图的构造邻接矩阵邻接表 所有可达路径邻接矩阵存储邻接表存储 字符串接龙有向图的完全可达性 图的构造 这里仅对图论路径问题中图的构造做整理总结归纳&#xff0c;具体详细相关概念请参考代码随想录上的整理总结&#xff1a; 图论理论基础深度优先搜索理…

【万字长文】Word2Vec计算详解(一)CBOW模型

【万字长文】Word2Vec计算详解&#xff08;一&#xff09;CBOW模型 写在前面 本文用于记录本人学习NLP过程中&#xff0c;学习Word2Vec部分时的详细过程&#xff0c;本文与本人写的其他文章一样&#xff0c;旨在给出Word2Vec模型中的详细计算过程&#xff0c;包括每个模块的计…

jmeter学习(7)beanshell

beanshell preprocessor 发送请求前执行 beanshell postprocessor 发送请求前执行 获取请求相关信息 String body sampler.getArguments().getArgument(0).getValue(); String url sampler.getPath(); 获取响应报文 String responseprev.getResponseDataAsString(); 获…

论文翻译 | Fairness-guided Few-shot Prompting for LargeLanguage Models

摘要 大型语言模型已经显示出令人惊讶的执行上下文学习的能力&#xff0c;也就是说&#xff0c;这些模型可以通过对由几个输入输出示例构建的提示进行条件反射&#xff0c;直接应用于解决大量下游任务。然而&#xff0c;先前的研究表明&#xff0c;由于训练示例、示例顺序和提示…

熵权法计算评价指标权重——使用Excel VBA实现

[ 熵权法 ] 信息是系统有序程度的一个度量&#xff0c;熵是系统无序程度的一个度量&#xff1b;根据信息熵的定义&#xff0c;对于某项指标&#xff0c;可以用熵值来判断某个指标的离散程度&#xff0c;其信息熵值越小&#xff0c;指标的离散程度越大&#xff0c; 该指标对综合…

科研绘图系列:R语言绘制SCI文章图2

文章目录 介绍加载R包导入数据图a图b图d系统信息介绍 文章提供了绘制图a,图b和图d的数据和代码 加载R包 library(ggplot2) library(dplyr) library(readxl) library(ggpmisc)导入数据 数据可从以下链接下载(画图所需要的所有数据): 百度网盘下载链接: https://pan.baid…

遍历有向图链路(DFS算法)- 优化版

在上一节基础上&#xff0c;去除了节点的pre集合&#xff0c;只保留节点next的结合&#xff0c;对数据模型进行了优化&#xff0c;实现思想做了优化。 有向图示例&#xff1a; 基本思路 构建有向图数据模型校验有向图不能出现回路&#xff0c;即当前节点不能出现在历史链路中首…

股指期货的杠杆是怎么体现和使用的?

股指期货的杠杆效应是通过保证金交易实现的。投资者只需支付合约价值的一小部分作为保证金&#xff0c;即可控制整个合约的价值。例如&#xff0c;如果一个股指期货合约的价值为100,000元&#xff0c;而保证金比例为10%&#xff0c;那么投资者只需支付10,000元即可控制这个合约…

SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(基础)

SpringBoot教程&#xff08;二十四&#xff09; | SpringBoot实现分布式定时任务之Quartz&#xff08;基础&#xff09; 简介适用场景Quartz核心概念Quartz 存储方式Quartz 版本类型引入相关依赖开始集成方式一&#xff1a;内存方式(MEMORY)存储实现定时任务1. 定义任务类2. 定…

从commit校验失效问题探究husky原理

一、背景 之前创建的项目&#xff0c;发现代码 commit 提交的时候没有了任何校验&#xff0c;具体表现&#xff1a; 一是 feat fix 等主题格式校验没有了二是代码 lint 不通过也能提交 尝试解决这个问题&#xff0c;并深入了解husky的实现原理&#xff0c;将相关的一些知识点…

【Vue】Vue扫盲(三)计算属性和监听器

【Vue】Vue扫盲&#xff08;一&#xff09;事件标签、事件修饰符&#xff1a;click.prevent click.stop click.stop.prevent、按键修饰符、及常用指令 【Vue】Vue扫盲&#xff08;二&#xff09;指令&#xff1a;v-for 、v-if、v-else-if、v-else、v-show 文章目录 1、 计算属…

用FPGA做一个全画幅无反相机

做一个 FPGA 驱动的全画幅无反光镜数码相机是不是觉得很酷&#xff1f; 就是上图这样。 Sitina 一款开源 35 毫米全画幅 (3624 毫米) CCD 无反光镜可换镜头相机 (MILC)&#xff0c;这个项目最初的目标是打造一款数码相机&#xff0c;将 SLR [单镜头反光] 相机转换为 DSLR [数码…

Spring事务的1道面试题

每次聊起Spring事务&#xff0c;好像很熟悉&#xff0c;又好像很陌生。本篇通过一道面试题和一些实践&#xff0c;来拆解几个Spring事务的常见坑点。 原理 Spring事务的原理是&#xff1a;通过AOP切面的方式实现的&#xff0c;也就是通过代理模式去实现事务增强。 具体过程是…