普通spring项目配置加密

概述

本文主要介绍普通spring项目(非springboot)怎么进行配置加密。

出于安全考虑,生产配置不能明文出现在配置文件中。对于SpringBoot可以使用jasypt-spring-boot这个组件来为配置属性提供加密。

普通的spring项目暂时就没有找到合适的加密工具。这时候那就只能自己造轮子了。

在造轮子前,可以看下jasypt-spring-boot是怎么实现的。

1.Spring boot 配置加密

spring boot配置加密比较简单,使用jasypt-spring-boot可以非常简单就能实现,具体参考 https://github.com/ulisesbocchio/jasypt-spring-boot 。这方面资料很多就不多赘述了。接下来我们主要了解下jasypt-spring-boot是怎么实现配置加密的,方便我们对普通的spring项目进行使用。如果只是使用可以直接看 后面的 3.普通spring 项目配置加密 具体实现

1.1 spring.factories 配置

了解了spring boot都知道,springboot有个相当好用的特性,叫做自动装配。就是springboot会自动加载引入的starter库,无需像传统的spring项目那些加大量的配置和引入代码。这个机制是会扫描引入库META-INF中的spring.factories文件,加载配置的的class name进行实例化。所以我们先找jasypt-spring-boot-starter的spring.factories文件。
在这里插入图片描述

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringCloudBootstrapConfiguration

由于我们使用的是spring boot所以 加载的类为 com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration。

1.2 JasyptSpringBootAutoConfiguration的加载

@Configuration
@Import(EnableEncryptablePropertiesConfiguration.class)
public class JasyptSpringBootAutoConfiguration {
}

JasyptSpringBootAutoConfiguration 没做啥,主要就 Import了 EnableEncryptablePropertiesConfiguration类,我们继续看 EnableEncryptablePropertiesConfiguration。

@Configuration
@Import({EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class})
@Slf4j
public class EnableEncryptablePropertiesConfiguration {

    @Bean
    public static EnableEncryptablePropertiesBeanFactoryPostProcessor enableEncryptablePropertySourcesPostProcessor(final ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter) {
        return new EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, converter);
    }
}

EnableEncryptablePropertiesConfiguration 做了3件事

  1. imoirt EncryptablePropertyResolverConfiguration
  2. imprt CachingConfiguration
  3. 创建了一个 EnableEncryptablePropertiesBeanFactoryPostProcessor bean对象

下面分别进行说明下:

EncryptablePropertyResolverConfiguration类负责创建各种加密解密需要用到的bean对象。

CachingConfiguration 用于刷新 properties信息,主要在cloud 场景下使用,如nacos配置刷新。通过实现ApplicationListener 监听配置变化。

EnableEncryptablePropertiesBeanFactoryPostProcessor 实现BeanFactoryPostProcessor,Ordered。BeanFactoryPostProcessor的主要作用是在Spring容器实例化bean之前,对bean的定义进行修改。这意味着我们可以在bean实例化之前,对bean的定义进行一些自定义的修改。

接下来我们重点关注 EnableEncryptablePropertiesBeanFactoryPostProcessor#postProcessBeanFactory。

1.3 postProcessBeanFactory 修改PropertySource

@Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        LOG.info("Post-processing PropertySource instances");
        //获取PropertySources
        MutablePropertySources propSources = environment.getPropertySources();
        //转换PropertySources
        converter.convertPropertySources(propSources);
    }

继续进入 converter.convertPropertySources(propSources),这个方法将propSources 进行了steam操作,这个sream操作主要做了俩件事,将PropertySource 进行了转换,然后将转换后的PropertySource 替换原有的PropertySource;就是将原有的属性配置信息进行了处理,替换了原有的PropertySource 对象。

public void convertPropertySources(MutablePropertySources propSources) {
        StreamSupport.stream(propSources.spliterator(), false)
                .filter(ps -> !(ps instanceof EncryptablePropertySource))
                //进行转换
                .map(this::makeEncryptable)
                .collect(toList())
            	//替换原有的资源对象
                .forEach(ps -> propSources.replace(ps.getName(), ps));
    }

继续看 makeEncryptable,返回一个新的PropertySource。

