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

Spring配置文件中:密码明文改为密文处理方式(通用方法)

目录

一、背景    

二、思路

A) 普通方式

B) 适合bootstrap.properties方式

三、示例

A) 普通方式(连接Redis集群)

A) 普通方式(连接RocketMQ)

B) 适合bootstrap.properties方式

四、总结


一、背景    

  SpringBoot和SpringCloud中涉及多个配置文件,配置文件中对于密码默认是明文方式,这种方式在生产环境一般是不被允许的。为避免配置文件中出现明文,应当在配置文件中配置为密文,然后在启动时在程序内部完成解密。

    本文提供了通用的处理方式,可以适配以下几类配置文件:

  • 本地bootstrap.properties   在Spring的Bean创建之前的配置
  • 本地application.properties   在Spring的配置,包括带profile环境的配置
  • 配置中心上的配置(例如nacos上的Data ID)   

   为了适应配置文件涉及密码由明文改为密文,需要分为两步:

①将配置文件中涉及密文的配置项配置为密文字符串(需自己加密计算得到);

②在Spring启动中读取密文字符串并解密还原。

二、思路

       对于以上第②步Spring启动时的处理,由于以上配置文件在Spring加载的时机和生命周期不同,有两种处理方式:

A) 普通方式

      由于Spring中的对本地application.properties或者配置中心上的配置(例如nacos上的Data ID)在Spring Bean创建过程中,会有对应的配置Bean(通过注解@Configuration申明的Java类),Spring会自动根据读取解析配置文件并赋值给Bean。

      因此,若需要对密文字符串并解密还原,可以对配置Bean(通过注解@Configuration申明的Java类)进行继承,Override重写对应的set方法,完成解密。

B) 适合bootstrap.properties方式

      对于Spring Cloud,在bootstrap阶段还未创建Bean,所以以上Override重写对应的set方法并不适用。所以对于bootstrap.properties配置文件。可通过实现EnvironmentPostProcessor接口,来捕获Environment配置,解密后将配置新值设置到Environment中。

三、示例

A) 普通方式(连接Redis集群)

      下面以连接Redis集群为例进行说明,连接Redis集群的配置项可以在本地application.properties或者配置中心上的配置(例如nacos上的Data ID),且其中spring.redis.password配置项值已经设置为密文。

      下面代码对配置Bean(通过注解@Configuration申明的Java类RedisProperties)进行继承,Override重写对应的set方法。Java代码如下:

