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…...
树的模拟实现
一.链式前向星 所谓链式前向星,就是用链表的方式实现树。其中的链表是用数组模拟实现的链表。 首先我们需要创建一个足够大的数组h,作为所有结点的哨兵位。创建两个足够大的数组e和ne,一个作为数据域,一个作为指针域。创建一个变…...
计算机图形学【直线和圆的生成算法】
在计算机图形学中,光栅化是将几何图元转换成一个光栅图像(像素或点)在屏幕上输出的过程。光栅化可实现图形变为二维图像,其目的是将连续的几何图形转换为离散的像素点。光栅化算法的基本原理包括两个主要步骤:首先&…...
OpenAI 故障复盘 - 阿里云容器服务与可观测产品如何保障大规模 K8s 集群稳定性
本文作者: 容器服务团队:刘佳旭、冯诗淳 可观测团队:竺夏栋、麻嘉豪、隋吉智 一、前言 Kubernetes(K8s)架构已经是当今 IT 架构的主流与事实标准(CNCF Survey[1])。随着承接的业务规模越来越大,用户也在使…...
【深度学习】Pytorch:加载自定义数据集
本教程将使用 flower_photos 数据集演示如何在 PyTorch 中加载和导入自定义数据集。该数据集包含不同花种的图像,每种花的图像存储在以花名命名的子文件夹中。我们将深入讲解每个函数和对象的使用方法,使读者能够推广应用到其他数据集任务中。 flower_ph…...
vue js实现时钟以及刻度效果
2025.01.08今天我学习如何用js实现时钟样式,效果如下: 一、html代码如下: <template><!--圆圈--><div class"notice_border"><div class"notice_position notice_name_class" v-for"item in …...
js基础---注释与结束符
JavaScript 基础:注释与结束符 注释 注释是代码中用于解释说明的部分,不会被执行,主要有两种类型: 单行注释 符号://作用:从符号开始到该行末尾的所有内容都会被忽略,不会被执行。示例代码&…...
from pytorch3d import _C问题
离线安装pytorch3d后,先测试: import pytorch3d 没问题后,再测试: from pytorch3d import _C 单独测试会出现: ImportError: libc10.so: cannot open shared object file: No such file or directory 或者类似不…...
PHP进阶-在Ubuntu上搭建LAMP环境教程
本文将为您提供一个在Ubuntu服务器上搭建LAMP(Linux, Apache, MySQL, PHP)环境的完整指南。通过本文,您将学习如何安装和配置Apache、MySQL、PHP,并将您的PHP项目部署到服务器上。本文适用于Ubuntu 20.04及更高版本。 一、系统更新…...
SQLite 命令
关于《SQLite 命令》的文章,我可以为您提供一个概要。SQLite是一个轻量级的嵌入式关系数据库管理系统,它以单个文件的形式存储数据,非常适合用于不需要传统数据库服务器的场景。SQLite3的命令行工具(sqlite3.exe)是一个…...
ASP.NET Core 实现微服务 - Consul 配置中心
这一次我们继续介绍微服务相关组件配置中心的使用方法。本来打算介绍下携程开源的重型配置中心框架 apollo 但是体系实在是太过于庞大,还是让我爱不起来。因为前面我们已经介绍了使用Consul 做为服务注册发现的组件 ,那么干脆继续使用 Consul 来作为配置…...
自定义Java注解及其应用
上一篇博客:Java注解 写在前面:大家好!我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。…...
回归预测 | MATLAB实GRU多输入单输出回归预测
回归预测 | MATLAB实GRU多输入单输出回归预测 目录 回归预测 | MATLAB实GRU多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 回归预测 | MATLAB实GRU多输入单输出回归预测。使用GRU作为RNN的一种变体来处理时间序列数据。GRU相比传统的RNN有较好的记…...
ISP流程--去马赛克详解
前言 本期我们将深入讨论ISP流程中的去马赛克处理。我们熟知,彩色图像由一个个像元组成,每个像元又由红、绿、蓝(RGB)三通道构成。而相机传感器只能感知光的强度,无法直接感知光谱信息,即只有亮暗而没有颜色…...
用户注册模块用户校验(头条项目-05)
1 用户注册后端逻辑 1.1 接收参数 username request.POST.get(username) password request.POST.get(password) phone request.POST.get(phone) 1.2 校验参数 前端校验过的后端也要校验,后端的校验和前端的校验是⼀致的 # 判断参数是否⻬全 # 判断⽤户名是否…...
【大数据】Apache Superset:可视化开源架构
Apache Superset是什么 Apache Superset 是一个开源的现代化数据可视化和数据探索平台,主要用于帮助用户以交互式的方式分析和展示数据。有不少丰富的可视化组件,可以将数据从多种数据源(如 SQL 数据库、数据仓库、NoSQL 数据库等࿰…...
如何搭建 Vue.js 开源项目的 CI/CD 流水线
网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…...
OCR文字识别—基于PP-OCR模型实现ONNX C++推理部署
概述 PaddleOCR 是一款基于 PaddlePaddle 深度学习平台的开源 OCR 工具。PP-OCR是PaddleOCR自研的实用的超轻量OCR系统。它是一个两阶段的OCR系统,其中文本检测算法选用DB,文本识别算法选用CRNN,并在检测和识别模块之间添加文本方向分类器&a…...
国产3D CAD将逐步取代国外软件
在工业软件的关键领域,计算机辅助设计(CAD)软件对于制造业的重要性不言而喻。近年来,国产 CAD 的发展态势迅猛,展现出巨大的潜力与机遇,正逐步改变着 CAD 市场长期由国外软件主导的格局。 国产CAD发展现状 …...
GoLand 如何集成 Netty?
目录 1.回答问题: 2.以下是实现类似 Netty 功能的步骤: 2.1 实现基本的网络通信功能: 3. 使用 Go 的第三方库实现 Netty 功能 4.实现类似 Netty 的事件循环: 5. 运用场景: 1.回答问题: 要在 GoLand 中…...
C++中 为什么要把基类指针指向子类对象?
为什么要把基类指针指向子类对象? 1)实现多态性 动态绑定行为:通过基类指针指向子类对象,可以利用 C 的多态机制。当基类中有虚函数,并且子类重写了这些虚函数时,通过基类指针调用虚函数,实际调…...
2025年第三届“华数杯”国际赛A题解题思路与代码(Matlab版)
游泳竞技策略优化模型代码详解(MATLAB版) 第一题:速度优化模型 本部分使用MATLAB实现游泳运动员在不同距离比赛中的速度分配策略优化。 1. 模型概述 模型包含三个主要文件: speed_optimization.m: 核心优化类plot_speeds.m: …...
做一个 简单的Django 《股票自选助手》显示 用akshare 库(A股数据获取)
图: 股票自选助手 这是一个基于 Django 开发的 A 股自选股票信息查看系统。系统使用 akshare 库获取实时股票数据,支持添加、删除和更新股票信息。 功能特点 支持添加自选股票实时显示股票价格和涨跌幅一键更新所有股票数据支持删除不需要的股票使用中…...
深入探索 ScottPlot.WPF:在 Windows 桌面应用中绘制精美图表的利器
一、ScottPlot.WPF 简介 ScottPlot.WPF 是基于 ScottPlot 绘图库专门为 Windows Presentation Foundation (WPF) 框架量身定制的强大绘图组件。它无缝集成到 WPF 应用程序中,为开发者提供了一种简洁、高效的方式来可视化数据,无论是科学研究中的实验数据展示、金融领域的行情…...
Spring bean的生命周期和扩展
接AnnotationConfigApplicationContext流程看实例化的beanPostProcessor-CSDN博客,以具体实例看bean生命周期的一些执行阶段 bean生命周期流程 生命周期扩展处理说明实例化:createBeanInstance 构造方法, 如Autowired的构造方法注入依赖bean 如UserSer…...
【Docker】docker compose 安装 Redis Stack
注:整理不易,请不要吝啬你的赞和收藏。 前文 Redis Stack 什么是? 简单来说,Redis Stack 是增强版的 Redis ,它在传统的 Redis 数据库基础上增加了一些高级功能和模块,以支持更多的使用场景和需求。Redis…...
Life Long Learning(李宏毅)机器学习 2023 Spring HW14 (Boss Baseline)
1. 终身学习简介 神经网络的典型应用场景是,我们有一个固定的数据集,在其上训练并获得模型参数,然后将模型应用于特定任务而无需进一步更改模型参数。 然而,在许多实际工程应用中,常见的情况是系统可以不断地获取新数据,例如 Web 应用程序中的新用户数据或自动驾驶中的…...
JavaEE之线程池
前面我们了解了多个任务可以通过创建多个线程去处理,达到节约时间的效果,但是每一次的线程创建和销毁也是会消耗计算机资源的,那么我们是否可以将线程进阶一下,让消耗计算机的资源尽可能缩小呢?线程池可以达到此效果&a…...
错误修改系列---基于RNN模型的心脏病预测(pytorch实现)
前言 前几天发布了pytorch实现,TensorFlow实现为:基于RNN模型的心脏病预测(tensorflow实现),但是一处繁琐地方 一处错误,这篇文章进行修改,修改效果还是好了不少;源文章为:基于RNN模型的心脏病…...
修改之前的代码使得利用设备树文件和Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】
引言 在下面这篇博文中: 利用Linux的Platform总线设备驱动实现对多个LED的驱动【只是假想对LED进行驱动,并没有实际的硬件操作】 我们利用Platform总线设备驱动的思想实现了对多个LED的驱动。 Platform总线设备驱动以及其它的总线设备驱动都将驱动分成了三个部分…...
从CentOS到龙蜥:企业级Linux迁移实践记录(龙蜥开局)
引言: 在我们之前的文章中,我们详细探讨了从CentOS迁移到龙蜥操作系统的基本过程和考虑因素。今天,我们将继续这个系列,重点关注龙蜥系统的实际应用——特别是常用软件的安装和配置。 龙蜥操作系统(OpenAnolis&#…...
多云架构,JuiceFS 如何实现一致性与低延迟的数据分发
随着大模型的普及,GPU 算力成为稀缺资源,单一数据中心或云区域的 GPU 资源常常难以满足用户的全面需求。同时,跨地域团队的协作需求也推动了企业在不同云平台之间调度数据和计算任务。多云架构正逐渐成为一种趋势,然而该架构下的数…...
Jenkins持续集成与交付安装配置
Jenkins 是一款开源的持续集成(CI)和持续交付(CD)工具,它主要用于自动化软件的构建、测试和部署流程。为项目持续集成与交付功能强大的应用。下面我们来介绍下它的安装与配置。 环境准备 更新系统组件(这…...
十大排序简介
十大排序简介 一、排序分类二、排序思路1.冒泡排序(Bubble Sort)2.选择排序(Selection Sort)3.插入排序(Insertion Sort)4.希尔排序(Shell Sort&a…...
uniapp小程序中隐藏顶部导航栏和指定某页面去掉顶部导航栏小程序
uniappvue3开发小程序过程中隐藏顶部导航栏和指定某页面去掉顶部导航栏方法 在page.json中 "globalStyle": {"navigationStyle":"custom",}, 如果是指定某个页面关闭顶部导航栏,在style中添加"navigationStyle": "cus…...
echarts:dataZoom属性横向滚动条拖拽不生效
问: 拖拽的过程中,第一次向右拖拽正常,然后就报错: echarts报错: var pointerOption pointerShapeBuilder[axisPointerType](axis,pixeValue,otherExtent),(axis,pixeValue,otherExtent)下划线红色报错:…...
【Leetcode 热题 100】739. 每日温度
问题背景 给定一个整数数组 t e m p e r a t u r e s temperatures temperatures,表示每天的温度,返回一个数组 a n s w e r answer answer,其中 a n s w e r [ i ] answer[i] answer[i] 是指对于第 i i i 天,下一个更高温度…...
R数据分析:多分类问题预测模型的ROC做法及解释
有同学做了个多分类的预测模型,结局有三个类别,做的模型包括多分类逻辑回归、随机森林和决策树,多分类逻辑回归是用ROC曲线并报告AUC作为模型评估的,后面两种模型报告了混淆矩阵,审稿人就提出要统一模型评估指标。那么肯定是统一成ROC了,刚好借这个机会给大家讲讲ROC在多…...
如何用 SSH 访问 QNX 虚拟机
QNX 虚拟机默认是开启 SSH 服务的,如果要用 SSH 访问 QNX 虚拟机,就需要知道虚拟机的 IP 地址,用户和密码。本文我们来看看如何获取这些参数。 1. 启动虚拟机 启动过程很慢,请耐心等待。 2. 查看 IP 地址 等待 IDE 连接到虚拟机。…...
交响曲-24-3-单细胞CNV分析及聚类
CNV概述 小于1kb是常见的插入、移位、缺失等的变异 人体内包含<10% 的正常CNV,我们的染色体数是两倍体,正常情况下,只有一条染色体表达,另一条沉默,当表达的那条染色体发生CNV之后,表达数量就会成倍增加…...
java远程调试debug
文章目录 首先被调试的服务配置idea 中配置远程调试连接上被调试服务打断点开始调试 首先被调试的服务配置 被调试的 java 服务需要开启允许被远程调试的配置,具体就是启动脚本中,加上允许被远程调试以及相应端口 # 针对JDK15.-1.8 -agentlib:jdwptran…...
操作系统之系统调用
系统调用 从上文简介得知,操作系统是计算机硬件和软件之间的桥梁,通过管理计算机软件和硬件资源,最终为我们用户提供服务。就如同一个管家帮助我们对CPU(进程)的管理、内存的管理、设备的管理、文件的管理。而我们如何…...
【docker】exec /entrypoint.sh: no such file or directory
dockerfile生成的image 报错内容: exec /entrypoint.sh: no such file or directory查看文件正常在此路径,但是就是报错没找到。 可能是因为sh文件的换行符使用了win的。...
CAPL概述与环境搭建
CAPL概述与环境搭建 目录 CAPL概述与环境搭建1. CAPL简介与应用领域1.1 CAPL简介1.2 CAPL的应用领域 2. CANoe/CANalyzer 安装与配置2.1 CANoe/CANalyzer 简介2.2 安装CANoe/CANalyzer2.2.1 系统要求2.2.2 安装步骤 2.3 配置CANoe/CANalyzer2.3.1 配置CAN通道2.3.2 配置CAPL节点…...
ML-Agents:智能体(三)
注:本文章为官方文档翻译,如有侵权行为请联系作者删除 Agent - Unity ML-Agents Toolkit–原文链接> ML-Agents:智能体(一) ML-Agents:智能体(二) ML-Agents:智能体&a…...
【harbor】离线安装2.9.0-arm64架构服务制作和升级部署
执行: .prepare 【作用就是产生一些配置信息 和docker-compose.yaml文件,然后docker-compose发布docker】 harbor官网地址:Harbor 参考文档可以看这里:部署 harbor 2.10.1 arm64 - 简书。 前提环境准备: 安装docker 和 docker…...
可视化-Visualization
可视化-Visualization 1.Introduction Visualization in Open CASCADE Technology is based on the separation of: on the one hand – the data which stores the geometry and topology of the entities you want to display and select, andon the other hand – its pr…...
完整化安装kubesphere,ks-jenkins的状态一直为init
错误描述: 打印日志: kubectl describe pod ks-jenkins-7fcff7857b-gh4g5 -n kubesphere-devops-system 日志描述如下: Events: Type Reason Age From Message ---- ------ ---- …...
halcon三维点云数据处理(十)locate_cylinder_3d
目录 一、locate_cylinder_3d例程代码二、gen_binocular_rectification_map函数三、binocular_disparity函数四、自定义函数select_best_candidates五、自定义函数remove_shadowed_regions 一、locate_cylinder_3d例程代码 1、读取或者创建3D形状模型, 2、根据双目…...
【CSS】设置滚动条样式
文章目录 基本语法用法案例 基本语法 在CSS中,可以使用 ::-webkit-scrollbar 和相关伪元素来为滚动条设置样式,但请注意这些伪元素是非标准的,主要用于WebKit内核浏览器(如Chrome、Safari)。 ::-webkit-scrollbar CSS …...