public <T> PropertySource<T> makeEncryptable(PropertySource<T> propertySource) {
        if (propertySource instanceof EncryptablePropertySource || skipPropertySourceClasses.stream().anyMatch(skipClass -> skipClass.equals(propertySource.getClass()))) {
            log.info("Skipping PropertySource {} [{}", propertySource.getName(), propertySource.getClass());
            return propertySource;
        }
    	// 转换一个新的 PropertySource
        PropertySource<T> encryptablePropertySource = convertPropertySource(propertySource);
        log.info("Converting PropertySource {} [{}] to {}", propertySource.getName(), propertySource.getClass().getName(),
                AopUtils.isAopProxy(encryptablePropertySource) ? "AOP Proxy" : encryptablePropertySource.getClass().getSimpleName());
        return encryptablePropertySource;
    }

具有实现看convertPropertySource 方法

private <T> PropertySource<T> convertPropertySource(PropertySource<T> propertySource) {
        return interceptionMode == InterceptionMode.PROXY
                ? proxyPropertySource(propertySource) : instantiatePropertySource(propertySource);
    }

默认使用WRAPPER 模式,我们主要看instantiatePropertySource

private <T> PropertySource<T> instantiatePropertySource(PropertySource<T> propertySource) {
        PropertySource<T> encryptablePropertySource;
        if (needsProxyAnyway(propertySource)) {
            encryptablePropertySource = proxyPropertySource(propertySource);
        } else if (propertySource instanceof  SystemEnvironmentPropertySource) {
            encryptablePropertySource = (PropertySource<T>) new EncryptableSystemEnvironmentPropertySourceWrapper((SystemEnvironmentPropertySource) propertySource, propertyResolver, propertyFilter);
        } else if (propertySource instanceof MapPropertySource) {
            encryptablePropertySource = (PropertySource<T>) new EncryptableMapPropertySourceWrapper((MapPropertySource) propertySource, propertyResolver, propertyFilter);
        } else if (propertySource instanceof EnumerablePropertySource) {
            encryptablePropertySource = new EncryptableEnumerablePropertySourceWrapper<>((EnumerablePropertySource) propertySource, propertyResolver, propertyFilter);
        } else {
            encryptablePropertySource = new EncryptablePropertySourceWrapper<>(propertySource, propertyResolver, propertyFilter);
        }
        return encryptablePropertySource;
    }

这里将propertySource包装为各种PropertySource wrapper.通过包装后的PropertySource 就能够进行解密了。

1.4 配置解密

spring框架读取配置信息时是通过PropertySource 的getProperty获取。

我们进入其中一个EncryptablePropertySourceWrapper 查看

@Override
    public Object getProperty(String name) {
        return encryptableDelegate.getProperty(name);
    }

继续进入EncryptablePropertySource#getProperty,

进行了两个动作 1.获取配置内容2.进行解密

default Object getProperty(EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter, PropertySource<T> source, String name) {
    	// 获取配置内容
        Object value = source.getProperty(name);
        if (filter.shouldInclude(source, name) && value instanceof String) {
            String stringValue = String.valueOf(value);
            // 这里就是进行解密
            return resolver.resolvePropertyValue(stringValue);
        }
        return value;
    }

resolvePropertyValue 就是的解密方法。
在这里插入图片描述
它有两个默认实现,最后一个是我自定义实现的解密。

这个方法就是判断是否是已经加密的value,如果是,则进行解密。如果不是,那就返回原值。

 @Override
    public String resolvePropertyValue(String value) {
        return Optional.ofNullable(value)
                .filter(detector::isEncrypted)		// 如果经过属性探测器确认的,才继续
                .map(resolvedValue -> {
                    try {
                        String unwrappedProperty = detector.unwrapEncryptedValue(resolvedValue.trim());	// 过滤加密规则后的字符串
                        return encryptor.decrypt(unwrappedProperty);	// 解密
                    } catch (EncryptionOperationNotPossibleException e) {
                        throw new DecryptionException("Unable to decrypt: " + value + ". Decryption of Properties failed,  make sure encryption/decryption " +
                                "passwords match", e);
                    }
                })
                .orElse(value);
    }

1.5流程说明

通过上面我们发现 jasypt-spring-boot 的大致实现流程如下:

1.自动装配初始化JasyptSpringBootAutoConfiguration,生成解密相关的bean

