修复资源泄露问题并优化资源管理

- 添加 AutoCloseable 接口实现,支持自动资源释放
   - 修复 Graphics2D、ImageOutputStream 等资源未正确关闭的问题
   - 为 Spring Bean 添加 destroyMethod 配置
   - 添加定时任务线程池的正确关闭逻辑
   - FontCache 添加缓存大小限制防止内存泄露
   - 优化 StandardWordClickImageCaptchaGenerator 代码结构
This commit is contained in:
天爱有情
2026-01-14 17:09:59 +08:00
parent db0603a124
commit 29279e8c56
24 changed files with 762 additions and 187 deletions
@@ -2,11 +2,15 @@ package cloud.tianai.captcha.spring.autoconfiguration;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.cache.impl.LocalCacheStore;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.impl.LocalMemoryResourceStore;
import cloud.tianai.captcha.spring.plugins.RedisResourceStore;
import cloud.tianai.captcha.spring.store.impl.RedisCacheStore;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@@ -17,8 +21,6 @@ import org.springframework.data.redis.core.StringRedisTemplate;
*
* @author Hccake
*/
@AutoConfigureAfter(name = {"org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration",
"org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration"})
@Configuration(proxyBeanMethods = false)
public class CacheStoreAutoConfiguration {
@@ -31,15 +33,23 @@ public class CacheStoreAutoConfiguration {
@Order(1)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(StringRedisTemplate.class)
@AutoConfigureAfter({RedisAutoConfiguration.class})
public static class RedisCacheStoreConfiguration {
@Bean
@Bean(destroyMethod = "")
@ConditionalOnBean(StringRedisTemplate.class)
@ConditionalOnMissingBean(CacheStore.class)
public CacheStore redis(StringRedisTemplate redisTemplate) {
return new RedisCacheStore(redisTemplate);
}
@Bean
@ConditionalOnBean(StringRedisTemplate.class)
@ConditionalOnMissingBean(ResourceStore.class)
public ResourceStore redisResourceStore(StringRedisTemplate redisTemplate) {
return new RedisResourceStore(redisTemplate);
}
}
/**
@@ -52,12 +62,18 @@ public class CacheStoreAutoConfiguration {
@Configuration(proxyBeanMethods = false)
public static class LocalCacheStoreConfiguration {
@Bean
@Bean(destroyMethod = "close")
@ConditionalOnMissingBean(CacheStore.class)
public CacheStore local() {
return new LocalCacheStore();
}
@Bean
@ConditionalOnMissingBean(ResourceStore.class)
public ResourceStore resourceStore() {
return new LocalMemoryResourceStore();
}
}
}
@@ -4,33 +4,31 @@ package cloud.tianai.captcha.spring.autoconfiguration;
import cloud.tianai.captcha.application.ImageCaptchaApplication;
import cloud.tianai.captcha.application.TACBuilder;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.common.util.CollectionUtils;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.impl.transform.Base64ImageTransform;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.interceptor.CaptchaInterceptorGroup;
import cloud.tianai.captcha.interceptor.impl.ParamCheckCaptchaInterceptor;
import cloud.tianai.captcha.interceptor.EmptyCaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceProviders;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.impl.DefaultImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.impl.LocalMemoryResourceStore;
import cloud.tianai.captcha.spring.common.util.URL;
import cloud.tianai.captcha.spring.plugins.SpringMultiImageCaptchaGenerator;
import cloud.tianai.captcha.spring.plugins.secondary.SecondaryVerificationApplication;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
/**
* @Author: 天爱有情
@@ -44,17 +42,13 @@ import org.springframework.core.annotation.Order;
@EnableConfigurationProperties({SpringImageCaptchaProperties.class})
public class ImageCaptchaAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ResourceStore resourceStore() {
return new LocalMemoryResourceStore();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(ResourceStore.class)
public ImageCaptchaResourceManager imageCaptchaResourceManager(ResourceStore resourceStore) {
ResourceProviders resourceProviders = new ResourceProviders();
return new DefaultImageCaptchaResourceManager(resourceStore,resourceProviders);
return new DefaultImageCaptchaResourceManager(resourceStore, resourceProviders);
}
@Bean
@@ -70,7 +64,9 @@ public class ImageCaptchaAutoConfiguration {
ImageCaptchaResourceManager captchaResourceManager,
ImageTransform imageTransform,
BeanFactory beanFactory) {
return new SpringMultiImageCaptchaGenerator(captchaResourceManager, imageTransform, beanFactory);
// 构建多验证码生成器
ImageCaptchaGenerator captchaGenerator = new SpringMultiImageCaptchaGenerator(captchaResourceManager, imageTransform, beanFactory);
return captchaGenerator;
}
@Bean
@@ -80,26 +76,24 @@ public class ImageCaptchaAutoConfiguration {
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean
public CaptchaInterceptor captchaInterceptor() {
CaptchaInterceptorGroup group = new CaptchaInterceptorGroup();
group.addInterceptor(new ParamCheckCaptchaInterceptor());
// group.addInterceptor(new BasicTrackCaptchaInterceptor());
return group;
return new EmptyCaptchaInterceptor();
}
@Bean
@Bean(destroyMethod = "close")
@ConditionalOnMissingBean
@ConditionalOnBean(CacheStore.class)
public ImageCaptchaApplication imageCaptchaApplication(ImageCaptchaGenerator captchaGenerator,
ImageCaptchaValidator imageCaptchaValidator,
CacheStore cacheStore,
ResourceStore resourceStore,
SpringImageCaptchaProperties prop,
CaptchaInterceptor captchaInterceptor,
ApplicationContext applicationContext) {
TACBuilder tacBuilder = TACBuilder.builder(resourceStore)
ApplicationContext applicationContext
) {
TACBuilder tacBuilder = TACBuilder.builder()
.setResourceStore(resourceStore)
.setGenerator(captchaGenerator)
.setValidator(imageCaptchaValidator)
.setCacheStore(cacheStore)
@@ -116,7 +110,10 @@ public class ImageCaptchaAutoConfiguration {
String[] split = index > 0 ? new String[]{fontPath.substring(0, index), fontPath.substring(index + 1)} : new String[]{"", fontPath};
String type = split[0];
String path = split[1];
tacBuilder.addFont(new cloud.tianai.captcha.resource.common.model.dto.Resource(type, path));
URL fontUrl = URL.valueOf(fontPath);
String tag = fontUrl.getParam(URL.PARAM_TAG_KEY, null);
tacBuilder.addFont(new cloud.tianai.captcha.resource.common.model.dto.Resource(type, path, tag));
}
}
ImageCaptchaApplication target = tacBuilder.build();