diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java index 874e2da..b8088b3 100644 --- a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java @@ -35,15 +35,25 @@ import java.util.concurrent.TimeUnit; @Slf4j public class DefaultImageCaptchaApplication implements ImageCaptchaApplication { private CaptchaInterceptor captchaInterceptor; - /** 图片验证码生成器. */ + /** + * 图片验证码生成器. + */ private ImageCaptchaGenerator captchaGenerator; - /** 图片验证码校验器. */ + /** + * 图片验证码校验器. + */ private ImageCaptchaValidator imageCaptchaValidator; - /** 缓冲存储. */ + /** + * 缓冲存储. + */ private CacheStore cacheStore; - /** 验证码配置属性. */ + /** + * 验证码配置属性. + */ private final ImageCaptchaProperties prop; - /** 默认的过期时间. */ + /** + * 默认的过期时间. + */ private long defaultExpire = 20000L; public static final String ID_SPLIT = "_"; @@ -69,9 +79,11 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication { } captchaGenerator.setInterceptor(this.captchaInterceptor); if (prop.isLocalCacheEnabled()) { - captchaGenerator = new CacheImageCaptchaGenerator(captchaGenerator, + CacheImageCaptchaGenerator cacheImageCaptchaGenerator = new CacheImageCaptchaGenerator(captchaGenerator, prop.getLocalCacheSize(), prop.getLocalCacheWaitTime(), prop.getLocalCachePeriod(), prop.getLocalCacheExpireTime()); + cacheImageCaptchaGenerator.setIgnoredCacheFields(prop.getLocalCacheIgnoredCacheFields()); + captchaGenerator = cacheImageCaptchaGenerator; } // 初始化生成器 captchaGenerator.init(); @@ -134,7 +146,7 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication { } // 生成ID - ApiResponse response = beforeGenerateImageCaptchaValidData( imageCaptchaInfo); + ApiResponse response = beforeGenerateImageCaptchaValidData(imageCaptchaInfo); if (response != null) { return response; } diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/ImageCaptchaProperties.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/ImageCaptchaProperties.java index 6af6aff..f572e08 100644 --- a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/ImageCaptchaProperties.java +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/ImageCaptchaProperties.java @@ -4,6 +4,7 @@ import lombok.Data; import java.util.HashMap; import java.util.Map; +import java.util.Set; /** * @Author: 天爱有情 @@ -23,4 +24,5 @@ public class ImageCaptchaProperties { private int localCacheWaitTime = 1000; private int localCachePeriod = 5000; private Long localCacheExpireTime; + private Set localCacheIgnoredCacheFields; } diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/TACBuilder.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/TACBuilder.java index 7c19809..0428ab4 100644 --- a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/TACBuilder.java +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/TACBuilder.java @@ -19,6 +19,7 @@ import cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; /** * @Author: 天爱有情 @@ -34,19 +35,18 @@ public class TACBuilder { private ImageCaptchaProperties prop = new ImageCaptchaProperties(); private ResourceStore resourceStore; private ImageTransform imageTransform; -// private List fontWrappers = new ArrayList<>(); + // private List fontWrappers = new ArrayList<>(); private Map resourceCache; private Map templateCache; public static TACBuilder builder() { - return TACBuilder.builder(); + return new TACBuilder(); } - - private TACBuilder(ResourceStore resourceStore) { - this.resourceStore = resourceStore; + private TACBuilder() { } + public TACBuilder setResourceStore(ResourceStore resourceStore) { this.resourceStore = resourceStore; return this; @@ -89,11 +89,17 @@ public class TACBuilder { public TACBuilder cached(int size, int waitTime, int period, Long expireTime) { + cached(size, waitTime, period, expireTime, null); + return this; + } + + public TACBuilder cached(int size, int waitTime, int period, Long expireTime, Set ignoredCacheFields) { prop.setLocalCacheEnabled(true); prop.setLocalCacheSize(size); prop.setLocalCacheWaitTime(waitTime); prop.setLocalCachePeriod(period); prop.setLocalCacheExpireTime(expireTime); + prop.setLocalCacheIgnoredCacheFields(ignoredCacheFields); return this; } @@ -182,6 +188,7 @@ public class TACBuilder { } resourceCache.put(captchaType, imageResource); } + private void cacheTemplate(String captchaType, ResourceMap resourceMap) { if (templateCache == null) { templateCache = new LinkedHashMap<>(8); diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/cache/impl/ConCurrentExpiringMap.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/cache/impl/ConCurrentExpiringMap.java index 7d5e12d..7a55aa0 100644 --- a/tianai-captcha/src/main/java/cloud/tianai/captcha/cache/impl/ConCurrentExpiringMap.java +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/cache/impl/ConCurrentExpiringMap.java @@ -15,7 +15,7 @@ import java.util.stream.Collectors; */ @Slf4j @Accessors(chain = true) -public class ConCurrentExpiringMap implements ExpiringMap { +public class ConCurrentExpiringMap implements ExpiringMap, AutoCloseable { private ConcurrentHashMap> storage; private SortedMap> sortedMap = new ConcurrentSkipListMap<>(); @@ -219,6 +219,15 @@ public class ConCurrentExpiringMap implements ExpiringMap { } } + /** + * 实现 AutoCloseable 接口,支持 try-with-resources 语法 + * 调用 destroy() 方法释放资源 + */ + @Override + public void close() { + destroy(); + } + /** * 定时执行任务 * diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/common/AnyMap.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/common/AnyMap.java index ed4d3d6..0c8e29b 100644 --- a/tianai-captcha/src/main/java/cloud/tianai/captcha/common/AnyMap.java +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/common/AnyMap.java @@ -10,6 +10,10 @@ import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; +/** + * @Author: 天爱有情 + * @Description 通用Map包装类,提供类型安全的get/set方法 + */ @EqualsAndHashCode public class AnyMap implements Map { @@ -19,99 +23,245 @@ public class AnyMap implements Map { target = new LinkedHashMap<>(); } + /** + * 构造函数 - 防御性拷贝 + * @param map 源Map(会被复制,不会共享引用) + */ public AnyMap(Map map) { - this.target = map; + this.target = map != null ? new LinkedHashMap<>(map) : new LinkedHashMap<>(); } + // ================== 类型转换方法 ======================= + + /** + * 获取Float值 + */ public Float getFloat(String key) { return getFloat(key, null); } - public Float getFloat(String key, Float defaultData) { + /** + * 获取Float值,支持默认值 + */ + public Float getFloat(String key, Float defaultValue) { + return convertToNumber(key, defaultValue, Number::floatValue, Float::parseFloat); + } + + /** + * 获取Integer值 + */ + public Integer getInt(String key) { + return getInt(key, null); + } + + /** + * 获取Integer值,支持默认值 + */ + public Integer getInt(String key, Integer defaultValue) { + return convertToNumber(key, defaultValue, Number::intValue, Integer::parseInt); + } + + /** + * 获取Long值 + */ + public Long getLong(String key) { + return getLong(key, null); + } + + /** + * 获取Long值,支持默认值 + */ + public Long getLong(String key, Long defaultValue) { + return convertToNumber(key, defaultValue, Number::longValue, Long::parseLong); + } + + /** + * 获取Double值 + */ + public Double getDouble(String key) { + return getDouble(key, null); + } + + /** + * 获取Double值,支持默认值 + */ + public Double getDouble(String key, Double defaultValue) { + return convertToNumber(key, defaultValue, Number::doubleValue, Double::parseDouble); + } + + /** + * 获取Boolean值 + */ + public Boolean getBoolean(String key) { + return getBoolean(key, null); + } + + /** + * 获取Boolean值,支持默认值 + */ + public Boolean getBoolean(String key, Boolean defaultValue) { Object data = get(key); - if (data != null) { - if (data instanceof Number) { - return ((Number) data).floatValue(); - } + if (data == null) { + return defaultValue; + } + if (data instanceof Boolean) { + return (Boolean) data; + } + if (data instanceof String) { + return Boolean.parseBoolean((String) data); + } + if (data instanceof Number) { + return ((Number) data).intValue() != 0; + } + return defaultValue; + } + + /** + * 获取String值 + */ + public String getString(String key) { + return getString(key, null); + } + + /** + * 获取String值,支持默认值 + */ + public String getString(String key, String defaultValue) { + Object data = get(key); + if (data == null) { + return defaultValue; + } + if (data instanceof String) { + return (String) data; + } + return String.valueOf(data); + } + + /** + * 通用数字类型转换方法(减少重复代码) + */ + private T convertToNumber( + String key, + T defaultValue, + Function numberConverter, + Function stringParser) { + Object data = get(key); + if (data == null) { + return defaultValue; + } + if (data instanceof Number) { + return numberConverter.apply((Number) data); + } + if (data instanceof String) { try { - if (data instanceof String) { - return Float.parseFloat((String) data); - } + return stringParser.apply((String) data); } catch (NumberFormatException e) { - throw e; + return defaultValue; } } - return defaultData; + return defaultValue; } - public Integer getInt(String key, Integer defaultData) { - Object data = get(key); - if (data != null) { - if (data instanceof Number) { - return ((Number) data).intValue(); - } - try { - if (data instanceof String) { - return Integer.parseInt((String) data); - } - } catch (NumberFormatException e) { - throw e; - } - } - return defaultData; - } - - public String getString(String key, String defaultData) { - Object data = get(key); - if (data != null) { - if (data instanceof String) { - return (String) data; - } - return String.valueOf(data); - } - return defaultData; - } - - - public static AnyMap of(Map map) { - return new AnyMap(map); - } - - - public void addParam(String key, Object value) { - put(key, value); - } - - public Object getParam(String key) { - return get(key); - } - - public Object removeParam(String key) { - return remove(key); - } - - public Object removeParam(ParamKey paramKey) { - return removeParam(paramKey.getKey()); - } + // ================== ParamKey 相关方法 ======================= + /** + * 添加参数(使用ParamKey) + */ public void addParam(ParamKey paramKey, T value) { - addParam(paramKey.getKey(), value); + put(paramKey.getKey(), value); } + /** + * 获取参数(使用ParamKey) + */ public T getParam(ParamKey paramKey) { return getParam(paramKey, null); } + /** + * 获取参数(使用ParamKey),支持默认值 + */ + @SuppressWarnings("unchecked") public T getParam(ParamKey paramKey, T defaultValue) { - return (T) getParam(paramKey.getKey()); - + Object value = get(paramKey.getKey()); + return value != null ? (T) value : defaultValue; } + /** + * 移除参数(使用ParamKey) + */ + public Object removeParam(ParamKey paramKey) { + return remove(paramKey.getKey()); + } + + /** + * 获取参数或默认值(使用ParamKey) + */ + @SuppressWarnings("unchecked") public T getOrDefault(ParamKey paramKey, T defaultValue) { return (T) getOrDefault(paramKey.getKey(), defaultValue); } - // ================== implement Map ======================= + // ================== 便捷方法 ======================= + + /** + * 添加参数(String key) + * 注意:这个方法等价于put(),保留是为了向后兼容 + */ + public void addParam(String key, Object value) { + put(key, value); + } + + /** + * 获取参数(String key) + * 注意:这个方法等价于get(),保留是为了向后兼容 + */ + public Object getParam(String key) { + return get(key); + } + + /** + * 移除参数(String key) + * 注意:这个方法等价于remove(),保留是为了向后兼容 + */ + public Object removeParam(String key) { + return remove(key); + } + + /** + * 链式调用 - 设置值并返回this + * @param key 键 + * @param value 值 + * @return this,支持链式调用 + */ + public AnyMap set(String key, Object value) { + put(key, value); + return this; + } + + /** + * 链式调用 - 使用ParamKey设置值 + */ + public AnyMap set(ParamKey paramKey, T value) { + put(paramKey.getKey(), value); + return this; + } + + /** + * 静态工厂方法 + */ + public static AnyMap of(Map map) { + return new AnyMap(map); + } + + /** + * 静态工厂方法 - 创建空Map + */ + public static AnyMap create() { + return new AnyMap(); + } + + // ================== implement Map ======================= @Override public int size() { diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/common/util/CaptchaTypeClassifier.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/common/util/CaptchaTypeClassifier.java index d79567e..0ed6503 100644 --- a/tianai-captcha/src/main/java/cloud/tianai/captcha/common/util/CaptchaTypeClassifier.java +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/common/util/CaptchaTypeClassifier.java @@ -1,5 +1,6 @@ package cloud.tianai.captcha.common.util; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -10,15 +11,22 @@ import java.util.Set; */ public class CaptchaTypeClassifier { + /** + * 每个类型集合的最大容量,防止内存泄漏 + */ + private static final int MAX_TYPE_SIZE = 100; + private static final Set SLIDER_CAPTCHA_TYPES = new HashSet<>(); private static final Set CLICK_CAPTCHA_TYPES = new HashSet<>(); private static final Set JIGSAW_CAPTCHA_TYPES = new HashSet<>(); public static void addSliderCaptchaType(String type) { + checkCapacity(SLIDER_CAPTCHA_TYPES, "SLIDER_CAPTCHA_TYPES"); SLIDER_CAPTCHA_TYPES.add(type.toUpperCase()); } public static void addClickCaptchaType(String type) { + checkCapacity(CLICK_CAPTCHA_TYPES, "CLICK_CAPTCHA_TYPES"); CLICK_CAPTCHA_TYPES.add(type.toUpperCase()); } @@ -31,11 +39,11 @@ public class CaptchaTypeClassifier { } public static Set getSliderCaptchaTypes() { - return SLIDER_CAPTCHA_TYPES; + return Collections.unmodifiableSet(SLIDER_CAPTCHA_TYPES); } public static Set getClickCaptchaTypes() { - return CLICK_CAPTCHA_TYPES; + return Collections.unmodifiableSet(CLICK_CAPTCHA_TYPES); } public static void removeSliderCaptchaType(String type) { @@ -51,6 +59,7 @@ public class CaptchaTypeClassifier { } public static void addJigsawCaptchaType(String type) { + checkCapacity(JIGSAW_CAPTCHA_TYPES, "JIGSAW_CAPTCHA_TYPES"); JIGSAW_CAPTCHA_TYPES.add(type.toUpperCase()); } @@ -59,6 +68,19 @@ public class CaptchaTypeClassifier { } public static Set getJigsawCaptchaTypes() { - return JIGSAW_CAPTCHA_TYPES; + return Collections.unmodifiableSet(JIGSAW_CAPTCHA_TYPES); + } + + /** + * 检查集合容量,防止内存泄漏 + * @param set 要检查的集合 + * @param name 集合名称,用于日志 + */ + private static void checkCapacity(Set set, String name) { + if (set.size() >= MAX_TYPE_SIZE) { + throw new IllegalStateException( + String.format("验证码类型集合 %s 已达到最大容量 %d,无法继续添加", name, MAX_TYPE_SIZE) + ); + } } } diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java index 3aca2fb..f4b2313 100644 --- a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java @@ -168,17 +168,21 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener protected BufferedImage getTemplateImage(ResourceMap templateImages, String imageName) { InputStream stream = getTemplateFile(templateImages, imageName); - BufferedImage bufferedImage = CaptchaImageUtils.wrapFile2BufferedImage(stream); - closeStream(stream); - return bufferedImage; + try { + return CaptchaImageUtils.wrapFile2BufferedImage(stream); + } finally { + closeStream(stream); + } } protected BufferedImage getResourceImage(Resource resource) { InputStream stream = getResourceInputStream(resource, null); - BufferedImage bufferedImage = CaptchaImageUtils.wrapFile2BufferedImage(stream); - closeStream(stream); - return bufferedImage; + try { + return CaptchaImageUtils.wrapFile2BufferedImage(stream); + } finally { + closeStream(stream); + } } protected int randomInt(int origin, int bound) { diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/common/model/dto/CacheKey.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/common/model/dto/CacheKey.java new file mode 100644 index 0000000..fec49d7 --- /dev/null +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/common/model/dto/CacheKey.java @@ -0,0 +1,69 @@ +package cloud.tianai.captcha.generator.common.model.dto; + +import java.util.*; + +/** + * @Author: 天爱有情 + * @Description 缓存键包装类,支持忽略指定字段 + */ +public class CacheKey { + + private final GenerateParam generateParam; + private final Set ignoredFields; + + /** + * 构造函数 + * + * @param generateParam 生成参数 + * @param ignoredFields 需要忽略的字段集合(不参与equals和hashCode计算) + */ + public CacheKey(GenerateParam generateParam, Set ignoredFields) { + if (generateParam == null) { + throw new IllegalArgumentException("generateParam 不能为 null"); + } + this.generateParam = generateParam; + this.ignoredFields = ignoredFields != null ? ignoredFields : Collections.emptySet(); + } + + /** + * 获取原始的GenerateParam + */ + public GenerateParam getGenerateParam() { + return generateParam; + } + + /** + * 获取参与缓存计算的有效字段 + */ + private Map getEffectiveFields() { + Map effectiveMap = new LinkedHashMap<>(); + for (Map.Entry entry : generateParam.entrySet()) { + if (!ignoredFields.contains(entry.getKey())) { + effectiveMap.put(entry.getKey(), entry.getValue()); + } + } + return effectiveMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CacheKey cacheKey = (CacheKey) o; + return Objects.equals(getEffectiveFields(), cacheKey.getEffectiveFields()); + } + + @Override + public int hashCode() { + return Objects.hash(getEffectiveFields()); + } + + @Override + public String toString() { + return "CacheKey{effectiveFields=" + getEffectiveFields() + "}"; + } +} diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java index 548ffd4..63a9bfa 100644 --- a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java +++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java @@ -3,6 +3,7 @@ package cloud.tianai.captcha.generator.impl; import cloud.tianai.captcha.common.util.NamedThreadFactory; import cloud.tianai.captcha.generator.ImageCaptchaGenerator; import cloud.tianai.captcha.generator.ImageTransform; +import cloud.tianai.captcha.generator.common.model.dto.CacheKey; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; import cloud.tianai.captcha.interceptor.CaptchaInterceptor; @@ -12,7 +13,7 @@ import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import java.util.Map; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -22,19 +23,31 @@ import java.util.concurrent.atomic.AtomicInteger; * @Description 滑块验证码缓冲器 */ @Slf4j -public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { +public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator, AutoCloseable { protected final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("slider-captcha-queue")); - protected Map> queueMap = new ConcurrentHashMap<>(8); - protected Map posMap = new ConcurrentHashMap<>(8); - protected Map lastUpdateMap = new ConcurrentHashMap<>(8); + protected Map> queueMap = new ConcurrentHashMap<>(8); + protected Map posMap = new ConcurrentHashMap<>(8); + protected Map lastUpdateMap = new ConcurrentHashMap<>(8); + /** + * 忽略的字段集合(不参与缓存key计算),默认为空 + */ + @Getter + @Setter + protected Set ignoredCacheFields = Collections.emptySet(); protected ImageCaptchaGenerator target; protected int size; - /** 等待时间,一般报错或者拉取为空时会休眠一段时间再试. */ + /** + * 等待时间,一般报错或者拉取为空时会休眠一段时间再试. + */ protected int waitTime = 1000; - /** 调度器检查缓存的间隔时间. */ + /** + * 调度器检查缓存的间隔时间. + */ protected int period = 5000; - /** 10天内没有任何操作就删除已缓存的数据. */ + /** + * 10天内没有任何操作就删除已缓存的数据. + */ protected long expireTime = TimeUnit.DAYS.toMillis(10); @Getter @Setter @@ -59,7 +72,7 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { this.size = size; this.waitTime = waitTime; this.period = period; - if (expireTime != null){ + if (expireTime != null) { this.expireTime = expireTime; } } @@ -77,15 +90,18 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { } this.size = z; // 初始化一个队列扫描 - scheduledExecutor.scheduleAtFixedRate(() -> queueMap.forEach((k, queue) -> { + scheduledExecutor.scheduleAtFixedRate(() -> queueMap.forEach((cacheKey, queue) -> { try { - AtomicInteger pos = posMap.computeIfAbsent(k, k1 -> new AtomicInteger(0)); + AtomicInteger pos = posMap.computeIfAbsent(cacheKey, k1 -> new AtomicInteger(0)); int addCount = 0; while (pos.get() < this.size) { if (pos.get() >= size) { return; } - ImageCaptchaInfo slideImageInfo = target.generateCaptchaImage(k); + GenerateParam generateParam = cacheKey.getGenerateParam(); + generateParam = beforeGenerateCaptchaImage(generateParam); + // 使用原始GenerateParam生成验证码 + ImageCaptchaInfo slideImageInfo = target.generateCaptchaImage(generateParam); if (slideImageInfo != null) { boolean addStatus = queue.offer(slideImageInfo); addCount++; @@ -99,20 +115,20 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { } if (addCount == 0) { // 没有添加,检测最新更新时间 如果时间过长,直接清除数据 - Long lastUpdate = lastUpdateMap.get(k); + Long lastUpdate = lastUpdateMap.get(cacheKey); if (lastUpdate != null && System.currentTimeMillis() - lastUpdate > expireTime) { - queueMap.remove(k); - posMap.remove(k); - lastUpdateMap.remove(k); + queueMap.remove(cacheKey); + posMap.remove(cacheKey); + lastUpdateMap.remove(cacheKey); } } } catch (Exception e) { // cache所有 log.error("缓存队列扫描时出错, ex", e); // 删掉它 - queueMap.remove(k); - posMap.remove(k); - lastUpdateMap.remove(k); + queueMap.remove(cacheKey); + posMap.remove(cacheKey); + lastUpdateMap.remove(cacheKey); // 休眠 sleep(); } @@ -120,6 +136,7 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { init = true; } + private void sleep() { try { TimeUnit.MILLISECONDS.sleep(waitTime); @@ -131,7 +148,8 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { public ImageCaptchaGenerator init() { ImageCaptchaGenerator captchaGenerator = target.init(); // 初始化缓存 - init(size);; + init(size); + return captchaGenerator; } @@ -145,21 +163,25 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { @SneakyThrows public ImageCaptchaInfo generateCaptchaImage(GenerateParam generateParam, boolean requiredGetCaptcha) { - ConcurrentLinkedQueue queue = queueMap.get(generateParam); + // 创建CacheKey + CacheKey cacheKey = new CacheKey(generateParam, ignoredCacheFields); + + ConcurrentLinkedQueue queue = queueMap.get(cacheKey); ImageCaptchaInfo captchaInfo = null; if (queue != null) { captchaInfo = queue.poll(); if (captchaInfo == null) { log.warn("滑块验证码缓存不足, genParam:{}", generateParam); } else { - AtomicInteger pos = posMap.get(generateParam); + AtomicInteger pos = posMap.get(cacheKey); if (pos != null) { pos.decrementAndGet(); } } } else { - queueMap.putIfAbsent(generateParam, new ConcurrentLinkedQueue<>()); - posMap.putIfAbsent(generateParam, new AtomicInteger(0)); + cacheKey = beforeAddQueue(cacheKey); + queueMap.putIfAbsent(cacheKey, new ConcurrentLinkedQueue<>()); + posMap.putIfAbsent(cacheKey, new AtomicInteger(0)); } if (captchaInfo == null && requiredGetCaptcha) { // 直接生成 不走缓存 @@ -167,11 +189,12 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { } if (captchaInfo != null) { // 记录最新时间 - lastUpdateMap.put(generateParam, System.currentTimeMillis()); + lastUpdateMap.put(cacheKey, System.currentTimeMillis()); } return captchaInfo; } + @Override public ImageCaptchaInfo generateCaptchaImage(String type, String targetFormatName, String matrixFormatName) { return generateCaptchaImage(GenerateParam.builder().type(type).backgroundFormatName(targetFormatName).templateFormatName(matrixFormatName).build(), true); @@ -236,4 +259,37 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { lastUpdateMap.clear(); } + /** + * 实现 AutoCloseable 接口,支持 try-with-resources 语法 + * 调用 destroy() 方法释放资源 + */ + @Override + public void close() { + destroy(); + } + + + //=============== 模板方法 ============= + + + /** + * 添加到队列前扩展的函数 + * + * @param cacheKey cacheKey + * @return CacheKey + */ + public CacheKey beforeAddQueue(CacheKey cacheKey) { + return cacheKey; + } + + + /** + * 生成验证码前调用的函数, 方便子类扩展 + * + * @param generateParam generateParam + * @return GenerateParam + */ + public GenerateParam beforeGenerateCaptchaImage(GenerateParam generateParam) { + return generateParam; + } }