2.通过EnableEncryptablePropertiesBeanFactoryPostProcessor#postProcessBeanFactory 覆盖原有PropertySource(重写了getProperty方法),使新的PropertySource具有解密能力

3.spring加载配置信息时调用新PropertySource getProperty,由于新PropertySource 重写了getProperty,所以可以进行解密。
在这里插入图片描述
解密实际上就是调用应用中的PropertySource#getProperty方法。

2.解密详细分析

说明解密前我们得先了解下 @Value 和 配置${}是如何实现的。

在spring 3.1以前

是通过 PropertyPlaceholderConfigurer 实现的。
在这里插入图片描述
上面是以@value注解为例,xml中使用也是同样的逻辑。只是注入的入口不一样。

最后获取配置内容时调用的PropertyPlaceholderConfigurer.PropertyPlaceholderConfigurerResolver#resolvePlaceholder 方法

private class PropertyPlaceholderConfigurerResolver implements PlaceholderResolver {
        private final Properties props;

        private PropertyPlaceholderConfigurerResolver(Properties props) {
            this.props = props;
        }

        public String resolvePlaceholder(String placeholderName) {
            return PropertyPlaceholderConfigurer.this.resolvePlaceholder(placeholderName, this.props, PropertyPlaceholderConfigurer.this.systemPropertiesMode);
        }
    }

实际最后取得是props 中得值。那这个props是什么时候写入的呢?

答案是:PropertyPlaceholderConfigurer中得PropertyPlaceholderConfigurerResolver对象是在PropertyPlaceholderConfigurer#processProperties方法进行初始化的。这点很关键,后面我们方案3就是利用到这个机制。

在spring 3.1及以后

是通过 PropertySourcesPlaceholderConfigurer 实现的。为什么这么说呢?大家看这个栈信息。
在这里插入图片描述
可以看到获取值最终是在PropertySourcesPropertyResolver的this.propertySources中获取,而propertySources来自哪里呢?

答案是:PropertySourcesPlaceholderConfigurer中得postProcessBeanFactory方法中通过

processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));

传入的。我们执行查看下postProcessBeanFactory方法

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		if (this.propertySources == null) {
			this.propertySources = new MutablePropertySources();
			if (this.environment != null) {
                // 1.加载原有 Environment 属性信息
				this.propertySources.addLast(
					new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
						@Override
						@Nullable
						public String getProperty(String key) {
							return this.source.getProperty(key);
						}
					}
				);
			}
			try {
                // 2.通过 mergeProperties 方法载入 resources目录内配置信息
				PropertySource<?> localPropertySource =
						new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
				if (this.localOverride) {
					this.propertySources.addFirst(localPropertySource);
				}
				else {
					this.propertySources.addLast(localPropertySource);
				}
			}
			catch (IOException ex) {
				throw new BeanInitializationException("Could not load properties", ex);
			}
		}
		//3.创建 PropertySourcesPropertyResolver 传入propertySources
		processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
		this.appliedPropertySources = this.propertySources;
	}

processProperties这个方法需要我们重点关注下,它有两个参数beanFactory 是一个ConfigurableListableBeanFactory(实现类DefaultListableBeanFactory)(bean工厂,在这个场景中负责初始化bean实例)对象,PropertySourcesPropertyResolver 是获取properties属性的类。

看到beanFactory 这里其实 我们可以知道processProperties 方法是将PropertySourcesPropertyResolver 提供给beanFactory用来在bean初始化时给bean对象的属性进行赋值。

最终会调用 PlaceholderConfigurerSupport 的 doProcessProperties方法。

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
			StringValueResolver valueResolver) {

		BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

		String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
		for (String curName : beanNames) {
			// Check that we're not parsing our own bean definition,
			// to avoid failing on unresolvable placeholders in properties file locations.
			if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
				BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
				try {
					visitor.visitBeanDefinition(bd);
				}
				catch (Exception ex) {
					throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
				}
			}
		}
		// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
		beanFactoryToProcess.resolveAliases(valueResolver);
		// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
		beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
	}

PlaceholderConfigurerSupport 这个其实PropertyPlaceholderConfigurer也有继承。也就是说PropertyPlaceholderConfigurer 和 PropertySourcesPlaceholderConfigurer 都是基于 PlaceholderConfigurerSupport 实现的。

