mirror of
https://github.com/dromara/tianai-captcha.git
synced 2026-05-07 06:04:34 +08:00
feat: CacheImageCaptchaGenerator支持自定义忽略某些参数不参与校验
fix: 修复TACBuilder.builder无法创建的bug perf: 优化AnyMap结构
This commit is contained in:
+18
-6
@@ -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();
|
||||||
|
|||||||
+2
@@ -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);
|
||||||
|
|||||||
+10
-1
@@ -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() {
|
||||||
|
|||||||
+25
-3
@@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -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) {
|
||||||
|
|||||||
+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() + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
-24
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user