mirror of
https://github.com/dromara/tianai-captcha.git
synced 2026-05-06 21:53:10 +08:00
feat: CacheImageCaptchaGenerator支持自定义忽略某些参数不参与校验
fix: 修复TACBuilder.builder无法创建的bug perf: 优化AnyMap结构
This commit is contained in:
+19
-7
@@ -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;
|
||||
}
|
||||
|
||||
+2
@@ -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);
|
||||
|
||||
+10
-1
@@ -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) {
|
||||
/**
|
||||
* 获取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 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 {
|
||||
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<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 相关方法 =======================
|
||||
|
||||
/**
|
||||
* 添加参数(使用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() {
|
||||
|
||||
+25
-3
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -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) {
|
||||
|
||||
+69
@@ -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() + "}";
|
||||
}
|
||||
}
|
||||
+81
-25
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user