最后我们简单总结一下。

调用过程大致有几个关键节点点如下:
在这里插入图片描述

  1. DefaultListableBeanFactory 开始进行初始化bean

  2. AutowiredAnnotationBeanPostProcessor 寻找@value注解,准备加载属性值

  3. DefaultListableBeanFactory 进行属性值加载
    在这里插入图片描述

    可以看到他是通过Resolver来获取值的

  4. PropertySourcesPropertyResolver 最终加载propertySources 的属性值

其中 PropertySourcesPropertyResolver #getProperty获取的propertySources 信息来自于PropertySourcesPlaceholderConfigurer中的postProcessBeanFactory传入。

而postProcessBeanFactory 有做了三件事

1.加载原有 Environment 属性信息

2.通过 PropertiesLoaderSupport的mergeProperties 方法载入 resources目录内配置信息

3.创建 PropertySourcesPropertyResolver 传入propertySources,通过 PropertiesLoaderSupport的doProcessProperties方法进行加载

看到这里我们不难分析出我们想要获取到解密后的属性信息,我们可以从这几个方向着手。

1.改变 Environment 中的propertySources信息

2.改变mergeProperties 加载的propertes

3.创建自定义的 ConfigurablePropertyResolver,目的就是改变对象里面的 props

下面我们根据这几个方向分别进行实现。

3.普通spring 项目配置加密

spring 配置加密有几种方案

方案1. 通过 environment 实现

官方建议的方式,通过我们对jasypt-spring-boot分析, 这个其实就是jasypt-spring-boot的实现方式。我们简单仿照来一波就可以了。

通过实现BeanFactoryPostProcessor我们实现 postProcessBeanFactory实现改变 Environment 中的propertySources信息。

public class EnableEncryptablePropertiesBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {

    private final ConfigurableEnvironment environment;

    public EnableEncryptablePropertiesBeanFactoryPostProcessor(StandardEnvironment environment) {
        this.environment = environment;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        MutablePropertySources propSources = this.environment.getPropertySources();
        StreamSupport.stream(propSources.spliterator(), false)
                .filter(ps -> !(ps instanceof EncryptablePropertySource))
                .map(this::makeEncryptable)
                .collect(toList())
                .forEach(ps -> propSources.replace(ps.getName(), ps));
    }

    @Override
    public int getOrder() {
        return 2147483547;
    }
	/**
     * 包装自定义的PropertySource指定解析器
     * @param propertySource
     * @param <T>
     * @return
     */
    public <T> PropertySource<T> makeEncryptable(PropertySource<T> propertySource) {
        if (propertySource instanceof EncryptablePropertySource ) {
            return propertySource;
        }
        PropertySource<T> encryptablePropertySource = new EncryptablePropertySourceWrapper(propertySource,new EncryptablePropertyResolver());
        return encryptablePropertySource;
    }
}

具体代码可以查看

源码:https://github.com/AndsGo/spring-conf-encryption/tree/main/spring5

方案2.改变PropertySourcesPlaceholderConfigurer 加载的properties

我们知道spring context:property-placeholder/ 标签是用来加载配置文件的。

我们可以查看 property-placeholder xsd定义 发现:
在这里插入图片描述

他本质上是加载 PropertySourcesPlaceholderConfigurer(spring 3.1 之后,3.1之前是加载 PropertyPlaceholderConfigurer ),

它有那些属性可以配置内,我们继续看propertyLoading。
在这里插入图片描述
property-placeholder 可以配置如下属性:

(1)location:表示属性文件位置,多个之间通过如逗号/分号等分隔;
(2)file-encoding:文件编码;
(3)ignore-resource-not-found:如果属性文件找不到,是否忽略,默认false,即不忽略,找不到将抛出异常
(4)ignore-unresolvable:是否忽略解析不到的属性,如果不忽略,找不到将抛出异常
(5)properties-ref:本地java.util.Properties配置
(6)local-override:是否本地覆盖模式,即如果true,那么properties-ref的属性将覆盖location加载的属性
(7)system-properties-mode:系统属性模式,ENVIRONMENT(默认),NEVER,OVERRIDE
(8)ENVIRONMENT:将使用Spring 3.1提供的PropertySourcesPlaceholderConfigurer,其他情况使用Spring 3.1之前的PropertyPlaceholderConfigurer
(9)OVERRIDE:PropertyPlaceholderConfigurer使用,因为在spring 3.1之前版本是没有Enviroment的,所以OVERRIDE是spring 3.1之前版本的Environment
(10)NEVER:只查找properties-ref、location;
(11)order:当配置多个<context:property-placeholder/>时的查找顺序