package 包指定忽略,请自定;import 忽略解密计算工具类SystemSecurityAlgorithm,请自定;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.StringUtils;/*** 连接Redis集群的配置类【通过@Configuration覆盖原Bean机制】:*   1、连接Redis的连接password不得出现明文,故需在properties配置文件中配置为加密密文(加密算法Java类为:SystemSecurityAlgorithm),然后在启动时通过本类解密*   2、贵金属应用服务采用多数据中心DataCenter部署。而每逻辑中心均有独立的Redis集群。 应用服务应连接同逻辑中心内的Redis集群,既北京的应用服务不应该连接合肥Redis集群*     既:对于同服务的不同实例,应根据服务实例所在逻辑中心(具体见枚举ServiceConstant.DataCenter定义的逻辑中心)连接相同逻辑中心下的Redis集群。*     因此:*        a).以Spring标准Redis连接配置为基础,对nodes值中各个IP端口配置,在各IP前增加一个大写字母:该IP所在DataCenter数据中心的英文代码*        b).以Spring标准Redis连接配置为基础,对password值改为可配多个密码,以逗号分隔,每个密码前增加一个大写字母,该密码是连接哪个Redis集群的DataCenter数据中心的英文代码* 为支持以上,定制化开发本类,实现处理最终还原至Spring标准连接Redis的配置,以供lettuce创建连接池。*  -----------------------------------------------------------* 机制适用性:* 除了通过@Configuration覆盖原Bean机制,还有通过实现EnvironmentPostProcessor接口机制。两种机制适用性说明如下:*   bootstrap.properties配置文件(bootstrap阶段,还未创建Bean) →→适合→→ 【实现EnvironmentPostProcessor接口机制】*   本地application.properties配置文件(正常SpringBoot启动,通过@Configuration注解的Bean) →→适合→→ 【实现EnvironmentPostProcessor接口机制】和【通过@Configuration覆盖原Bean机制】均可*   从Nacos等配置中心获取得到的配置文件 →→适合→→ 【通过@Configuration覆盖原Bean机制】**/
@Configuration
@Primary // 由于默认RedisProperties作为配置类会自动创建Bean。 为避免存在两个同类型(RedisProperties)Bean,所以本类通过注解Primary,使得只有本类生效。相当于替代默认RedisProperties
public class GjsRedisProperties extends RedisProperties {private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GjsRedisProperties.class);@Overridepublic void setPassword(String orginPassword) {if(StringUtils.hasText(orginPassword)) {// 对密文解密并设置if (StringUtils.hasText(orginPassword) && orginPassword.length() >= 32 ) { // 如果满足密码密文的长度及大小写要求,视为密文,解密String padStr = SystemSecurityAlgorithm.decryptStr(orginPassword);log.debug("连接Redis配置项spring.redis.password: 解密前orginPassword=[{}], 解密后padStr=[{}]", orginPassword, padStr); //为避免密码泄露,仅debug才输出明文log.info("连接Redis配置项spring.redis.password: 对密文orginPassword=[{}]已完成解密", orginPassword);super.setPassword(padStr);} else { // 不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变log.warn("连接Redis配置项spring.redis.password的:orginPassword=[{}]不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变", orginPassword);super.setPassword(orginPassword);}}}
}

A) 普通方式(连接RocketMQ)

      下面以连接RocketMQ为例进行说明,连接RocketMQ的配置项可以在本地application.properties或者配置中心上的配置(例如nacos上的Data ID),且其中rocketmq.producer.secret-keyrocketmq.consumer.secret-key配置项值已经设置为密文。

      下面代码对配置Bean(通过注解@Configuration申明的Java类RocketMQProperties)进行继承,Override重写对应的set方法。Java代码如下:

