制作一个rabbitmq-sdk

news2024/9/19 16:50:57
目录结构

在这里插入图片描述

pom.xml
<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>
    <parent>
        <groupId>com.aasee</groupId>
        <artifactId>sms-modules</artifactId>
        <version>3.6.3</version>
    </parent>

    <artifactId>sms-rabbitmq-starter</artifactId>

    <name>sms-rabbitmq-starter</name>

    <description>
        rabbitmq-sdk
    </description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- SpringBoot Boot rabbitmq -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- skywalking -->
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-toolkit-logback-1.x</artifactId>
            <version>9.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-toolkit-trace</artifactId>
            <version>9.3.0</version>
        </dependency>
    </dependencies>
</project>

com.aasee.rabbitmq.configure.Callback
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@Slf4j
public class Callback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

    /**
     *
     * @param correlationData correlation data for the callback. 相关配置信息
     * @param ack true for ack, false for nack exchange交换机 是否成功收到了消息。true代表成功,false代表失败
     * @param cause An optional cause, for nack, when available, otherwise null.  失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.info("confirm方法被执行了!!!!! correlationData:{}",correlationData);
        if (ack){
            // 接受成功
            log.info("接受消息成功! correlationDataId: {} ,cause: {} " ,correlationData.getId(), cause);
        }else {
            // 接受失败
            log.error("接受消息失败! correlationDataId: {} ,cause: {} " ,correlationData.getId(), cause);
        }
    }

    /**
     * 回退模式:当消息发送给Exchange后, Exchange路由到Queue失败时 才会执行 ReturnCallBack
     * 步骤:
     *  1. 开启回退模式: publisher-returns: true #是否开启发送端消息抵达队列的确认
     *  2. 设置ReturnCallBack
     *  3. 设置Exchange处理消息的模式:
     *      1. 如果消息没有路由到Queue,则丢弃信息(默认)
     *      2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack  设置mandatory为true
     *
     */

    /**
     *
     * @param returned the returned message and metadata.
     */
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        log.info("return机制方法被执行了。。。。。");
        log.info("消息(message):" + returned.getMessage());
        log.info("退回原因代码(replyCode):" + returned.getReplyCode());
        log.info("退回原因(replyText):" + returned.getReplyText());
        log.info("交换机(exchange):" + returned.getExchange());
        log.info("路由Key(routingKey):" + returned.getRoutingKey());

        // TODO 处理未到Queue的数据(或者使用备份交换机)
    }
}
com.aasee.rabbitmq.configure.CustomMessageInterceptor
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.stereotype.Component;

/**
 * 自定义消息拦截器
 *
 * @author Aasee
 * @date 2024-03-11
 */
@Component
public class CustomMessageInterceptor implements MessagePostProcessor {

    @Override
    public Message postProcessMessage(Message message) {
        // 获取原始消息属性
        MessageProperties properties = message.getMessageProperties();

        // 设置新的消息头部信息(如果有需求)
//        properties.setHeader("loginUser", SecurityUtils.getLoginUser());
        return new Message(message.getBody(), properties);
    }
}
com.aasee.rabbitmq.configure.RabbitMQConfig
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMQ配置
 *
 * @author Aasee
 * @date 2024-02-24
 */
@Configuration
public class RabbitMQConfig {
    /**
     * rabbitmq Redis 防止重复消费键
     */
    public final static String RABBITMQ_REDIS_KEY="RabbitMq_Send_Sms:";
    public final static String RABBITMQ_REISSUE_KEY="RabbitMq_Reissue_Sms:";
    public final static String RABBITMQ_CALLBACK_KEY="RabbitMq_Callback_Sms:";

    //--------------交换机名称-----------------------------------
    public static final String EXCHANGE_NAME = "aasee_topic_exchange";

    //---------------队列名称------------------------------------
    public static final String QUEUE_NAME = "aasee_queue";
    public static final String SMS_QUEUE_NAME = "cloud_sms_queue";
    public static final String REISSUE_QUEUE_NAME = "sms_reissue_queue";
    public static final String CALLBACK_QUEUE_NAME = "callback_sms_queue";