我们在这里重点关注properties-ref,这里可以配置我们自定义的 Properties,我们可以在这里面进行解密操作。

自定义:DataSourceProperties

public class DataSourceProperties extends Properties {
    private String location;
    /**
     * 构造方法
     * 通过构造方法,在Properties初始化时就是进行配置解密
     * @param location 需要解密的属性名称
     */
    public DataSourceProperties(String location) throws IOException {
        String[] split = location.split(",");
        for (String loc : split) {
            Properties properties = new Properties();
            properties.load(DataSourceProperties.class.getClassLoader()
                    .getResourceAsStream(loc));
            Enumeration<?> propertyNames = properties.propertyNames();
            while (propertyNames.hasMoreElements()) {
                String key = propertyNames.nextElement().toString();
                String value = properties.getProperty(key);
                // 进行解密操作
                this.setProperty(key, EncryptablePropertyResolver.resolvePropertyValue(value));
            }
        }
    }
}

配置applicationContext.xml

	<bean id="dataSourceProperties" class="com.DataSourceProperties">
		<constructor-arg name="location" value="constant.properties,constant1.properties"/>
	</bean>
	<context:property-placeholder properties-ref="dataSourceProperties"/>

我们将自定义的dataSourceProperties 初始化给到 property-placeholder。PropertySourcesPlaceholderConfigurer 会在 mergeProperties 方法中将我们自定义的 Properties(已经解密) 加载进入 PropertySources 中。

https://github.com/AndsGo/spring-conf-encryption/tree/main/spring5-1

方案3.重写PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer 继承自PlaceholderConfigurerSupport,它可以用于解析 bean 定义属性值中的占位符。实现值从属性文件或其他属性源提取到 bean 定义中。

因此我们可以通过重写PropertyPlaceholderConfigurer,来进行的配置的解密。

特别注意 super.processProperties(beanFactory, props);它就是注册ConfigurablePropertyResolver,我们重写覆盖了props的属性值,存入了已经解密的属性值。

public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
    private String prefix = "ABC(";
    private String suffix = ")";
    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactory,
                                     Properties props) throws BeansException {
        try {
            // 实际使用中我们会将密钥放入环境变量中
			//String encryption_key = System.getenv("XXX_KEY");
            String encryption_key = "12345678";
            for (Object key : props.keySet()) {
                if(isEncrypted(props.getProperty(key.toString()))){
                    //配置解密
                    props.setProperty(key.toString(), EncryptUtil.decrypt(unwrapEncryptedValue(props.getProperty(key.toString())),encryption_key));
                }
            }
            //注册 ConfigurablePropertyResolver
            super.processProperties(beanFactory, props);
        } catch (Exception e) {
            throw new BeanInitializationException(e.getMessage());
        }
    }
    private boolean isEncrypted(String property) {
        if (property == null) {
            return false;
        }
        final String trimmedValue = property.trim();
        return (trimmedValue.startsWith(prefix) &&
                trimmedValue.endsWith(suffix));
    }
    private String unwrapEncryptedValue(String property) {
        return property.substring(
                prefix.length(),
                (property.length() - suffix.length()));
    }
}

重写了后我们需要在spring配置文件中将其初始化,覆盖默认的 bean,propertyPlaceholderConfigurer

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
						http://www.springframework.org/schema/context
						http://www.springframework.org/schema/context/spring-context.xsd">
	<bean id="propertyPlaceholderConfigurer" class="com.EncryptPropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:constant.properties</value>
			</list>
		</property>
	</bean>
	<!-- 扫描对应包下所有使用注解的类型 -->
	<context:component-scan base-package="com"/>
</beans>

这样就是可以实现解密了。

需要注意的是PropertyPlaceholderConfigurer 在sppring 5.2后面过时,官方不建议使用。
在这里插入图片描述