package 包指定忽略,请自定;import 忽略解密计算工具类SystemSecurityAlgorithm,请自定;
import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.StringUtils;import java.util.HashMap;
import java.util.Map;/*** 连接RocketMQ的配置类【通过@Configuration覆盖原Bean机制】:*   因连接RocketMQ的secret-key不得出现明文,故需在properties配置文件中配置为加密密文(加密算法Java类为:SystemSecurityAlgorithm),然后在启动时通过本类解密*  -----------------------------------------------------------* 机制适用性:* 除了通过@Configuration覆盖原Bean机制,还有通过实现EnvironmentPostProcessor接口机制。两种机制适用性说明如下:*   bootstrap.properties配置文件(bootstrap阶段,还未创建Bean) →→适合→→ 【实现EnvironmentPostProcessor接口机制】*   本地application.properties配置文件(正常SpringBoot启动,通过@Configuration注解的Bean) →→适合→→ 【实现EnvironmentPostProcessor接口机制】和【通过@Configuration覆盖原Bean机制】均可*   从Nacos等配置中心获取得到的配置文件 →→适合→→ 【通过@Configuration覆盖原Bean机制】**/
@Configuration
@Primary // 由于默认RocketMQProperties作为配置类会自动创建Bean。 为避免存在两个同类型(RocketMQProperties)Bean,所以本类通过注解Primary,使得只有本类生效。相当于替代默认RocketMQProperties
public class GjsRocketMQProperties extends RocketMQProperties {final private String KEYNAME_PRODUCER_SECRET = "rocketmq.producer.secret-key";final private String KEYNAME_CONSUMER_SECRET = "rocketmq.consumer.secret-key";@AutowiredConfigurableApplicationContext springContext;private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GjsRocketMQProperties.class);@Overridepublic void setProducer(Producer producer) {final String orginSecretKey = producer.getSecretKey();// 对密文解密并设置if (StringUtils.hasText(orginSecretKey) && orginSecretKey.length() >= 32) { // 如果满足密码密文的长度及大小写要求,视为密文,解密String padStr = SystemSecurityAlgorithm.decryptStr(orginSecretKey);log.debug("连接RocketMQ配置项{}: 解密前orginSecretKey=[{}], 解密后padStr=[{}]", KEYNAME_PRODUCER_SECRET, orginSecretKey, padStr); //为避免密码泄露,仅debug才输出明文log.info("连接RocketMQ配置项{}: 对密文orginSecretKey=[{}]已完成解密", KEYNAME_PRODUCER_SECRET, orginSecretKey);producer.setSecretKey(padStr);// 由于RocketMQ在构建DefaultRocketMQListenerContainer过程中,会从Spring的Environment中获取配置。// 附调用关系简要说明如下://     org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()//       org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()//         org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()//           org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()//             ......//               org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()// 因此一并修改环境中的值,使其能取得新值modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_PRODUCER_SECRET, padStr);} else { // 不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变log.warn("连接RocketMQ配置项rocketmq.producer.secret-key值=[{}]不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变", orginSecretKey);}super.setProducer(producer);}@Overridepublic void setConsumer(PushConsumer pushConsumer) {final String orginSecretKey = pushConsumer.getSecretKey();// 对密文解密并设置if (StringUtils.hasText(orginSecretKey) && orginSecretKey.length() >= 32 ) { // 如果满足密码密文的长度及大小写要求,视为密文,解密String padStr = SystemSecurityAlgorithm.decryptStr(orginSecretKey);log.debug("连接RocketMQ配置项{}: 解密前orginSecretKey=[{}], 解密后padStr=[{}]", KEYNAME_CONSUMER_SECRET, orginSecretKey, padStr); //为避免密码泄露,仅debug才输出明文log.info("连接RocketMQ配置项{}: 对密文orginSecretKey=[{}]已完成解密", KEYNAME_CONSUMER_SECRET, orginSecretKey);pushConsumer.setSecretKey(padStr);// 由于RocketMQ在构建DefaultRocketMQListenerContainer过程中,会从Spring的Environment中获取配置。// 附调用关系简要说明如下://     org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()//       org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()//         org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()//           org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()//             ......//               org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()// 因此一并修改环境中的值,使其能取得新值modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_CONSUMER_SECRET, padStr);} else { // 不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变log.warn("连接RocketMQ配置项{}的值=[{}]不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变", KEYNAME_CONSUMER_SECRET, orginSecretKey);}super.setConsumer(pushConsumer);}@Overridepublic void setPullConsumer(PullConsumer pullConsumer) {final String orginSecretKey = pullConsumer.getSecretKey();// 对密文解密并设置if (StringUtils.hasText(orginSecretKey) && orginSecretKey.length() >= 32 ) { // 如果满足密码密文的长度及大小写要求,视为密文,解密String padStr = SystemSecurityAlgorithm.decryptStr(orginSecretKey);log.debug("连接RocketMQ配置项{}: 解密前orginSecretKey=[{}], 解密后padStr=[{}]", KEYNAME_CONSUMER_SECRET, orginSecretKey, padStr); //为避免密码泄露,仅debug才输出明文log.info("连接RocketMQ配置项{}: 对密文orginSecretKey=[{}]已完成解密", KEYNAME_CONSUMER_SECRET, orginSecretKey);pullConsumer.setSecretKey(padStr);// 由于RocketMQ在构建DefaultRocketMQListenerContainer过程中,会从Spring的Environment中获取配置。// 附调用关系简要说明如下://     org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()//       org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()//         org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()//           org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()//             ......//               org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()// 因此一并修改环境中的值,使其能取得新值modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_CONSUMER_SECRET, padStr);} else { // 不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变log.warn("连接RocketMQ配置项{}的值=[{}]不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变", KEYNAME_CONSUMER_SECRET, orginSecretKey);}super.setPullConsumer(pullConsumer);}/*** 对Spring的Environment的配置项的值修改为新值* @param environment Spring的Environment对象* @param keyName 配置项名* @param newValue 新值*/private void modifyEnvironmentValue(ConfigurableEnvironment environment, final String keyName, String newValue) {if(!environment.containsProperty(keyName)) {log.warn("当前Spring的environment中不存在名为{}的配置项", keyName);return;}if(environment.getProperty(keyName, "").equals(newValue)) {log.debug("当前Spring的environment中配置项{}的值已与新值相同,无需修改", keyName);return;}Map<String, Object> map = new HashMap<>(); //用于存放新值map.put(keyName, newValue);// 若有map有值,则把该map作为PropertySource加入列表中,以实现:把environment中对应key的value覆盖为新值// 必须加到First并且不能存在两个相同的Name的MapPropertySource,值覆盖才能生效environment.getPropertySources().addFirst(new MapPropertySource("modifyEnvironmentValue-"+keyName, map));log.info("已对Spring的Environment的配置项{}的值修改为新值", keyName);}
}