    //--------------路由键名称-----------------------------------
    public static final String ROUTING_KEY = "aasee.#";
    public static final String SMS_ROUTING_KEY = "sms.#";
    public static final String REISSUE_ROUTING_KEY = "reissue.#";
    public static final String CALLBACK_ROUTING_KEY = "callback.#";
    //------------------------交换机--------------------------
	// 交换机
    @Bean(value = "aaseeExchange")
    public Exchange bootExchange(){
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    //------------------------队列--------------------------
    // 队列
    @Bean("aaseeQueue")
    public Queue bootQueue(){
        return QueueBuilder.durable(QUEUE_NAME).build();
    }
    // SMS 队列
    @Bean("cloudSmsQueue")
    public Queue smsQueue(){
        return QueueBuilder.durable(SMS_QUEUE_NAME).build();
    }
    // SMS 补发 队列
    @Bean("smsReissueQueue")
    public Queue smsReissueQueue(){
        return QueueBuilder.durable(REISSUE_QUEUE_NAME).build();
    }
    // 短信回调 队列
    @Bean("callbackSmsQueue")
    public Queue callbackSmsQueue(){
        return QueueBuilder.durable(CALLBACK_QUEUE_NAME).build();
    }
    //-------------------路由绑定-------------------------
    // 队列和交换机绑定关系 Binding
    /*
        1. 知道哪个队列
        2. 知道哪个交换机
        3. routing key
     */
    // 路由绑定
    @Bean
    public Binding bingQueueExchange(@Qualifier("aaseeQueue") Queue queue, @Qualifier("aaseeExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();
    }
    // SMS 路由绑定
    @Bean
    public Binding bingSmsQueueExchange(@Qualifier("cloudSmsQueue") Queue queue, @Qualifier("aaseeExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(SMS_ROUTING_KEY).noargs();
    }
    // 补发 路由绑定
    @Bean
    public Binding bingSmsReissueQueueExchange(@Qualifier("smsReissueQueue") Queue queue, @Qualifier("aaseeExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(REISSUE_ROUTING_KEY).noargs();
    }
    // 短信回调 路由绑定
    @Bean
    public Binding bingCallbackSmsQueueExchange(@Qualifier("callbackSmsQueue") Queue queue, @Qualifier("aaseeExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(CALLBACK_ROUTING_KEY).noargs();
    }


    // Mq模板类
    @Bean
    //设置rabbitTemplate的scope为:prototype
//    @Scope("prototype")
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setBeforePublishPostProcessors(new CustomMessageInterceptor());
        //成功回调
        template.setConfirmCallback(new Callback());
        // 开启mandatory模式(开启失败回调)
        template.setMandatory(true);
        //失败回调
        template.setReturnsCallback(new Callback());
        return template;
    }



}
com.aasee.rabbitmq.service.RabbitMqService
import com.alibaba.fastjson2.JSON;
import com.aasee.rabbitmq.configure.RabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;


/**
 * Rabbit MQ 工具类
 *
 * @author Aasee
 * @date 2024-03-11
 */
//@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
@Slf4j
public class RabbitMqService {
    @Autowired
    public RabbitTemplate rabbitTemplate;


    /**
     * 发送 MQ 消息
     *
     * @param exchange             交换机
     * @param routingKey           路由键
     * @param value                放入mq中的消息体(需要对象)
     * @param messagePostProcessor 消息后处理器(自定义处理)
     * @param correlationData      相关数据(用于传递唯一标识,跟踪绑定数据信息)
     */
    public <T> void sendMqMessage(String exchange, String routingKey,
                                  T value, MessagePostProcessor messagePostProcessor,CorrelationData correlationData) {
        // 推送到Mq中
        rabbitTemplate.convertAndSend(exchange, routingKey, JSON.toJSONString(value), messagePostProcessor,correlationData);
    }


    /**
     * 发送 MQ 消息
     *
     * @param exchange             交换机
     * @param routingKey           路由键
     * @param value                放入mq中的消息体(需要对象)
     * @param messagePostProcessor 消息后处理器(自定义处理)
     */
    public <T> void sendMqMessage(String exchange, String routingKey,
                                  T value, MessagePostProcessor messagePostProcessor) {
        // 唯一标识用于判断消息身份和内容
        String messageId = UUID.randomUUID().toString();
        CorrelationData correlationData = new CorrelationData(messageId);
        // 推送到Mq中
        String jsonString = JSON.toJSONString(value);
        log.info("发送 MQ 消息! exchange: {} , routingKey: {} , messageId: {} , jsonString: {}",exchange,routingKey,messageId,jsonString);
        rabbitTemplate.convertAndSend(exchange, routingKey, jsonString, messagePostProcessor,correlationData);
    }


    /**
     * 发送短信信息
     *
     * @param value                短信信息
     * @param messagePostProcessor 消息后处理器(自定义处理)
     */
    public <T> void sendSmsMessage(T value, MessagePostProcessor messagePostProcessor) {
        // 推送到Mq中
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.SMS_ROUTING_KEY, JSON.toJSONString(value), messagePostProcessor);
    }
}
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.aasee.rabbitmq.configure.RabbitMQConfig, \
  com.aasee.rabbitmq.service.RabbitMqService, \
  com.aasee.rabbitmq.configure.CustomMessageInterceptor

以上就是sdk的所有配置内容,使用bean的自动装配原理让这个sdk被引入时可以自动被spring托管配置。

接下来就是展示如何使用sdk的,同时展示nacos配置或者说application.yml配置

引入SDK
<dependency>
    <groupId>com.aasee</groupId>
    <artifactId>sms-rabbitmq-starter</artifactId>
    <version>3.6.3</version>
</dependency>

可以把这个sdk放到阿里云制品仓库,或者自建私服,又或是直接托管到maven中央仓库,这样你的小伙伴们就能直接引入你的sdk

application.yml
# rabbitmq 配置
  rabbitmq:
    host: xx.xx.xxx.xxx
    virtual-host: /cloudsmsTest
    username: root
    password: xxxxxx
    port: 5672
    publisher-confirm-type: correlated
    publisher-returns: true #是否开启发送端消息抵达队列的确认
    template:
      mandatory: true # 只要消息没有正确抵达队列,以异步方式优先执行我们自己设置的回调,设置交换机处理失败消息的模式
    listener:
      simple:
        acknowledge-mode: manual
        prefetch: 1 #更改为每次读取1条消息,在消费者未回执确认之前,不在进行下一条消息的投送
定时上下线

在开发中我发现了一个有趣的需求,定时上线消费,定时下线停止消费,生产者可以持续往队列里发送消息,但是消费者则可以在指定时间,或者通过手动的方式上下线,以下是具体实现方法

com.aasee.smsconsumer.scheduler.ConsumerScheduler
import com.aasee.common.core.constant.ChannelConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
@Slf4j
public class ConsumerScheduler implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private RabbitListenerEndpointRegistry registry;

    // 每天早上8点启动消费者
    // todo 创建接口用于手动启动
    @Scheduled(cron = "0 0 8 * * ?")
    public void startListening() {
//        RabbitListenerEndpointRegistry registry = applicationContext.getBean(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, RabbitListenerEndpointRegistry.class);
        startConsumer();
    }

    public void startConsumer() {
        if (!registry.getListenerContainer(ChannelConstant.SMSCONSUMER).isRunning()) {
            registry.getListenerContainer(ChannelConstant.SMSCONSUMER).start();
            log.info("smsConsumer 开启监听");
        }
        if (!registry.getListenerContainer(ChannelConstant.REISSUECONSUMER).isRunning()) {
            registry.getListenerContainer(ChannelConstant.REISSUECONSUMER).start();
            log.info("reissueConsumer 开启监听");
        }
    }

    // 每天晚上7点停止消费者
    @Scheduled(cron = "0 0 19 * * ?")
    public void stopListening() {
//        RabbitListenerEndpointRegistry registry = applicationContext.getBean(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, RabbitListenerEndpointRegistry.class);
        if (registry.getListenerContainer(ChannelConstant.SMSCONSUMER).isRunning()) {
            registry.getListenerContainer(ChannelConstant.SMSCONSUMER).stop();
            log.info("smsConsumer 停止监听");
        }
        if (registry.getListenerContainer(ChannelConstant.REISSUECONSUMER).isRunning()) {
            registry.getListenerContainer(ChannelConstant.REISSUECONSUMER).stop();
            log.info("reissueConsumer 停止监听");
        }
    }

    // 这个是为了每次启动或者重启服务后马上开始消费,可以取消
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("服务启动开启消费!");
        startConsumer();
    }
}
注意

如何你们存在这种定时上下线的需求,需要在**@RabbitListener**注解上加多一个参数 autoStartup = “false” ,这样消费者就不会自动消费消息了

@RabbitListener(id = ChannelConstant.REISSUECONSUMER,queues = RabbitMQConfig.REISSUE_QUEUE_NAME,autoStartup = "false")

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VR全景摄影制作中的常见问题及解决方案

随着VR全景摄影技术的普及&#xff0c;越来越多的摄影师和企业开始尝试使用这种方式来展示产品、场景或服务。虽然VR全景摄影有着强大的视觉表现力和沉浸式体验&#xff0c;但在制作过程中也会遇到各种挑战。为了帮助大家更好地掌握VR全景摄影的制作技巧&#xff0c;今天给分享…

STM32 单片机最小系统全解析

STM32 单片机最小系统全解析 本文详细介绍了 STM32 单片机最小系统&#xff0c;包括其各个组成部分及设计要点与注意事项。STM32 最小系统在嵌入式开发中至关重要&#xff0c;由电源、时钟、复位、调试接口和启动电路等组成。 在电源电路方面&#xff0c;采用 3.3V 直流电源供…

【Bug解决】Nacos启动成功,但却无法访问(提示:无法访问此网站,192.168.10.88的响应时间过长)

项目场景&#xff1a; 在虚拟机上通过Docker创建Nacos容器&#xff0c;已经创建成功&#xff0c;查看Nacos启动日志也是成功。但通过端口号加8848/nacos&#xff08;如&#xff1a;http://IP:8848/nacos&#xff09;无法访问到Nacos管理页面。 愿意分析一&#xff1a; 先检查好…

MyISAM引擎介绍

文章目录 特点适用场景不足锁机制表级锁的类型锁的获取和释放锁的等待队列 示例共享锁排他锁READ LOCAL MyISAM是MySQL的一种存储引擎&#xff0c;它以其简单性和高速度而著称。在早期的MySQL版本中&#xff0c;MyISAM广泛使用&#xff0c;尤其是在那些以读操作为主的应用场景中…

Parallels Desktop 20(Mac虚拟机) v20.0.0 for Mac 最新破解版(支持M系列)

Parallels Desktop 20 for Mac 正式发布&#xff0c;完全支持 macOS Sequoia 和 Windows 11 24H2&#xff0c;并且在企业版中引入了全新的管理门户。 据介绍&#xff0c;新版本针对 Windows、macOS 和 Linux 虚拟机进行了大量更新&#xff0c;最大的亮点是全新推出的 Parallels…

6款好用的电脑监控软件推荐|2024电脑监控软件干货整理!

电脑监控软件成为了企业管理中不可或缺的工具&#xff0c;这些软件不仅能够帮助企业实时监控员工的工作状态&#xff0c;还能有效提升团队效率和保障数据安全。 本文将为您推荐六款2024年值得一试的电脑监控软件&#xff0c;每款软件都有其独特的亮点和优势&#xff0c;适合不…

【Java-线程池】

目录 什么是线程池&#xff1a; 线程池七大参数&#xff1a; 参数详细介绍&#xff1a; 1.核心线程数&#xff1a; 2.最大线程数&#xff1a; 3.线程存活时间&#xff1a; 4.时间单位&#xff1a; 5. 阻塞工作队列&#xff1a; 6.线程工厂&#xff1a; 7.拒绝策略&am…

青柠视频云——视频丢包(卡顿、花屏、绿屏)排查

一、问题说明 近期有客户反馈&#xff0c;接入平台的设备经常出来卡顿、花屏、录屏的情况&#xff0c;出现这样的场景很是尴尬。 客户是私有化部署在公网环境&#xff0c;于是我们联系客户&#xff0c;对问题进行追踪排查。 二、场景复现 我们现场情况确认的过程中&#xff0c;…

怎么理解机器学习与数据融合的集成?

在科技进步的浪潮中&#xff0c;数据的重要性日益成为共识。但数据本身&#xff0c;若不经过有效的整合与分析&#xff0c;其价值便难以充分发挥。本文将探讨如何通过集成数据融合与机器学习&#xff0c;提升预测和决策的准确性。将海量数据转化为富含洞察力的信息&#xff0c;…

腾讯云TDSQL数据库认证值得考吗?来看看TDSQL证书有什么用

国内市场上的数据库产品有不少&#xff0c;很多大企业都有自己的数据库产品&#xff0c;比如金仓的KingBase、华为的OpenGauss、阿里云的PolarDB、达梦DM数据库等等&#xff0c;腾讯云也有自己的数据库产品&#xff0c;叫做TDSQL数据库&#xff0c;TDSQL数据库有两个分支&#…

基于SpringBoot+定时任务实现地图上绘制车辆实时运动轨迹图

目录 1. 项目结构 2. Maven依赖配置 (pom.xml) 3. 实现后端服务 4. 配置文件 (application.properties) 5. 启动项目 6. 访问页面 实现基于北斗卫星的车辆定位和轨迹图的Maven工程&#xff08;使用模拟数据&#xff09;&#xff0c;我们将使用以下技术&#xff1a; Spri…

企业导师面对面,产教融合实训基地搭建人才成长快车道

产教融合实训基地是指以特定专业群为服务对象&#xff0c;由高职院校和企业双方共同投入、共同利用&#xff0c;并由若干实验实训室和模块产线组成的综合性实训基地。这一基地不仅具备工学结合的功能&#xff0c;还融合了教育、产业和科技创新的要素&#xff0c;形成了一种特殊…

食品检测与分类系统源码分享

食品检测与分类检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

Galileo与GLONASS信号类型

GLONASS的G1信号与G2信号是频分多址&#xff0c;用于标记卫星。

面向切面:单元测试、事务、资源操作

目录 一、单元测试二、事务2.1、概述2.1.1、编程式事务2.1.2、声明式事务 2.2、JdbcTemplate2.3、基于注解的声明式事务2.3.1、基本用例-实现注解式的声明事务2.3.2、事务属性&#xff1a;只读2.3.3、事务属性&#xff1a;超时2.3.4、事务属性&#xff1a;回滚策略2.3.5、事务属…

柯桥生活英语培训之“老顾客”在英语中通常有几种表达方式?

“老顾客”用英语怎么说&#xff1f;千万不要直接翻译成“old customer”&#xff0c;因为这会让人误以为是指年龄大的顾客。 “老顾客”在英语中通常有以下几种表达方式&#xff1a; regular customer 这是最常见的表达&#xff0c;强调顾客经常光顾。这里的“regular”强调的…

RTR——Chapter5 下

锯齿和抗锯齿 想象现在有一个巨大的黑色三角形&#xff0c;正在白色的背景上缓缓移动。由于屏幕上的网格单元被三角形所覆盖&#xff0c;因此其像素值的强度应该会平滑的下降。但是通常在各种基础渲染器中发生的情况是&#xff0c;一旦网格单元的中心被三角形所覆盖&#xff0c…

【JavaScript】算法之分治、动态规划

一个大问题分成多个小问题&#xff0c;递归解决小问题&#xff0c;将结果合并从而来解决原来的问题 分治 子问题都是独立的 动态规划 把分治优化了【重复的问题&#xff0c;单独保存起来】斐波那契数列 leetcode 习题 分治、动态规划习题

Varjo在芬兰开设新工厂,以满足国防部门在XR模拟训练中的需求

在军事国防领域&#xff0c;全新技术的投入使用最看重的就是保密与安全。作为全球领先的XR头戴式显示器提供商Varjo&#xff0c;近日正式宣布将在位于芬兰的赫尔辛基开设一家新的安全制造工厂。 此次工厂扩建将使Varjo能够满足国防训练和模拟领域对其高分辨率XR解决方案日益增…

Python if 语句优化技巧

大家好&#xff01;今天我们来聊聊Python中的if语句优化技巧。if语句是Python中最基本的控制结构之一&#xff0c;它用于根据条件执行不同的代码块。虽然if语句本身非常简单&#xff0c;但通过一些小技巧&#xff0c;可以让我们的代码更加高效、简洁。接下来&#xff0c;我们将…