源码: https://github.com/AndsGo/spring-conf-encryption/tree/main/spring3

最后我们总结下这三种方式:

1.通过 environment 实现 这种方式代码复杂,但是相当灵活,可以实现多种业场景jasypt-spring-boot就是用这种方式,也是官方推荐的方式。

2.改变mergeProperties 加载的propertes 和 3.创建自定义的 ConfigurablePropertyResolver,目的就是改变对象里面的 props

这两种反式代码比较简单,但是可操作性就很弱了,基本只能进行Properties值得调整。但是简单,业务简单场景用这两种方式还是比较方便。

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

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

相关文章

Banana Pi BPI-R4开源路由器开发板快速上手用户手册,采用联发科MT7988芯片设计

介绍 Banana Pi BPI-R4 路由器板采用 MediaTek MT7988A (Filogic 880) 四核 ARM Corex-A73 设计&#xff0c;4GB DDR4 RAM&#xff0c;8GB eMMC&#xff0c;板载 128MB SPI-NAND 闪存&#xff0c;还有 2x 10Gbe SFP、4x Gbe 网络端口&#xff0c;带 USB3 .2端口&#xff0c;M.2…

basic CNN

文章目录 回顾卷积神经网络卷积卷积核卷积过程卷积后图像尺寸计算公式&#xff1a;代码 padding代码 Stride代码 MaxPooling代码 一个简单的卷积神经网络用卷积神经网络来对MINIST数据集进行分类如何使用GPU代码 练习 回顾 下面这种由线形层构成的网络是全连接网络。 对于图像…

Codesys与威纶通触摸屏标签通信步骤

codesys软件&#xff0c;添加对象结构体。 添加对象全局变量列表。设置变量列表属性。 添加对象符号配置&#xff0c;编译。 勾选变量&#xff0c;编译。 文件夹出现xml文件。 打开威纶通软件&#xff0c;添加设备&#xff0c;导入标签&#xff0c;选择上图的文件。

RX-4571LC/NB/SA实时时钟模块

RX-4571LC实时时钟模块是EPSON推出的一求款额定频率32.768KHz&#xff0c;接口为SPI(3-wire)&#xff0c;月偏差为60 s的实时时钟模块&#xff0c;12脚贴片&#xff0c;具有小尺寸&#xff0c;高稳定性。该款实时时钟模块&#xff0c;可以在-40~85 C的温度内稳定工作,频率公差为…

解决Could not transfer artifact org.springframework.boot的问题

进行maven更新的时候&#xff0c;发现报错了 Could not transfer artifact org.springframework.boot&#xff0c;提示网络错误&#xff0c;搜了一下&#xff0c;应该是要忽略https 在maven设置中添加如下语句 -Dmaven.wagon.http.ssl.insecuretrue -Dmaven.wagon.http.ssl.a…

Redis(十)SpringBoot集成Redis

文章目录 连接单机mvnYMLController.javaRedisConfig.java 连接集群YML问题复现 RedisTemplate方式 连接单机 mvn <!--Redis--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</art…

[C++]类和对象(上)

目录 一:面向过程与面向对象的区别 二:类的定义 三:类的访问限定符和封装 3.1访问限定符 3.2 封装 四:类的实例化 五:类对象模型 如何计算类的大小 类对象的存储方式 六:this指针 this指针的引出 this指针的特性 一:面向过程与面向对象的区别 面向过程 C语言是面…

C#: 软件任务栏托盘图标添加关闭软件菜单等

说明&#xff1a;在软件在任务栏右下角的系统托盘的图标添加个右键弹出菜单功能&#xff0c;案例实现右键弹窗菜单关闭软件功能。 1.添加系统托盘图标控件 NotifyIcon 2.右键打开控件属性菜单添加鼠标点击事件函数 3.事件函数添加代码 //右键点击任务栏图标弹出关闭菜单 priv…

LeetCode Hot100 回顾(二)

子串 560.和为K的子数组 使用前缀和预处理一下题目给的数组, 然后用二重循环遍历一遍就可以了。 239.滑动窗口最大值 看题面比较容易想到的是用优先级队列来解决, 但是STL中的priority_queue不支持随机删除, 如果要用优先级队列来解决这道题的话比较复杂。这道题的一种正确…