B) 适合bootstrap.properties方式

      下面以连接Nacos配置中心为例进行说明,需要在本地bootstrap.properties配置文件中指定连接Nacos配置中心的Nacos用户名、密码、服务端地址、Data ID等信息。bootstrap.properties配置文件有关连接Nacos配置中心类似如下:

#Nacos配置中心及注册中心的authenticate鉴权用户名和密码(需Nacos服务端开启auth鉴权)
spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=760dee29f9fc82af0cc1d6074879dc39
#Nacos配置中心服务端的地址和端口(形式ip:port,ip:port,...) 。注:nacos-client1.x会按顺序选其中地址进行连接(前个连接失败则自动选后一个)。nacos-client2.x会随机选其中地址进行连接(若连接失败则自动另选)
spring.cloud.nacos.config.server-addr=ip1:8848,ip2:8848,ip3:8848,ip4:8848#Data ID的前缀(如果不设置,则默认取 ${spring.application.name})
#spring.cloud.nacos.config.prefix=
#默认指定为开发环境
#spring.profiles.active=
#Nacos命名空间,此处不设置,保持默认
#spring.cloud.nacos.config.namespace=
#配置组(如果不设置,则默认为DEFAULT_GROUP)
spring.cloud.nacos.config.group=G_CONFIG_GJS_SERVICE
#指定文件后缀(如果不设置,则默认为properties)
spring.cloud.nacos.config.file-extension=properties#以下为全局Data ID
spring.cloud.nacos.config.shared-configs[0].data-id=NacosRegDiscoveryInfo.properties
spring.cloud.nacos.config.shared-configs[0].group=G_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[0].refresh=truespring.cloud.nacos.config.shared-configs[1].data-id=XXXXX.properties
spring.cloud.nacos.config.shared-configs[1].group=G_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[1].refresh=truespring.cloud.nacos.config.shared-configs[2].data-id=YYYYY.properties
spring.cloud.nacos.config.shared-configs[2].group=G_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[2].refresh=true

其中spring.cloud.nacos.password配置项值已经设置为密文。

      下面的代码通过实现EnvironmentPostProcessor接口,来捕获配置,并将配置新值设置到Environment中。Java代码如下:

