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
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<ImageCaptchaVO> response = beforeGenerateImageCaptchaValidData( imageCaptchaInfo);
ApiResponse<ImageCaptchaVO> response = beforeGenerateImageCaptchaValidData(imageCaptchaInfo);
if (response != null) {
return response;
}
@@ -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<String> localCacheIgnoredCacheFields;
}
@@ -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<FontWrapper> fontWrappers = new ArrayList<>();
// private List<FontWrapper> fontWrappers = new ArrayList<>();
private Map<String, Resource> resourceCache;
private Map<String, ResourceMap> 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<String> 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);
@@ -15,7 +15,7 @@ import java.util.stream.Collectors;
*/
@Slf4j
@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 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.Function;
/**
* @Author: 天爱有情
* @Description 通用Map包装类,提供类型安全的get/set方法
*/
@EqualsAndHashCode
public class AnyMap implements Map<String, Object> {
@@ -19,99 +23,245 @@ public class AnyMap implements Map<String, Object> {
target = new LinkedHashMap<>();
}
/**
* 构造函数 - 防御性拷贝
* @param map 源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) {
return getFloat(key, null);
}
public Float getFloat(String key, Float defaultData) {
Object data = get(key);
if (data != null) {
if (data instanceof Number) {
return ((Number) data).floatValue();
}
try {
if (data instanceof String) {
return Float.parseFloat((String) data);
}
} catch (NumberFormatException e) {
throw e;
}
}
return defaultData;
/**
* 获取Float值,支持默认值
*/
public Float getFloat(String key, Float defaultValue) {
return convertToNumber(key, defaultValue, Number::floatValue, Float::parseFloat);
}
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;
/**
* 获取Integer值
*/
public Integer getInt(String key) {
return getInt(key, null);
}
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);
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) {
return (String) 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);
}
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());
}
/**
* 添加参数(使用ParamKey
*/
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) {
return getParam(paramKey, null);
}
/**
* 获取参数(使用ParamKey),支持默认值
*/
@SuppressWarnings("unchecked")
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) {
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
public int size() {
@@ -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<String> SLIDER_CAPTCHA_TYPES = new HashSet<>();
private static final Set<String> CLICK_CAPTCHA_TYPES = new HashSet<>();
private static final Set<String> 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<String> getSliderCaptchaTypes() {
return SLIDER_CAPTCHA_TYPES;
return Collections.unmodifiableSet(SLIDER_CAPTCHA_TYPES);
}
public static Set<String> 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<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) {
InputStream stream = getTemplateFile(templateImages, imageName);
BufferedImage bufferedImage = CaptchaImageUtils.wrapFile2BufferedImage(stream);
try {
return CaptchaImageUtils.wrapFile2BufferedImage(stream);
} finally {
closeStream(stream);
return bufferedImage;
}
}
protected BufferedImage getResourceImage(Resource resource) {
InputStream stream = getResourceInputStream(resource, null);
BufferedImage bufferedImage = CaptchaImageUtils.wrapFile2BufferedImage(stream);
try {
return CaptchaImageUtils.wrapFile2BufferedImage(stream);
} finally {
closeStream(stream);
return bufferedImage;
}
}
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.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<GenerateParam, ConcurrentLinkedQueue<ImageCaptchaInfo>> queueMap = new ConcurrentHashMap<>(8);
protected Map<GenerateParam, AtomicInteger> posMap = new ConcurrentHashMap<>(8);
protected Map<GenerateParam, Long> lastUpdateMap = new ConcurrentHashMap<>(8);
protected Map<CacheKey, ConcurrentLinkedQueue<ImageCaptchaInfo>> queueMap = new ConcurrentHashMap<>(8);
protected Map<CacheKey, AtomicInteger> posMap = new ConcurrentHashMap<>(8);
protected Map<CacheKey, Long> lastUpdateMap = new ConcurrentHashMap<>(8);
/**
* 忽略的字段集合(不参与缓存key计算),默认为空
*/
@Getter
@Setter
protected Set<String> 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<ImageCaptchaInfo> queue = queueMap.get(generateParam);
// 创建CacheKey
CacheKey cacheKey = new CacheKey(generateParam, ignoredCacheFields);
ConcurrentLinkedQueue<ImageCaptchaInfo> 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;
}
}