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-key和rocketmq.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) 普通方式(连接Redis集群) A) 普通方式(连接RocketMQ) B) 适合bootstrap.properties方式 四、总结 一、背景 SpringBoot和Sprin…...

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

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

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

【2024年华为OD机试】(C卷,100分)- 分割均衡字符串 (Java JS PythonC/C++)
一、问题描述 题目描述 均衡串定义:字符串中只包含两种字符,且这两种字符的个数相同。 给定一个均衡字符串,请给出可分割成新的均衡子串的最大个数。 约定:字符串中只包含大写的 X 和 Y 两种字符。 输入描述 输入一个均衡串…...

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

GESP202312 四级【小杨的字典】题解(AC)
》》》点我查看「视频」详解》》》 [GESP202312 四级] 小杨的字典 题目描述 在遥远的星球,有两个国家 A 国和 B 国,他们使用着不同的语言:A 语言和 B 语言。小杨是 B 国的翻译官,他的工作是将 A 语言的文章翻译成 B 语言的文章…...
键盘过滤驱动
文章目录 概述注意源码参考资料 概述 irp请求会从io管理器中传递到设备栈中依次向下发送,当到达底层真实设备处理完成后,会依次返回,这时如果在设备栈中有我们自己注册的设备,就可以起到一个过滤的功能。键盘过滤驱动就是如此&am…...

dolphinscheduler2.0.9升级3.1.9版本问题记录
相关版本说明 JDK:JDK (1.8) DolphinScheduler :3.1.9 数据库:MySQL (8),驱动:MySQL JDBC Driver 8.0.16 注册中心:ZooKeeper (3.8.4) 问题一:dolphinscheduler2.0.9对应zk版本使用…...
【权限管理】Apache Shiro学习教程
Apache Shiro 是一个功能强大且灵活的安全框架,主要用于身份认证(Authentication)、授权(Authorization)、会话管理(Session Management)和加密(Cryptography)。它旨在为…...

9.4 visualStudio 2022 配置 cuda 和 torch (c++)
一、配置torch 1.Libtorch下载 该内容看了【Libtorch 一】libtorchwin10环境配置_vsixtorch-CSDN博客的博客,作为笔记用。我自己搭建后可以正常运行。 下载地址为windows系统下各种LibTorch下载地址_libtorch 百度云-CSDN博客 下载解压后的目录为: 2.vs…...
python特殊参数
一、默认值参数和关键字参数 1.默认值参数 形参设定默认值 称为 默认参数。调用函数时,可以使用比定义时更少的参数。调用函数时,如果没有传入默认参数对应的实参,则实参使用默认值默认参数在调用的时候可以不传递,也可以传递 …...

Ubuntu系统Qt的下载、安装及入门使用,图文详细,内容全面
文章目录 说明1 在线安装2 离线安装3 使用Qt Creator创建Qt应用程序并构建运行补充补充一:注册Qt账号 说明 本文讲解Ubuntu系统下安装Qt,包括在线安装和离线安装两种方式,内容充实细致,话多但是没有多余(不要嫌我啰嗦…...
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? DAC的特性参数 STM32各系列DAC的主要特性 DAC框图简介(F1/F4/F7) 参考电压/模拟部分电压 触发源 关闭触发时(TEN0)的转换时序图 DMA请求 DAC输出电压 什么是DAC? DAC,全称:Digital…...

保证Mysql数据库到ES的数据一致性的解决方案
文章目录 1.业务场景介绍1.1 需求分析1.2 技术实现方案 2.业界常用数据一致性方案分析2.1 同步双写方案2.2 MQ异步双写方案2.3 扫表定期同步方案2.4 监听binlog同步方案 1.业务场景介绍 1.1 需求分析 某知名的在线旅游平台,在即将到来的春季促销活动之前ÿ…...
Flutter Xcode 16+ iOS 18.1 使用image_pickers无法弹出选择图片的视图问题
解决 Flutter Xcode 16 使用 image_pickers 无法弹出选择图片视图的问题 在开发 Flutter 应用时,图片选择功能是常见的需求之一。image_pickers 库因其便捷性和功能丰富性,成为了许多开发者的选择。然而,随着 Xcode 版本的不断更新ÿ…...

socket网络编程-TC/IP方式
网络编程 1.概念:两台设备之间通过网络数据传输。 2.网络通信:将数据通过网络从一台设备传输另外一台设备。 3.java.net包下提供了一系列的类和接口,提供程序员使用,完成网络通信。 TCP和UDP TCP协议: 1.使用TCP协…...

《分布式光纤测温:解锁楼宇安全的 “高精度密码”》
在楼宇建筑中,因其内部空间庞大,各类电器设施众多,如何以一种既高效又稳定,兼具低成本与高覆盖特性的方式,为那些关键线路节点开展温度监测,是目前在安全监测领域一项重点研究项目,而无锡布里渊…...
C语言基本知识复习浓缩版:数组
所谓数组(Array),就是一系列数据的集合。这些数据具有相同的类型,并且在内存中挨着存放,彼此之间没有缝隙。换句话说,数组用来存放多份数据,但是它有两个要求: 这些数据的类型必须相…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...