package 包指定忽略,请自定;import 忽略解密计算工具类SystemSecurityAlgorithm,请自定;
import org.apache.commons.logging.Log;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.StringUtils;import java.util.HashMap;
import java.util.Map;/*** 本类通过实现EnvironmentPostProcessor接口,实现在Spring启动过程中从environment中读取指定的key值,处理后,然后把environment中对应key的value覆盖为新值。* 通过本类已经实现对bootstrap阶段的配置文件处理:*   因连接Nacos的password不得出现明文,故bootstrap配置文件中为加密密文(加密算法Java类为:SystemSecurityAlgorithm),然后在启动时通过本类解密* -----------------------------------------------------------* 注意:*   a) 需要在META-INF下的spring.factories文件中配置本类后,本类才会生效(才被Spring扫描识别到)*   b) 因为本类是通过实现EnvironmentPostProcessor接口方式,所以本类在SpringCloud启动过程中会被调用两次:*         首先是在bootstrap配置文件加载后(SpringCloud为支持配置中心的bootstrap阶段)*         其次是在application配置文件加载后(SpringBoot的正常启动时加载配置文件阶段)* 机制适用性:* 除了通过实现EnvironmentPostProcessor接口机制,还有通过@Configuration覆盖原Bean机制。两种机制适用性说明如下:*   bootstrap.properties配置文件(bootstrap阶段,还未创建Bean) →→适合→→ 【实现EnvironmentPostProcessor接口机制】*   本地application.properties配置文件(正常SpringBoot启动,通过@Configuration注解的Bean) →→适合→→ 【实现EnvironmentPostProcessor接口机制】和【通过@Configuration覆盖原Bean机制】均可*   从Nacos等配置中心获取得到的配置文件 →→适合→→ 【通过@Configuration覆盖原Bean机制】**/
public class GjsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {/*** The default order for the processor.  值越小,优先级越高* 因bootstrap配置文件是通过{@link org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor}完成加载处理* 由于本EnvironmentPostProcessor类需等待SpringCloud对bootstrap配置文件后才能执行,所以本EnvironmentPostProcessor类优先级需更低*/public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 50;private final DeferredLogFactory logFactory;private final Log logger;public GjsEnvironmentPostProcessor(DeferredLogFactory logFactory,ConfigurableBootstrapContext bootstrapContext) {this.logFactory = logFactory;this.logger = logFactory.getLog(getClass());}@Overridepublic int getOrder() {return ORDER;}/*** 从environment中读取指定的key,并进行解密,解密后的结果放入map对象中* @param environment 已经有的Spring环境* @param keyName 指定的key名* @param map 若完成解密,则将解密后的结果放入map对象*/private void decodePwd(ConfigurableEnvironment environment, String keyName, Map<String, Object> map ) {if(!environment.containsProperty(keyName)) {this.logger.debug("EnvironmentPostProcessor 当前Spring的environment中不存在名为"+keyName+"的配置项");return;}final String origalValue = environment.getProperty(keyName);// 对密文解密并设置if (StringUtils.hasText(origalValue) && origalValue.length() >= 32) { // 如果满足密码密文的长度及大小写要求,视为密文,解密String padStr = SystemSecurityAlgorithm.decryptStr(origalValue);this.logger.debug("EnvironmentPostProcessor 配置项"+keyName+"原值=["+origalValue+"], 解密后值=["+padStr+"]"); //为避免在日志中密码泄露,仅debug才输出明文this.logger.info("EnvironmentPostProcessor 配置项"+keyName+"原值=["+origalValue+"]已完成解密");map.put(keyName, padStr);}else {this.logger.warn("EnvironmentPostProcessor 配置项"+keyName+"值=["+origalValue+"]不满足密码密文的长度及大小写要求(视为明文,不解密),保持不变");}}@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {this.logger.debug("EnvironmentPostProcessor before PropertySources size=" + environment.getPropertySources().size());this.logger.debug("EnvironmentPostProcessor before PropertySources : " + environment.getPropertySources());Map<String, Object> map = new HashMap<>(); //用于存放新值decodePwd(environment, "spring.cloud.nacos.password", map);if(!map.isEmpty()) {// 若有map有值,则把该map作为PropertySource加入列表中,以实现:把environment中对应key的value覆盖为新值// 必须加到First并且不能存在两个相同的Name的MapPropertySource,值覆盖才能生效environment.getPropertySources().addFirst(new MapPropertySource("afterDecodePassword", map));}this.logger.debug("EnvironmentPostProcessor after PropertySources size=" + environment.getPropertySources().size());this.logger.debug("EnvironmentPostProcessor after PropertySources : " + environment.getPropertySources());}}

四、总结

      通过以上两种方式,可解决Spring各类配置文件对配置密文的适配和处理。

      同时不仅仅用于密文,凡是需对配置文件的内容在启动时进行改变情况都可以按以上方式进行处理。例如启动时对配置项值中多个IP进行动态使用等情形。

相关文章:

Spring配置文件中:密码明文改为密文处理方式(通用方法)

目录 一、背景 二、思路 A) 普通方式 B) 适合bootstrap.properties方式 三、示例 A) 普通方式&#xff08;连接Redis集群&#xff09; A) 普通方式&#xff08;连接RocketMQ&#xff09; B) 适合bootstrap.properties方式 四、总结 一、背景 SpringBoot和Sprin…...