leetcode刷题(剑指offer) 82.删除排序链表中的重复元素Ⅱ

82.删除排序链表中的重复元素Ⅱ 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5]示例 2&#xff1a…

数据监测的频次应如何设定

品牌在做控价时&#xff0c;需要先对线上数据进行监测&#xff0c;监测就要考虑监测的时间点&#xff0c;是白天监测还是夜晚监测&#xff0c;或者一天要监测几轮&#xff0c;这些问题都需要提前考虑好&#xff0c;因为待监测结果出来还要做破价治理&#xff0c;所以时间结点必…

PyCharm安装教程(超详细),零基础小白也能看懂

一、简介 PyCharm是一款Python IDE&#xff0c;其带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具&#xff0c;比如&#xff0c; 调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制等等。此外&#xff0c;该IDE提供了一些高级功…

DAIL-SQL:LLM在Text-to-SQL任务中的详细评估

导语 本文聚焦于利用LLMs进行Text-to-SQL任务&#xff0c;并指出缺乏系统性基准测试限制了有效、高效和经济的LLM-based Text-to-SQL解决方案的发展。研究者首先系统地比较了现有的提示工程方法&#xff0c;并分析了它们的优缺点。基于这些发现&#xff0c;提出了一个新的综合…

高等数学基础【1】极限与连续

第一节 函数 一、基本概念 ①函数 设变量x的取值范围为D,若对任意的x∈D,按照某种对应关系总有唯一确定的值y与x对应,称y为x的函数,记为yf(z),其中D称为函数yf(x)的定义域 ②复合函数 设uφ(x)(x∈D1),yf(u)(u∈D,),且对任意的x∈D1有φ(x)∈D2,称y为x的复合函数,记为yf[φ…

iText操作pdf

最近有个任务是动态的创建pdf根据获取到的内容&#xff0c;百度到的知识点都比较零散&#xff0c;官方文档想必大家也不容易看懂。下文是我做出的汇总 public class CreatePdfUtils {public static void create(){//准备File file new File("C:\\code\\base-project-back…

pytorch学习笔记(十二)

以下代码是以CIFAR10这个10分类的图片数据集训练过程的完整的代码。 训练部分 train.py主要包含以下几个部件&#xff1a; 准备训练、测试数据集用DateLoader加载两个数据集&#xff0c;要设置好batchsize创建网络模型&#xff08;具体模型在model.py中&#xff09;设置损失函…

【大数据】Flink SQL 语法篇(三):窗口聚合(TUMBLE、HOP、SESSION、CUMULATE)

Flink SQL 语法篇&#xff08;三&#xff09;&#xff1a;窗口聚合 1.滚动窗口&#xff08;TUMBLE&#xff09;1.1 Group Window Aggregation 方案&#xff08;支持 Batch / Streaming 任务&#xff09;1.2 Windowing TVF 方案&#xff08;1.13 只支持 Streaming 任务&#xff…

自己实现的小功能

小功能实现 2024/1/31 问题一&#xff1a; 将文本模式的csv文件作为表编辑之后&#xff0c;先要再变回来。找了5分钟都没找到&#xff0c;去网上搜也没搜到 解决方案 复制一份&#xff0c;对没错。 不是把表遍历一遍&#xff0c;重新将数据写入。 3.5给的答案就是重新写入…

网络防御基础介绍和基本的策略集

1.什么是防火墙 防火墙的主要职责在于&#xff1a;控制和防护 --- 安全策略 --- 防火墙可以根据安全策略来抓取流量之后做出对应的动作。 2.防火墙的分类 防火墙吞吐量 --- 防火墙同一时间处理的数据量 3.防火墙的历史 4.防火墙的分类 1.基于数据包的防火墙-包过滤防火墙 缺…

深度学习(9)--pydot库和graphviz库安装流程详解

目录 一.pydot库安装 二.graphviz库安装 一.pydot库安装 pydot的安装可直接在编译器安装相关包&#xff0c;以PyCharm举例&#xff1a; 如果搜索可用软件包显示为空&#xff0c;记得在此处把使用Conda软件包管理器”点亮 二.graphviz库安装 点击链接下载安装包graphviz-2.38…