feat: CacheImageCaptchaGenerator支持自定义忽略某些参数不参与校验

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