Linux下ext2文件系统

文章目录 一 :penguin:基本概述二 :star: ext2文件系统:star:​ 1. :star:​Boot Block&#xff08;引导块&#xff09;位置与作用 三 Block Group(块组):star:​1.:star:​ Super Block(超级块):star:​2.:star:​ Group Descriptor&#xff08;块组描述符&#xff09;:star:​…...

BUUCTF:web刷题记录(1)

目录 [极客大挑战 2019]EasySQL1 [极客大挑战 2019]Havefun1 [极客大挑战 2019]EasySQL1 根据题目以及页面内容&#xff0c;这是一个sql注入的题目。 直接就套用万能密码试试。 admin or 1 # 轻松拿到flag 换种方式也可以轻松拿到flag 我们再看一下网页源码 这段 HTML 代码…...

【微服务】面试题 6、分布式事务

分布式事务面试题讲解 一、问题背景与解决方案概述 因微服务项目涉及远程调用可能引发分布式事务问题&#xff0c;需解决。主流解决方案有阿里 Seata 框架&#xff08;含 XA、AT、TCC 模式&#xff09;和 MQ。 二、Seata 框架关键角色 事务协调者&#xff08;TC&#xff09;&…...

【2024年华为OD机试】(C卷,100分)- 分割均衡字符串 (Java JS PythonC/C++)

一、问题描述 题目描述 均衡串定义&#xff1a;字符串中只包含两种字符&#xff0c;且这两种字符的个数相同。 给定一个均衡字符串&#xff0c;请给出可分割成新的均衡子串的最大个数。 约定&#xff1a;字符串中只包含大写的 X 和 Y 两种字符。 输入描述 输入一个均衡串…...

Spring Data Elasticsearch简介

一、Spring Data Elasticsearch简介 1 SpringData ElasticSearch简介 Elasticsearch是一个实时的分布式搜索和分析引擎。它底层封装了Lucene框架,可以提供分布式多用户的全文搜索服务。 Spring Data ElasticSearch是SpringData技术对ElasticSearch原生API封装之后的产物,它通…...

GESP202312 四级【小杨的字典】题解(AC)

》》》点我查看「视频」详解》》》 [GESP202312 四级] 小杨的字典 题目描述 在遥远的星球&#xff0c;有两个国家 A 国和 B 国&#xff0c;他们使用着不同的语言&#xff1a;A 语言和 B 语言。小杨是 B 国的翻译官&#xff0c;他的工作是将 A 语言的文章翻译成 B 语言的文章…...

键盘过滤驱动

文章目录 概述注意源码参考资料 概述 irp请求会从io管理器中传递到设备栈中依次向下发送&#xff0c;当到达底层真实设备处理完成后&#xff0c;会依次返回&#xff0c;这时如果在设备栈中有我们自己注册的设备&#xff0c;就可以起到一个过滤的功能。键盘过滤驱动就是如此&am…...

dolphinscheduler2.0.9升级3.1.9版本问题记录

相关版本说明 JDK&#xff1a;JDK (1.8&#xff09; DolphinScheduler &#xff1a;3.1.9 数据库&#xff1a;MySQL (8)&#xff0c;驱动&#xff1a;MySQL JDBC Driver 8.0.16 注册中心&#xff1a;ZooKeeper (3.8.4) 问题一&#xff1a;dolphinscheduler2.0.9对应zk版本使用…...

【权限管理】Apache Shiro学习教程

Apache Shiro 是一个功能强大且灵活的安全框架&#xff0c;主要用于身份认证&#xff08;Authentication&#xff09;、授权&#xff08;Authorization&#xff09;、会话管理&#xff08;Session Management&#xff09;和加密&#xff08;Cryptography&#xff09;。它旨在为…...

9.4 visualStudio 2022 配置 cuda 和 torch (c++)

一、配置torch 1.Libtorch下载 该内容看了【Libtorch 一】libtorchwin10环境配置_vsixtorch-CSDN博客的博客&#xff0c;作为笔记用。我自己搭建后可以正常运行。 下载地址为windows系统下各种LibTorch下载地址_libtorch 百度云-CSDN博客 下载解压后的目录为&#xff1a; 2.vs…...

python特殊参数

一、默认值参数和关键字参数 1.默认值参数 形参设定默认值 称为 默认参数。调用函数时&#xff0c;可以使用比定义时更少的参数。调用函数时&#xff0c;如果没有传入默认参数对应的实参&#xff0c;则实参使用默认值默认参数在调用的时候可以不传递&#xff0c;也可以传递 …...

Ubuntu系统Qt的下载、安装及入门使用,图文详细,内容全面

文章目录 说明1 在线安装2 离线安装3 使用Qt Creator创建Qt应用程序并构建运行补充补充一&#xff1a;注册Qt账号 说明 本文讲解Ubuntu系统下安装Qt&#xff0c;包括在线安装和离线安装两种方式&#xff0c;内容充实细致&#xff0c;话多但是没有多余&#xff08;不要嫌我啰嗦…...

elasticsearch集群部署

一、创建 elasticsearch-cluster 文件夹 创建 elasticsearch-7.6.2-cluster文件夹 修改服务es服务文件夹为node-001 修改config/elasticsearch.yml 配置文件 # Elasticsearch Configuration # # NOTE: Elasticsearch comes with reasonable defaults for most settings. # …...

初学stm32 --- DAC模数转换器工作原理

目录 什么是DAC&#xff1f; DAC的特性参数 STM32各系列DAC的主要特性 DAC框图简介&#xff08;F1/F4/F7&#xff09; 参考电压/模拟部分电压 触发源 关闭触发时(TEN0)的转换时序图 DMA请求 DAC输出电压 什么是DAC&#xff1f; DAC&#xff0c;全称&#xff1a;Digital…...

保证Mysql数据库到ES的数据一致性的解决方案

文章目录 1.业务场景介绍1.1 需求分析1.2 技术实现方案 2.业界常用数据一致性方案分析2.1 同步双写方案2.2 MQ异步双写方案2.3 扫表定期同步方案2.4 监听binlog同步方案 1.业务场景介绍 1.1 需求分析 某知名的在线旅游平台&#xff0c;在即将到来的春季促销活动之前&#xff…...

Flutter Xcode 16+ iOS 18.1 使用image_pickers无法弹出选择图片的视图问题

解决 Flutter Xcode 16 使用 image_pickers 无法弹出选择图片视图的问题 在开发 Flutter 应用时&#xff0c;图片选择功能是常见的需求之一。image_pickers 库因其便捷性和功能丰富性&#xff0c;成为了许多开发者的选择。然而&#xff0c;随着 Xcode 版本的不断更新&#xff…...

socket网络编程-TC/IP方式

网络编程 1.概念&#xff1a;两台设备之间通过网络数据传输。 2.网络通信&#xff1a;将数据通过网络从一台设备传输另外一台设备。 3.java.net包下提供了一系列的类和接口&#xff0c;提供程序员使用&#xff0c;完成网络通信。 TCP和UDP TCP协议&#xff1a; 1.使用TCP协…...

《分布式光纤测温:解锁楼宇安全的 “高精度密码”》

在楼宇建筑中&#xff0c;因其内部空间庞大&#xff0c;各类电器设施众多&#xff0c;如何以一种既高效又稳定&#xff0c;兼具低成本与高覆盖特性的方式&#xff0c;为那些关键线路节点开展温度监测&#xff0c;是目前在安全监测领域一项重点研究项目&#xff0c;而无锡布里渊…...

C语言基本知识复习浓缩版:数组

所谓数组&#xff08;Array&#xff09;&#xff0c;就是一系列数据的集合。这些数据具有相同的类型&#xff0c;并且在内存中挨着存放&#xff0c;彼此之间没有缝隙。换句话说&#xff0c;数组用来存放多份数据&#xff0c;但是它有两个要求&#xff1a; 这些数据的类型必须相…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...