1.5.0重构;

修复一大堆已知问题,
优化代码,
增加 application 类,
重构拦截器组件,
添加缓存模块,
This commit is contained in:
天爱有情
2024-07-12 14:54:25 +08:00
parent db2f409783
commit 2cfb72349d
42 changed files with 2311 additions and 192 deletions
@@ -0,0 +1,27 @@
package cloud.tianai.captcha.application;
import lombok.Getter;
/**
* @Author: 天爱有情
* @date 2022/2/24 16:01
* @Description 验证码图片类型
*/
@Getter
public enum CaptchaImageType {
/** webp类型. */
WEBP,
/** jpg+png类型. */
JPEG_PNG;
public static CaptchaImageType getType(String bgImageType, String sliderImageType) {
if ("webp".equalsIgnoreCase(bgImageType) && "webp".equalsIgnoreCase(sliderImageType)) {
return WEBP;
}
if (("jpeg".equalsIgnoreCase(bgImageType) || "jpg".equalsIgnoreCase(bgImageType)) && "png".equalsIgnoreCase(sliderImageType)) {
return JPEG_PNG;
}
return null;
}
}
@@ -0,0 +1,297 @@
package cloud.tianai.captcha.application;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.exception.ImageCaptchaException;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.common.response.ApiResponseStatusConstant;
import cloud.tianai.captcha.common.util.CollectionUtils;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.interceptor.EmptyCaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator;
import lombok.extern.slf4j.Slf4j;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Author: 天爱有情
* @Date 2020/5/29 8:52
* @Description 默认的 图片验证码应用程序
*/
@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 = "_";
public DefaultImageCaptchaApplication(ImageCaptchaGenerator captchaGenerator,
ImageCaptchaValidator imageCaptchaValidator,
CacheStore cacheStore,
ImageCaptchaProperties prop,
CaptchaInterceptor captchaInterceptor) {
this.prop = prop;
setImageCaptchaGenerator(captchaGenerator);
setImageCaptchaValidator(imageCaptchaValidator);
setCacheStore(cacheStore);
// 默认过期时间
Long defaultExpire = prop.getExpire().get("default");
if (defaultExpire != null && defaultExpire > 0) {
this.defaultExpire = defaultExpire;
}
if (captchaInterceptor == null) {
this.captchaInterceptor = EmptyCaptchaInterceptor.INSTANCE;
} else {
this.captchaInterceptor = captchaInterceptor;
}
captchaGenerator.setInterceptor(this.captchaInterceptor);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha() {
// 生成滑块验证码
return generateCaptcha(CaptchaTypeConstant.SLIDER);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type) {
GenerateParam generateParam = new GenerateParam();
generateParam.setType(type);
return generateCaptcha(generateParam);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param) {
CaptchaResponse<ImageCaptchaVO> captchaResponse = beforeGenerateCaptcha(param);
if (captchaResponse != null) {
return captchaResponse;
}
ImageCaptchaInfo imageCaptchaInfo = getImageCaptchaGenerator().generateCaptchaImage(param);
captchaResponse = convertToCaptchaResponse(imageCaptchaInfo);
afterGenerateCaptcha(imageCaptchaInfo, captchaResponse);
return captchaResponse;
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType) {
return generateCaptcha(CaptchaTypeConstant.SLIDER, captchaImageType);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType) {
GenerateParam param = new GenerateParam();
if (CaptchaImageType.WEBP.equals(captchaImageType)) {
param.setBackgroundFormatName("webp");
param.setTemplateFormatName("webp");
} else {
param.setBackgroundFormatName("jpeg");
param.setTemplateFormatName("png");
}
param.setType(type);
return generateCaptcha(param);
}
public CaptchaResponse<ImageCaptchaVO> convertToCaptchaResponse(ImageCaptchaInfo imageCaptchaInfo) {
if (imageCaptchaInfo == null) {
// 要是生成失败
throw new ImageCaptchaException("生成验证码失败,验证码生成为空");
}
// 生成ID
String id = generatorId(imageCaptchaInfo);
CaptchaResponse<ImageCaptchaVO> response = beforeGenerateImageCaptchaValidData(imageCaptchaInfo);
if (response != null) {
return response;
}
// 生成校验数据
AnyMap validData = getImageCaptchaValidator().generateImageCaptchaValidData(imageCaptchaInfo);
afterGenerateImageCaptchaValidData(imageCaptchaInfo, validData);
if (!CollectionUtils.isEmpty(validData)) {
// 存到缓存里
cacheVerification(id, imageCaptchaInfo.getType(), validData);
}
ImageCaptchaVO verificationVO = new ImageCaptchaVO();
verificationVO.setType(imageCaptchaInfo.getType());
verificationVO.setBackgroundImage(imageCaptchaInfo.getBackgroundImage());
verificationVO.setTemplateImage(imageCaptchaInfo.getTemplateImage());
verificationVO.setBackgroundImageTag(imageCaptchaInfo.getBackgroundImageTag());
verificationVO.setTemplateImageTag(imageCaptchaInfo.getTemplateImageTag());
verificationVO.setBackgroundImageWidth(imageCaptchaInfo.getBackgroundImageWidth());
verificationVO.setBackgroundImageHeight(imageCaptchaInfo.getBackgroundImageHeight());
verificationVO.setTemplateImageWidth(imageCaptchaInfo.getTemplateImageWidth());
verificationVO.setTemplateImageHeight(imageCaptchaInfo.getTemplateImageHeight());
verificationVO.setData(imageCaptchaInfo.getData() == null ? null : imageCaptchaInfo.getData().getViewData());
return CaptchaResponse.of(id, verificationVO);
}
@Override
public ApiResponse<?> matching(String id, ImageCaptchaTrack imageCaptchaTrack) {
AnyMap validData = getVerification(id);
if (validData == null) {
return ApiResponse.ofMessage(ApiResponseStatusConstant.EXPIRED);
}
ApiResponse<?> response = beforeValid(id, imageCaptchaTrack, validData);
if (!response.isSuccess()) {
return response;
}
ApiResponse<?> basicValid = getImageCaptchaValidator().valid(imageCaptchaTrack, validData);
response = afterValid(id, imageCaptchaTrack, validData, basicValid);
if (!response.isSuccess()) {
return response;
}
return basicValid;
}
@Override
public boolean matching(String id, Float percentage) {
AnyMap cachePercentage = getVerification(id);
if (cachePercentage == null) {
return false;
}
ImageCaptchaValidator imageCaptchaValidator = getImageCaptchaValidator();
if (!(imageCaptchaValidator instanceof SimpleImageCaptchaValidator)) {
return false;
}
SimpleImageCaptchaValidator simpleImageCaptchaValidator = (SimpleImageCaptchaValidator) imageCaptchaValidator;
Float oriPercentage = cachePercentage.getFloat(SimpleImageCaptchaValidator.PERCENTAGE_KEY);
// 读容错值
Float tolerant = cachePercentage.getFloat(SimpleImageCaptchaValidator.TOLERANT_KEY, simpleImageCaptchaValidator.getDefaultTolerant());
return simpleImageCaptchaValidator.checkPercentage(percentage, oriPercentage, tolerant);
}
@Override
public String getCaptchaTypeById(String id) {
String[] split = id.split(ID_SPLIT);
if (split.length >= 2) {
return split[0];
}
return null;
}
protected String generatorId(ImageCaptchaInfo imageCaptchaInfo) {
return imageCaptchaInfo.getType() + ID_SPLIT + UUID.randomUUID().toString().replace("-", "");
}
/**
* 通过缓存获取百分比
*
* @param id 验证码ID
* @return AnyMap
*/
protected AnyMap getVerification(String id) {
return getCacheStore().getAndRemoveCache(getKey(id));
}
/**
* 缓存验证码
*
* @param id id
* @param type
* @param validData validData
*/
protected void cacheVerification(String id, String type, AnyMap validData) {
Long expire = prop.getExpire().getOrDefault(type, defaultExpire);
if (!getCacheStore().setCache(getKey(id), validData, expire, TimeUnit.MILLISECONDS)) {
log.error("缓存验证码数据失败, id={}, validData={}", id, validData);
throw new ImageCaptchaException("缓存验证码数据失败" + type);
}
}
protected String getKey(String id) {
return prop.getPrefix().concat(":").concat(id);
}
@Override
public ImageCaptchaResourceManager getImageCaptchaResourceManager() {
return getImageCaptchaGenerator().getImageResourceManager();
}
@Override
public void setImageCaptchaValidator(ImageCaptchaValidator imageCaptchaValidator) {
this.imageCaptchaValidator = imageCaptchaValidator;
}
@Override
public void setImageCaptchaGenerator(ImageCaptchaGenerator imageCaptchaGenerator) {
this.captchaGenerator = imageCaptchaGenerator;
}
@Override
public CaptchaInterceptor getCaptchaInterceptor() {
return this.captchaInterceptor;
}
@Override
public void setCaptchaInterceptor(CaptchaInterceptor captchaInterceptor) {
this.captchaGenerator = captchaGenerator;
}
@Override
public void setCacheStore(CacheStore cacheStore) {
this.cacheStore = cacheStore;
}
@Override
public ImageCaptchaValidator getImageCaptchaValidator() {
return this.imageCaptchaValidator;
}
@Override
public ImageCaptchaGenerator getImageCaptchaGenerator() {
return this.captchaGenerator;
}
@Override
public CacheStore getCacheStore() {
return this.cacheStore;
}
// ============== 一些模板方法 ================
private void afterGenerateCaptcha(ImageCaptchaInfo imageCaptchaInfo, CaptchaResponse<ImageCaptchaVO> captchaResponse) {
captchaInterceptor.afterGenerateCaptcha(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo, captchaResponse);
}
private CaptchaResponse<ImageCaptchaVO> beforeGenerateCaptcha(GenerateParam param) {
return captchaInterceptor.beforeGenerateCaptcha(captchaInterceptor.createContext(), param.getType(), param);
}
private CaptchaResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
return captchaInterceptor.beforeGenerateImageCaptchaValidData(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo);
}
private void afterGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo, AnyMap validData) {
captchaInterceptor.afterGenerateImageCaptchaValidData(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo, validData);
}
private ApiResponse<?> beforeValid(String id, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData) {
return captchaInterceptor.beforeValid(captchaInterceptor.createContext(), getCaptchaTypeById(id), imageCaptchaTrack, validData);
}
private ApiResponse<?> afterValid(String id, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData, ApiResponse<?> basicValid) {
return captchaInterceptor.afterValid(captchaInterceptor.createContext(), getCaptchaTypeById(id), imageCaptchaTrack, validData, basicValid);
}
}
@@ -0,0 +1,112 @@
package cloud.tianai.captcha.application;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
/**
* @Author: 天爱有情
* @date 2022/3/2 14:22
* @Description 用于SliderCaptchaApplication增加附属功能
*/
public class FilterImageCaptchaApplication implements ImageCaptchaApplication {
protected ImageCaptchaApplication target;
public FilterImageCaptchaApplication(ImageCaptchaApplication target) {
this.target = target;
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha() {
return target.generateCaptcha();
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type) {
return target.generateCaptcha(type);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType) {
return target.generateCaptcha(captchaImageType);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType) {
return target.generateCaptcha(type, captchaImageType);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param) {
return target.generateCaptcha(param);
}
@Override
public ApiResponse<?> matching(String id, ImageCaptchaTrack ImageCaptchaTrack) {
return target.matching(id, ImageCaptchaTrack);
}
@Override
public boolean matching(String id, Float percentage) {
return target.matching(id, percentage);
}
@Override
public String getCaptchaTypeById(String id) {
return target.getCaptchaTypeById(id);
}
@Override
public ImageCaptchaResourceManager getImageCaptchaResourceManager() {
return target.getImageCaptchaResourceManager();
}
@Override
public void setImageCaptchaValidator(ImageCaptchaValidator sliderCaptchaValidator) {
target.setImageCaptchaValidator(sliderCaptchaValidator);
}
@Override
public void setImageCaptchaGenerator(ImageCaptchaGenerator imageCaptchaGenerator) {
target.setImageCaptchaGenerator(imageCaptchaGenerator);
}
@Override
public CaptchaInterceptor getCaptchaInterceptor() {
return target.getCaptchaInterceptor();
}
@Override
public void setCaptchaInterceptor(CaptchaInterceptor captchaInterceptor) {
target.setCaptchaInterceptor(captchaInterceptor);
}
@Override
public void setCacheStore(CacheStore cacheStore) {
target.setCacheStore(cacheStore);
}
@Override
public ImageCaptchaValidator getImageCaptchaValidator() {
return target.getImageCaptchaValidator();
}
@Override
public ImageCaptchaGenerator getImageCaptchaGenerator() {
return target.getImageCaptchaGenerator();
}
@Override
public CacheStore getCacheStore() {
return target.getCacheStore();
}
}
@@ -0,0 +1,152 @@
package cloud.tianai.captcha.application;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
/**
* @Author: 天爱有情
* @Date 2020/5/29 8:33
* @Description 滑块验证码应用程序
*/
public interface ImageCaptchaApplication {
/**
* 生成滑块验证码
*
* @return
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha();
/**
* 生成滑块验证码
*
* @param type type类型
* @return CaptchaResponse<SliderCaptchaVO>
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type);
/**
* 生成滑块验证码
*
* @param captchaImageType 要生成webp还是jpg类型的图片
* @return CaptchaResponse<SliderCaptchaVO>
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType);
/**
* 生成验证码
*
* @param type type
* @param captchaImageType CaptchaImageType
* @return CaptchaResponse<ImageCaptchaVO>
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType);
/**
* 生成滑块验证码
*
* @param param param
* @return CaptchaResponse<SliderCaptchaVO>
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param);
/**
* 匹配
*
* @param id 验证码的ID
* @param imageCaptchaTrack 滑动轨迹
* @return 匹配成功返回true 否则返回false
*/
ApiResponse<?> matching(String id, ImageCaptchaTrack imageCaptchaTrack);
/**
* 兼容一下旧版本,新版本建议使用 {@link ImageCaptchaApplication#matching(String, ImageCaptchaTrack)}
*
* @param id id
* @param percentage 百分比数据
* @return boolean
*/
@Deprecated
boolean matching(String id, Float percentage);
/**
* 查询该ID是属于哪个验证码类型
*
* @param id id
* @return String
*/
String getCaptchaTypeById(String id);
/**
* 获取验证码资源管理器
*
* @return SliderCaptchaResourceManager
*/
ImageCaptchaResourceManager getImageCaptchaResourceManager();
/**
* 设置 SliderCaptchaValidator 验证码验证器
*
* @param imageCaptchaValidator imageCaptchaValidator
*/
void setImageCaptchaValidator(ImageCaptchaValidator imageCaptchaValidator);
/**
* 设置 ImageCaptchaGenerator 验证码生成器
*
* @param imageCaptchaGenerator SliderCaptchaGenerator
*/
void setImageCaptchaGenerator(ImageCaptchaGenerator imageCaptchaGenerator);
/**
* 获取拦截器
*
* @return CaptchaInterceptor
*/
CaptchaInterceptor getCaptchaInterceptor();
/**
* 设置 拦截器
*
* @param captchaInterceptor captchaInterceptor
*/
void setCaptchaInterceptor(CaptchaInterceptor captchaInterceptor);
/**
* 设置 缓存存储器
*
* @param cacheStore cacheStore
*/
void setCacheStore(CacheStore cacheStore);
/**
* 获取验证码验证器
*
* @return SliderCaptchaValidator
*/
ImageCaptchaValidator getImageCaptchaValidator();
/**
* 获取验证码生成器
*
* @return SliderCaptchaTemplate
*/
ImageCaptchaGenerator getImageCaptchaGenerator();
/**
* 获取缓存存储器
*
* @return CacheStore
*/
CacheStore getCacheStore();
}
@@ -0,0 +1,19 @@
package cloud.tianai.captcha.application;
import lombok.Data;
import java.util.Collections;
import java.util.Map;
/**
* @Author: 天爱有情
* @date 2020/10/19 18:41
* @Description 滑块验证码属性
*/
@Data
public class ImageCaptchaProperties {
/** 过期key prefix. */
private String prefix = "captcha";
/** 过期时间. */
private Map<String, Long> expire = Collections.emptyMap();
}
@@ -0,0 +1,24 @@
package cloud.tianai.captcha.application.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author: 天爱有情
* @Date 2020/5/29 8:31
* @Description 验证码返回对象
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CaptchaResponse<T> implements Serializable {
private String id;
private T captcha;
public static <T> CaptchaResponse<T> of(String id, T data) {
return new CaptchaResponse<T>(id, data);
}
}
@@ -0,0 +1,33 @@
package cloud.tianai.captcha.application.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ImageCaptchaVO implements Serializable {
/** 验证码类型.*/
private String type;
/** 背景图.*/
private String backgroundImage;
/** 移动图.*/
private String templateImage;
/** 背景图片所属标签. */
private String backgroundImageTag;
/** 模板图片所属标签. */
private String templateImageTag;
/** 背景图片宽度.*/
private Integer backgroundImageWidth;
/** 背景图片高度.*/
private Integer backgroundImageHeight;
/** 滑动图片宽度.*/
private Integer templateImageWidth;
/** 滑动图片高度.*/
private Integer templateImageHeight;
/** data 扩展数据.*/
private Object data;
}
@@ -0,0 +1,60 @@
package cloud.tianai.captcha.cache;
import cloud.tianai.captcha.common.AnyMap;
import java.util.concurrent.TimeUnit;
/**
* @Author: 天爱有情
* @date 2022/3/2 14:35
* @Description 提取出用于缓存的接口
*/
public interface CacheStore {
/**
* 读取缓存数据通过key
*
* @param key key
* @return AnyMap
*/
AnyMap getCache(String key);
/**
* 获取并删除数据 通过key
*
* @param key key
* @return AnyMap
*/
AnyMap getAndRemoveCache(String key);
/**
* 添加缓存数据
*
* @param key key
* @param data data
* @param expire 过期时间
* @param timeUnit 过期时间单位
* @return boolean
*/
boolean setCache(String key, AnyMap data, Long expire, TimeUnit timeUnit);
/**
* incr 数字
*
* @param key key
* @param delta 境量
* @param expire 过期时间
* @param timeUnit 过期时间单位
* @return Long
*/
Long incr(String key, long delta, Long expire, TimeUnit timeUnit);
/**
* get 数字
*
* @param key key
* @return Long
*/
Long getLong(String key);
}
@@ -0,0 +1,264 @@
package cloud.tianai.captcha.cache.impl;
import cloud.tianai.captcha.common.util.NamedThreadFactory;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* @Author: 天爱有情
* @date 2020/10/12 10:02
* @Description 给予本人以前写的 expiring-map(redis淘汰策略的java实现) 项目进行改造
*/
@Slf4j
@Accessors(chain = true)
public class ConCurrentExpiringMap<K, V> implements ExpiringMap<K, V> {
private ConcurrentHashMap<K, TimeMapEntity<K, V>> storage;
private SortedMap<Long, LinkedList<K>> sortedMap = new ConcurrentSkipListMap<>();
private final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("expiring-map-expire"));
public static final int LIMIT = 500;
public ConCurrentExpiringMap() {
this(128);
}
@Override
public void init() {
scheduledExecutor.scheduleAtFixedRate(new ExpireThread(), 5, 5, TimeUnit.SECONDS);
}
public ConCurrentExpiringMap(Integer initialCapacity) {
storage = new ConcurrentHashMap<>(initialCapacity);
}
@Override
public TimeMapEntity<K, V> put(K k, V v, Long expire, TimeUnit timeUnit) {
if (expire == null || expire < 1) {
expire = DEFAULT_EXPIRE;
}
TimeMapEntity<K, V> entity;
if (expire != null && expire > 0) {
entity = new TimeMapEntity<>(k, v, timeUnit.toNanos(expire), System.nanoTime());
sortedMap.computeIfAbsent(entity.getTimeout(), (k1) -> new LinkedList<>()).add(k);
} else {
entity = new TimeMapEntity<>(k, v, DEFAULT_EXPIRE, System.nanoTime());
}
TimeMapEntity<K, V> old = storage.put(k, entity);
return old;
}
@Override
public Optional<TimeMapEntity<K, V>> getData(K k) {
return Optional.ofNullable(storage.get(k));
}
@Override
public Long getExpire(K k) {
return getData(k).map(TimeMapEntity::getExpire).orElse(DEFAULT_EXPIRE);
}
@Override
public boolean incr(K k, Long expire, TimeUnit timeUnit) {
Optional<TimeMapEntity<K, V>> entityOptional = getData(k);
if (!entityOptional.isPresent()) {
return false;
}
synchronized (k) {
// 双重校验
entityOptional = getData(k);
if (!entityOptional.isPresent()) {
return false;
}
TimeMapEntity<K, V> entity = entityOptional.get();
TimeMapEntity<K, V> newEntity = entity;
newEntity.setExpire(entity.getExpire() + expire);
if (expire != null && expire > 0) {
sortedMap.getOrDefault(k, new LinkedList<>()).add(k);
}
return true;
}
}
@Override
public int size() {
return storage.size();
}
@Override
public boolean isEmpty() {
return storage.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return storage.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
Collection<TimeMapEntity<K, V>> values = storage.values();
Optional<TimeMapEntity<K, V>> any = values.stream().filter(v -> v.getValue().equals(value)).findAny();
return any.isPresent();
}
@Override
public V get(Object key) {
TimeMapEntity<K, V> timeMapEntity = storage.get(key);
if (isTimeout(timeMapEntity)) {
removeData(key);
return null;
}
return timeMapEntity.getValue();
}
protected boolean isTimeout(K key) {
Optional<TimeMapEntity<K, V>> data = getData(key);
return isTimeout(data.orElse(null));
}
protected boolean isTimeout(TimeMapEntity<K, V> timeMapEntity) {
if (timeMapEntity == null || timeMapEntity.getExpire() < 1) {
return true;
}
long currentTimeMillis = System.nanoTime();
long timeout = timeMapEntity.getTimeout();
return timeout < currentTimeMillis;
}
@Override
public V put(K key, V value) {
return put(key, value, DEFAULT_EXPIRE, null).getValue();
}
@Override
public V remove(Object key) {
return removeData(key).map(TimeMapEntity::getValue).orElse(null);
}
protected Optional<TimeMapEntity<K, V>> removeData(Object key) {
synchronized (key) {
TimeMapEntity<K, V> oldValue = storage.get(key);
if (oldValue != null) {
TimeMapEntity<K, V> entity = storage.remove(key);
Long expire = oldValue.getExpire();
if (expire != null && expire > 0) {
LinkedList<K> ks = sortedMap.get(expire);
if (ks != null) {
ks.remove(key);
}
}
if (entity != null) {
return Optional.of(entity);
}
}
}
return Optional.empty();
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
m.forEach(this::put);
}
@Override
public void clear() {
Map<K, TimeMapEntity<K, V>> copyStorage = new HashMap<>(storage);
storage.clear();
sortedMap.clear();
}
/**
* 这个可能会消耗点cpu
*
* @return
*/
@Override
public Set<K> keySet() {
return storage.keySet()
.stream()
.parallel()
.filter(k -> !isTimeout(k))
.collect(Collectors.toSet());
}
@Override
public Collection<V> values() {
return storage.values().stream().map(TimeMapEntity::getValue).collect(Collectors.toSet());
}
@Override
public Set<Entry<K, V>> entrySet() {
throw new IllegalArgumentException("timemap not impl entrySet.");
}
/**
* 定时执行任务
*
* @since 0.0.3
*/
private class ExpireThread implements Runnable {
@Override
public void run() {
SortedMap<Long, LinkedList<K>> expireMap = ConCurrentExpiringMap.this.sortedMap;
int limit = ConCurrentExpiringMap.LIMIT;
//1.判断是否为空
if (expireMap == null || expireMap.size() < 1) {
return;
}
log.debug("storage-size: {}", ConCurrentExpiringMap.this.storage.size());
log.debug("expire-size: {}", expireMap.size());
//2. 获取 key 进行处理
int count = 0;
LinkedList<Long> removeKeys = null;
// 删除的逻辑处理
long currentTime = System.nanoTime();
if (currentTime < expireMap.firstKey()) {
return;
}
for (Entry<Long, LinkedList<K>> entry : expireMap.entrySet()) {
final Long expireAt = entry.getKey();
LinkedList<K> expireKeys = entry.getValue();
// 判断队列是否为空
if (expireKeys == null || expireKeys.size() < 1) {
if (removeKeys == null) {
removeKeys = new LinkedList<>();
}
removeKeys.add(expireAt);
continue;
}
if (count >= limit) {
// 检索数量达到z最大值,直接跳出
break;
}
if (currentTime >= expireAt) {
Iterator<K> iterator = expireKeys.iterator();
while (iterator.hasNext()) {
K key = iterator.next();
// 先移除本身
iterator.remove();
// 再移除缓存,后续可以通过惰性删除做补偿
ConCurrentExpiringMap.this.get(key);
if (removeKeys == null) {
removeKeys = new LinkedList<>();
}
removeKeys.add(expireAt);
count++;
}
}
}
if (removeKeys != null && removeKeys.size() > 0) {
for (Long removeKey : removeKeys) {
expireMap.remove(removeKey);
}
}
}
}
}
@@ -0,0 +1,78 @@
package cloud.tianai.captcha.cache.impl;
import lombok.Data;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
public interface ExpiringMap<K, V> extends Map<K, V> {
/**
* 默认-1 无超时时间.
*/
Long DEFAULT_EXPIRE = -1L;
/**
* 添加值
* @param k key
* @param v value
* @param timeout 超时时间,
* @param timeUnit 超时时间单位
* @return 返回旧的数据,如果没有,返回null
*/
TimeMapEntity<K, V> put(K k, V v, Long timeout, TimeUnit timeUnit);
/**
* 获取value值
* @param k key
* @return
*/
Optional<TimeMapEntity<K, V>> getData(K k);
/**
* 获取某个key的过期时间
* @param k key
* @return 单位毫秒
*/
Long getExpire(K k);
/**
* 增加过期时间
* @param k key
* @param expire 过期时间
* @param timeUnit 超时时间单位
* @return
*/
boolean incr(K k, Long expire, TimeUnit timeUnit);
/**
* 初始化
*/
void init();
@Data
class TimeMapEntity<K, V> {
private K key;
private V value;
private Long expire;
private Long createTime;
private long timeout = -1;
TimeMapEntity(K k, V value, Long expire, Long createTime) {
this.key = k;
this.value = value;
this.expire = expire;
this.createTime = createTime;
if (expire > 0) {
this.timeout = createTime + expire;
}
}
public TimeMapEntity(TimeMapEntity<K, V> entity) {
this.key = entity.getKey();
this.value = entity.getValue();
this.expire = entity.getExpire();
this.createTime = entity.getCreateTime();
}
}
}
@@ -0,0 +1,66 @@
package cloud.tianai.captcha.cache.impl;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.common.AnyMap;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Author: 天爱有情
* @date 2022/3/2 14:39
* @Description 本地缓存
*/
public class LocalCacheStore implements CacheStore {
protected ExpiringMap<String, AnyMap> cache;
public LocalCacheStore() {
cache = new ConCurrentExpiringMap<>();
cache.init();
}
@Override
public AnyMap getCache(String key) {
return cache.get(key);
}
@Override
public AnyMap getAndRemoveCache(String key) {
return cache.remove(key);
}
@Override
public boolean setCache(String key, AnyMap data, Long expire, TimeUnit timeUnit) {
cache.remove(key);
cache.put(key, data, expire, timeUnit);
return true;
}
@Override
public Long incr(String key, long delta, Long expire, TimeUnit timeUnit) {
Map<String, Object> value = cache.remove(key);
if (value != null) {
Long incr = (Long) value.get("___incr___");
if (incr == null) {
incr = 0L;
}
incr += delta;
cache.put(key, AnyMap.of(Collections.singletonMap("___incr___", incr)), expire, timeUnit);
return incr;
}
cache.put(key, AnyMap.of(Collections.singletonMap("___incr___", delta)), expire, timeUnit);
return delta;
}
@Override
public Long getLong(String key) {
Map<String, Object> stringObjectMap = cache.get(key);
if (stringObjectMap != null) {
return (Long) stringObjectMap.get("___incr___");
}
return null;
}
}
@@ -0,0 +1,193 @@
package cloud.tianai.captcha.common;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
public class AnyMap implements Map<String, Object> {
private Map<String, Object> target;
public AnyMap() {
target = new LinkedHashMap<>();
}
public AnyMap(Map<String, Object> map) {
this.target = map;
}
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;
}
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);
}
// ================== implement Map =======================
@Override
public int size() {
return target.size();
}
@Override
public boolean isEmpty() {
return target.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return target.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return target.containsValue(value);
}
@Override
public Object get(Object key) {
return target.get(key);
}
@Override
public Object put(String key, Object value) {
return target.put(key, value);
}
@Override
public Object remove(Object key) {
return target.remove(key);
}
@Override
public void putAll(Map<? extends String, ?> m) {
target.putAll(m);
}
@Override
public void clear() {
target.clear();
}
@Override
public Set<String> keySet() {
return target.keySet();
}
@Override
public Collection<Object> values() {
return target.values();
}
@Override
public Set<Entry<String, Object>> entrySet() {
return target.entrySet();
}
@Override
public Object getOrDefault(Object key, Object defaultValue) {
return target.getOrDefault(key, defaultValue);
}
@Override
public void forEach(BiConsumer<? super String, ? super Object> action) {
target.forEach(action);
}
@Override
public void replaceAll(BiFunction<? super String, ? super Object, ?> function) {
target.replaceAll(function);
}
@Override
public Object putIfAbsent(String key, Object value) {
return target.putIfAbsent(key, value);
}
@Override
public boolean remove(Object key, Object value) {
return target.remove(key, value);
}
@Override
public boolean replace(String key, Object oldValue, Object newValue) {
return target.replace(key, oldValue, newValue);
}
@Override
public Object replace(String key, Object value) {
return target.replace(key, value);
}
@Override
public Object computeIfAbsent(String key, Function<? super String, ?> mappingFunction) {
return target.computeIfAbsent(key, mappingFunction);
}
@Override
public Object computeIfPresent(String key, BiFunction<? super String, ? super Object, ?> remappingFunction) {
return target.computeIfPresent(key, remappingFunction);
}
@Override
public Object compute(String key, BiFunction<? super String, ? super Object, ?> remappingFunction) {
return target.compute(key, remappingFunction);
}
@Override
public Object merge(String key, Object value, BiFunction<? super Object, ? super Object, ?> remappingFunction) {
return target.merge(key, value, remappingFunction);
}
}
@@ -8,6 +8,7 @@ import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.generator.impl.transform.Base64ImageTransform;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
@@ -23,8 +24,6 @@ import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import static cloud.tianai.captcha.generator.impl.StaticCaptchaPostProcessorManager.*;
/**
* @Author: 天爱有情
* @date 2022/4/22 16:30
@@ -50,6 +49,8 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener
/** 图片转换器. */
protected ImageTransform imageTransform;
protected CaptchaInterceptor interceptor;
@Getter
private boolean init = false;
@@ -102,17 +103,36 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener
assertInit();
CustomData data = new CustomData();
CaptchaExchange captchaExchange = CaptchaExchange.create(data, param);
ImageCaptchaInfo imageCaptchaInfo = applyPostProcessorBeforeGenerate(captchaExchange, this);
ImageCaptchaInfo imageCaptchaInfo = beforeGenerate(captchaExchange);
if (imageCaptchaInfo != null) {
return imageCaptchaInfo;
}
doGenerateCaptchaImage(captchaExchange);
applyPostProcessorBeforeWrapImageCaptchaInfo(captchaExchange, this);
beforeWrapImageCaptchaInfo(captchaExchange);
imageCaptchaInfo = wrapImageCaptchaInfo(captchaExchange);
applyPostProcessorAfterGenerateCaptchaImage(captchaExchange, imageCaptchaInfo, this);
afterGenerateCaptchaImage(captchaExchange, imageCaptchaInfo);
return imageCaptchaInfo;
}
protected void afterGenerateCaptchaImage(CaptchaExchange captchaExchange, ImageCaptchaInfo imageCaptchaInfo) {
if (interceptor != null) {
interceptor.afterGenerateCaptchaImage(interceptor.createContext(), captchaExchange, imageCaptchaInfo, this);
}
}
protected void beforeWrapImageCaptchaInfo(CaptchaExchange captchaExchange) {
if (interceptor != null) {
interceptor.beforeWrapImageCaptchaInfo(interceptor.createContext(), captchaExchange, this);
}
}
protected ImageCaptchaInfo beforeGenerate(CaptchaExchange captchaExchange) {
if (interceptor != null) {
return interceptor.beforeGenerateCaptchaImage(interceptor.createContext(), captchaExchange, this);
}
return null;
}
public ImageCaptchaInfo wrapImageCaptchaInfo(CaptchaExchange captchaExchange) {
ImageCaptchaInfo imageCaptchaInfo = doWrapImageCaptchaInfo(captchaExchange);
imageCaptchaInfo.setData(captchaExchange.getCustomData());
@@ -250,4 +270,14 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener
public void setImageTransform(ImageTransform imageTransform) {
this.imageTransform = imageTransform;
}
@Override
public CaptchaInterceptor getInterceptor() {
return interceptor;
}
@Override
public void setInterceptor(CaptchaInterceptor interceptor) {
this.interceptor = interceptor;
}
}
@@ -3,6 +3,7 @@ package cloud.tianai.captcha.generator;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
/**
@@ -78,4 +79,9 @@ public interface ImageCaptchaGenerator {
*/
void setImageTransform(ImageTransform imageTransform);
CaptchaInterceptor getInterceptor();
void setInterceptor(CaptchaInterceptor interceptor);
}
@@ -1,6 +1,7 @@
package cloud.tianai.captcha.generator;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
/**
@@ -17,7 +18,7 @@ public interface ImageCaptchaGeneratorProvider {
* @param imageTransform imageTransform
* @return ImageCaptchaGenerator
*/
ImageCaptchaGenerator get(ImageCaptchaResourceManager resourceManager, ImageTransform imageTransform);
ImageCaptchaGenerator get(ImageCaptchaResourceManager resourceManager, ImageTransform imageTransform, CaptchaInterceptor interceptor);
/**
* 验证码类型
@@ -1,10 +1,8 @@
package cloud.tianai.captcha.generator.common.model.dto;
import cloud.tianai.captcha.common.AnyMap;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: 天爱有情
* @date 2023/4/24 10:27
@@ -14,9 +12,9 @@ import java.util.Map;
public class CustomData {
/** 透传字段,用于传给前端. */
private Map<String, Object> viewData;
private AnyMap viewData;
/** 内部使用的字段数据. */
private Map<String, Object> data;
private AnyMap data;
/**
* 扩展字段
*/
@@ -24,14 +22,14 @@ public class CustomData {
public void putViewData(String key, Object data) {
if (this.viewData == null) {
this.viewData = new HashMap<>();
this.viewData = new AnyMap();
}
this.viewData.put(key, data);
}
public void putData(String key, Object data) {
if (this.data == null) {
this.data = new HashMap<>();
this.data = new AnyMap();
}
this.data.put(key, data);
}
@@ -1,11 +1,9 @@
package cloud.tianai.captcha.generator.common.model.dto;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import lombok.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: 天爱有情
* @date 2022/2/11 9:44
@@ -32,7 +30,7 @@ public class GenerateParam {
/** 滑动图片标签,用户二级过滤模板图片,或指定某模板图片.. */
private String templateImageTag;
/** 扩展参数. */
private Map<String, Object> param = new HashMap<>(4);
private AnyMap param = new AnyMap();
public void addParam(String key, Object value) {
doGetOrCreateParam().put(key, value);
@@ -42,9 +40,9 @@ public class GenerateParam {
return param == null ? null : param.get(key);
}
private Map<String, Object> doGetOrCreateParam() {
private AnyMap doGetOrCreateParam() {
if (param == null) {
param = new HashMap<>(4);
param = new AnyMap();
}
return param;
}
@@ -5,6 +5,7 @@ import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import lombok.Getter;
import lombok.Setter;
@@ -198,4 +199,14 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator {
public void setImageTransform(ImageTransform imageTransform) {
target.setImageTransform(imageTransform);
}
@Override
public CaptchaInterceptor getInterceptor() {
return target.getInterceptor();
}
@Override
public void setInterceptor(CaptchaInterceptor interceptor) {
target.setInterceptor(interceptor);
}
}
@@ -110,7 +110,7 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
if (provider == null) {
throw new IllegalArgumentException("生成验证码失败,错误的type类型:" + t);
}
return provider.get(getImageResourceManager(), getImageTransform()).init(initDefaultResource);
return provider.get(getImageResourceManager(), getImageTransform(), getInterceptor()).init(initDefaultResource);
});
return imageCaptchaGenerator;
}
@@ -0,0 +1,167 @@
package cloud.tianai.captcha.generator.impl;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaExchange;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider;
import lombok.SneakyThrows;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.util.UUID;
import static cloud.tianai.captcha.common.constant.CommonConstant.*;
public class SliderImageCaptchaV2Generator extends AbstractImageCaptchaGenerator {
/** 模板滑块固定名称. */
public static String TEMPLATE_ACTIVE_IMAGE_NAME = "active.png";
/** 模板凹槽固定名称. */
public static String TEMPLATE_FIXED_IMAGE_NAME = "fixed.png";
/** 模板蒙版. */
public static String TEMPLATE_MASK_IMAGE_NAME = "mask.png";
public SliderImageCaptchaV2Generator(ImageCaptchaResourceManager imageCaptchaResourceManager) {
super(imageCaptchaResourceManager);
}
public SliderImageCaptchaV2Generator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform) {
super(imageCaptchaResourceManager);
setImageTransform(imageTransform);
}
@Override
protected void doInit(boolean initDefaultResource) {
if (initDefaultResource) {
initDefaultResource();
}
}
@Override
@SneakyThrows
protected void doGenerateCaptchaImage(CaptchaExchange captchaExchange) {
GenerateParam param = captchaExchange.getParam();
ResourceMap templateResource = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag());
BufferedImage background = getResourceImage(resourceImage);
BufferedImage fixedTemplate = getTemplateImage(templateResource, TEMPLATE_FIXED_IMAGE_NAME);
BufferedImage activeTemplate = getTemplateImage(templateResource, TEMPLATE_ACTIVE_IMAGE_NAME);
int randomX = randomInt(fixedTemplate.getWidth() + 5, background.getWidth() - background.getHeight() - 10);
int randomY = randomInt(background.getHeight() - fixedTemplate.getHeight());
// 随机角度
// double randomDegree = randomDouble(10, 80);
// double randomDegree2 = randomDouble(10, 80);
int randomObfuscateX = randomObfuscateX(randomX, fixedTemplate.getWidth(), background.getWidth() - background.getHeight() - 10);
int randomObfuscateY = randomInt(background.getHeight() - fixedTemplate.getHeight());
double rotatePosRight = background.getHeight();
BufferedImage cutImage = CaptchaImageUtils.cutImage(background, fixedTemplate, randomX, randomY);
// 正确的图
Graphics2D backgroundGraphics = background.createGraphics();
// backgroundGraphics.rotate(Math.toRadians(randomDegree), randomX + fixedTemplate.getWidth(), rotatePosRight);
backgroundGraphics.drawImage(fixedTemplate, randomX, randomY, null);
// backgroundGraphics.rotate(Math.toRadians(-randomDegree + randomDegree2), randomX + fixedTemplate.getWidth(), rotatePosRight);
// 干扰图
backgroundGraphics.drawImage(fixedTemplate, randomObfuscateX, randomObfuscateY, null);
backgroundGraphics.dispose();
CaptchaImageUtils.overlayImage(cutImage, activeTemplate, 0, 0);
BufferedImage matrixTemplate = CaptchaImageUtils.createTransparentImage(activeTemplate.getWidth(), background.getHeight());
CaptchaImageUtils.overlayImage(matrixTemplate, cutImage, 0, randomY);
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\captcha\\temp\\1\\test-bg" + UUID.randomUUID().toString()
+ ".jpg");
ImageIO.write(background, "jpg", fileOutputStream);
fileOutputStream.close();
// FileOutputStream fileOutputStream2 = new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\captcha\\temp\\test-slider.png");
// ImageIO.write(matrixTemplate, "png", fileOutputStream2);
// fileOutputStream2.close();
System.out.println("randomX=" + randomX);
System.out.println("randomY=" + randomY);
// System.out.println("randomDegree=" + randomDegree);
System.out.println("randomObfuscateX=" + randomObfuscateX);
System.out.println("randomObfuscateY=" + randomY);
// CaptchaImageUtils.overlayImage(background, fixedTemplate, randomX, randomY);
//
// BufferedImage matrixTemplate = CaptchaImageUtils.createTransparentImage(activeTemplate.getWidth(), background.getHeight());
}
protected double randomDegree(double hypotenuse, double adjacent) {
if (hypotenuse <= adjacent) {
return 90.0;
}
// 使用勾股定理计算对边的长度
double opposite = Math.sqrt(hypotenuse * hypotenuse - adjacent * adjacent);
// 使用Math.atan2计算角度(以弧度为单位)
double angleInRadians = Math.atan2(opposite, adjacent);
// 将弧度转换为度
double angleInDegrees = Math.toDegrees(angleInRadians);
return angleInDegrees;
}
protected int randomObfuscateX(int sliderX, int slWidth, int bgWidth) {
if (bgWidth / 2 > (sliderX + (slWidth / 2))) {
// 右边混淆
return randomInt(sliderX + 10, bgWidth - slWidth);
}
// 左边混淆
return randomInt(10, sliderX - slWidth);
}
@Override
protected ImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaExchange captchaExchange) {
return null;
}
/**
* 初始化默认资源
*/
public void initDefaultResource() {
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
// 添加一些系统的资源文件
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"), DEFAULT_TAG));
// 添加一些系统的 模板文件
ResourceMap template1 = new ResourceMap(DEFAULT_TAG, 4);
template1.put(TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png")));
template1.put(TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png")));
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template1);
ResourceMap template2 = new ResourceMap(DEFAULT_TAG, 4);
template2.put(TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png")));
template2.put(TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png")));
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template2);
}
}
@@ -4,6 +4,7 @@ import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.model.dto.*;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
@@ -33,6 +34,12 @@ public class StandardConcatImageCaptchaGenerator extends AbstractImageCaptchaGen
setImageTransform(imageTransform);
}
public StandardConcatImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, CaptchaInterceptor interceptor) {
super(imageCaptchaResourceManager);
setImageTransform(imageTransform);
setInterceptor(interceptor);
}
@Override
protected void doInit(boolean initDefaultResource) {
if (initDefaultResource) {
@@ -6,6 +6,7 @@ import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.model.dto.*;
import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
@@ -41,6 +42,11 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen
setImageTransform(imageTransform);
}
public StandardRotateImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, CaptchaInterceptor interceptor) {
super(imageCaptchaResourceManager);
setImageTransform(imageTransform);
setInterceptor(interceptor);
}
@Override
protected void doInit(boolean initDefaultResource) {
if (initDefaultResource) {
@@ -5,6 +5,7 @@ import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.model.dto.*;
import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
@@ -48,6 +49,13 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen
setImageTransform(imageTransform);
}
public StandardSliderImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, CaptchaInterceptor interceptor) {
super(imageCaptchaResourceManager);
setImageTransform(imageTransform);
setInterceptor(interceptor);
}
@Override
protected void doInit(boolean initDefaultResource) {
if (initDefaultResource) {
@@ -7,6 +7,7 @@ import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.FontWrapper;
import cloud.tianai.captcha.generator.common.model.dto.*;
import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
@@ -78,12 +79,21 @@ public class StandardWordClickImageCaptchaGenerator extends AbstractClickImageCa
setImageTransform(imageTransform);
}
public StandardWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, List<FontWrapper> fonts) {
public StandardWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, CaptchaInterceptor interceptor) {
super(imageCaptchaResourceManager);
setImageTransform(imageTransform);
setInterceptor(interceptor);
}
public StandardWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, CaptchaInterceptor interceptor, List<FontWrapper> fonts) {
super(imageCaptchaResourceManager);
setImageTransform(imageTransform);
setInterceptor(interceptor);
this.fonts = fonts;
}
@Override
protected List<Resource> randomGetClickImgTips(GenerateParam param) {
int tipSize = interferenceCount + checkClickCount;
@@ -1,83 +1,74 @@
package cloud.tianai.captcha.generator.impl;
import cloud.tianai.captcha.common.exception.ImageCaptchaException;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageCaptchaPostProcessor;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaExchange;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import lombok.Getter;
import java.util.LinkedList;
import java.util.List;
/**
* @Author: 天爱有情
* @date 2023/4/24 15:23
* @Description 验证码后处理器管理
*/
public class StaticCaptchaPostProcessorManager {
@Getter
private static LinkedList<ImageCaptchaPostProcessor> processors = new LinkedList<>();
public static void add(ImageCaptchaPostProcessor processor) {
processors.add(processor);
}
public static void add(Integer index, ImageCaptchaPostProcessor processor) {
processors.add(index, processor);
}
public static void addFirst(ImageCaptchaPostProcessor processor) {
processors.addFirst(processor);
}
public static void addLast(ImageCaptchaPostProcessor processor) {
processors.addLast(processor);
}
public static void clear() {
processors.clear();
}
public static void add(List<ImageCaptchaPostProcessor> addPostProcessors) {
processors.addAll(addPostProcessors);
}
public static ImageCaptchaInfo applyPostProcessorBeforeGenerate(CaptchaExchange captchaExchange, ImageCaptchaGenerator context) {
for (ImageCaptchaPostProcessor processor : processors) {
try {
ImageCaptchaInfo imageCaptchaInfo = processor.beforeGenerateCaptchaImage(captchaExchange, context);
if (imageCaptchaInfo != null) {
return imageCaptchaInfo;
}
} catch (Exception e) {
throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.beforeGenerateCaptchaImage error, [" + processor.getClass() + "]", e);
}
}
return null;
}
public static void applyPostProcessorBeforeWrapImageCaptchaInfo(CaptchaExchange captchaExchange, ImageCaptchaGenerator context) {
for (ImageCaptchaPostProcessor processor : processors) {
try {
processor.beforeWrapImageCaptchaInfo(captchaExchange, context);
} catch (Exception e) {
throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.beforeWrapImageCaptchaInfo error, [" + processor.getClass() + "]", e);
}
}
}
public static void applyPostProcessorAfterGenerateCaptchaImage(CaptchaExchange captchaExchange, ImageCaptchaInfo imageCaptchaInfo, ImageCaptchaGenerator context) {
for (ImageCaptchaPostProcessor processor : processors) {
try {
processor.afterGenerateCaptchaImage(captchaExchange, imageCaptchaInfo, context);
} catch (Exception e) {
throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.afterGenerateCaptchaImage error, [" + processor.getClass() + "]", e);
}
}
}
}
//
///**
// * @Author: 天爱有情
// * @date 2023/4/24 15:23
// * @Description 验证码后处理器管理
// */
//public class StaticCaptchaPostProcessorManager {
//
// @Getter
// private static LinkedList<ImageCaptchaPostProcessor> processors = new LinkedList<>();
//
// public static void add(ImageCaptchaPostProcessor processor) {
// processors.add(processor);
// }
//
// public static void add(Integer index, ImageCaptchaPostProcessor processor) {
// processors.add(index, processor);
// }
//
// public static void addFirst(ImageCaptchaPostProcessor processor) {
// processors.addFirst(processor);
// }
//
// public static void addLast(ImageCaptchaPostProcessor processor) {
// processors.addLast(processor);
// }
//
// public static void clear() {
// processors.clear();
// }
//
// public static void add(List<ImageCaptchaPostProcessor> addPostProcessors) {
// processors.addAll(addPostProcessors);
// }
//
//
// public static ImageCaptchaInfo applyPostProcessorBeforeGenerate(CaptchaExchange captchaExchange, ImageCaptchaGenerator context) {
// for (ImageCaptchaPostProcessor processor : processors) {
// try {
// ImageCaptchaInfo imageCaptchaInfo = processor.beforeGenerateCaptchaImage(captchaExchange, context);
// if (imageCaptchaInfo != null) {
// return imageCaptchaInfo;
// }
// } catch (Exception e) {
// throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.beforeGenerateCaptchaImage error, [" + processor.getClass() + "]", e);
// }
// }
// return null;
// }
//
// public static void applyPostProcessorBeforeWrapImageCaptchaInfo(CaptchaExchange captchaExchange, ImageCaptchaGenerator context) {
// for (ImageCaptchaPostProcessor processor : processors) {
// try {
// processor.beforeWrapImageCaptchaInfo(captchaExchange, context);
// } catch (Exception e) {
// throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.beforeWrapImageCaptchaInfo error, [" + processor.getClass() + "]", e);
// }
// }
// }
//
//
// public static void applyPostProcessorAfterGenerateCaptchaImage(CaptchaExchange captchaExchange, ImageCaptchaInfo imageCaptchaInfo, ImageCaptchaGenerator context) {
// for (ImageCaptchaPostProcessor processor : processors) {
// try {
// processor.afterGenerateCaptchaImage(captchaExchange, imageCaptchaInfo, context);
// } catch (Exception e) {
// throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.afterGenerateCaptchaImage error, [" + processor.getClass() + "]", e);
// }
// }
// }
//
//}
@@ -3,6 +3,7 @@ package cloud.tianai.captcha.generator.impl.provider;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageCaptchaGeneratorProvider;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
public class CommonImageCaptchaGeneratorProvider implements ImageCaptchaGeneratorProvider {
@@ -17,8 +18,8 @@ public class CommonImageCaptchaGeneratorProvider implements ImageCaptchaGenerato
}
@Override
public ImageCaptchaGenerator get(ImageCaptchaResourceManager resourceManager, ImageTransform imageTransform) {
return provider.get(resourceManager, imageTransform);
public ImageCaptchaGenerator get(ImageCaptchaResourceManager resourceManager, ImageTransform imageTransform, CaptchaInterceptor interceptor) {
return provider.get(resourceManager, imageTransform,interceptor);
}
@Override
@@ -0,0 +1,85 @@
package cloud.tianai.captcha.interceptor;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaExchange;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
// ============================ 拦截器执行顺序 ============================
// =================== 生成验证码 ===================
// beforeGenerateCaptcha(...) ↓
// beforeGenerateCaptchaImage(...) ↓
// beforeWrapImageCaptchaInfo(...) ↓
// afterGenerateCaptchaImage(...) ↓
// beforeGenerateImageCaptchaValidData(...) ↓
// afterGenerateImageCaptchaValidData(...) ↓
// afterGenerateCaptcha(...) ↓
// =================== 验证码校验 ===================
// beforeValid(...) ↓
// afterValid(...) ↓
// ============================ 拦截器执行顺序 ============================
/**
* @Author: 天爱有情
* @date 2024/7/11 18:05
* @Description 验证码拦截器
*/
public interface CaptchaInterceptor {
default String getName() {
return "interceptor";
}
default Context createContext() {
return new Context(getName(), null, -1, 1, EmptyCaptchaInterceptor.INSTANCE);
}
default CaptchaResponse<ImageCaptchaVO> beforeGenerateCaptcha(Context context, String type, GenerateParam param) {
return null;
}
default CaptchaResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo) {
return null;
}
default void afterGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, AnyMap validData) {
}
default void afterGenerateCaptcha(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, CaptchaResponse<ImageCaptchaVO> captchaResponse) {
}
default ApiResponse<?> beforeValid(Context context, String type, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData) {
Object preReturn = context.getPreReturnData();
if (preReturn != null) {
return (ApiResponse<?>) preReturn;
}
return ApiResponse.ofSuccess();
}
default ApiResponse<?> afterValid(Context context, String type, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData, ApiResponse<?> basicValid) {
Object preReturn = context.getPreReturnData();
if (preReturn != null) {
return (ApiResponse<?>) preReturn;
}
return ApiResponse.ofSuccess();
}
default ImageCaptchaInfo beforeGenerateCaptchaImage(Context context, CaptchaExchange captchaExchange, AbstractImageCaptchaGenerator generator) {
return null;
}
default void beforeWrapImageCaptchaInfo(Context context, CaptchaExchange captchaExchange, AbstractImageCaptchaGenerator generator) {
}
default void afterGenerateCaptchaImage(Context context, CaptchaExchange captchaExchange, ImageCaptchaInfo imageCaptchaInfo, AbstractImageCaptchaGenerator generator) {
}
}
@@ -0,0 +1,186 @@
package cloud.tianai.captcha.interceptor;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaExchange;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
public class CaptchaInterceptorGroup implements CaptchaInterceptor {
private String name = "group_interceptor";
@Getter
@Setter
private List<CaptchaInterceptor> validators = new ArrayList<>();
public void addInterceptor(CaptchaInterceptor validator) {
validators.add(validator);
}
public void addInterceptor(List<CaptchaInterceptor> validators) {
this.validators.addAll(validators);
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CaptchaInterceptorGroup() {
}
public CaptchaInterceptorGroup(String name) {
this.name = name;
}
@Override
public Context createContext() {
return new Context(getName(), null, -1, validators.size(), this);
}
protected Context createContextIfNecessary(Context context) {
if (context == null) {
return createContext();
}
if (!context.getGroup().equals(this)) {
Context innerContext = createContext();
innerContext.setParent(context);
context = innerContext;
}
return context;
}
@Override
public CaptchaResponse<ImageCaptchaVO> beforeGenerateCaptcha(Context context, String type, GenerateParam param) {
context = createContextIfNecessary(context);
CaptchaResponse<ImageCaptchaVO> captchaResponse = null;
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
captchaResponse = interceptor.beforeGenerateCaptcha(context, type, param);
context.setPreReturnData(captchaResponse);
}
return captchaResponse;
}
@Override
public void afterGenerateCaptcha(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, CaptchaResponse<ImageCaptchaVO> captchaResponse) {
context = createContextIfNecessary(context);
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
interceptor.afterGenerateCaptcha(context, type, imageCaptchaInfo, captchaResponse);
}
}
@Override
public ApiResponse<?> beforeValid(Context context, String type, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData) {
context = createContextIfNecessary(context);
ApiResponse<?> beforeValid = null;
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
beforeValid = interceptor.beforeValid(context, type, imageCaptchaTrack, validData);
context.setPreReturnData(beforeValid);
}
return beforeValid == null ? ApiResponse.ofSuccess() : beforeValid;
}
@Override
public ApiResponse<?> afterValid(Context context, String type, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData, ApiResponse<?> basicValid) {
context = createContextIfNecessary(context);
ApiResponse<?> valid = null;
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
valid = interceptor.afterValid(context, type, imageCaptchaTrack, validData, basicValid);
context.setPreReturnData(valid);
}
return valid == null ? ApiResponse.ofSuccess() : valid;
}
@Override
public CaptchaResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo) {
context = createContextIfNecessary(context);
CaptchaResponse<ImageCaptchaVO> captchaResponse = null;
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
captchaResponse = interceptor.beforeGenerateImageCaptchaValidData(context, type, imageCaptchaInfo);
context.setPreReturnData(captchaResponse);
}
return captchaResponse;
}
@Override
public void afterGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, AnyMap validData) {
context = createContextIfNecessary(context);
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
interceptor.afterGenerateImageCaptchaValidData(context, type, imageCaptchaInfo, validData);
}
}
@Override
public ImageCaptchaInfo beforeGenerateCaptchaImage(Context context, CaptchaExchange captchaExchange, AbstractImageCaptchaGenerator generator) {
context = createContextIfNecessary(context);
ImageCaptchaInfo response = null;
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
response = interceptor.beforeGenerateCaptchaImage(context, captchaExchange, generator);
}
return response;
}
@Override
public void beforeWrapImageCaptchaInfo(Context context, CaptchaExchange captchaExchange, AbstractImageCaptchaGenerator generator) {
context = createContextIfNecessary(context);
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
interceptor.beforeWrapImageCaptchaInfo(context, captchaExchange, generator);
}
}
@Override
public void afterGenerateCaptchaImage(Context context, CaptchaExchange captchaExchange, ImageCaptchaInfo imageCaptchaInfo, AbstractImageCaptchaGenerator generator) {
context = createContextIfNecessary(context);
while (context.next() < context.getCount()) {
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
interceptor.afterGenerateCaptchaImage(context, captchaExchange, imageCaptchaInfo, generator);
}
}
public String printTree() {
return doPrintTree(1);
}
private String doPrintTree(int index) {
StringBuilder sb = new StringBuilder();
StringBuilder start = new StringBuilder();
for (int i = 0; i < index; i++) {
start.append("|-----");
}
for (int i = 0; i < validators.size(); i++) {
CaptchaInterceptor validator = validators.get(i);
sb.append(start).append("[").append(validator.getName()).append("]").append("\n");
if (validator instanceof CaptchaInterceptorGroup) {
sb.append(((CaptchaInterceptorGroup) validator).doPrintTree(index + 1));
}
}
return sb.toString();
}
}
@@ -0,0 +1,105 @@
package cloud.tianai.captcha.interceptor;
import cloud.tianai.captcha.common.AnyMap;
import lombok.Getter;
import lombok.Setter;
/**
* @Author: 天爱有情
* @date 2024/7/11 16:22
* @Description 拦截器的上下文参数
*/
@Getter
public class Context {
/** 名称. */
private String name;
/** 父容器. */
@Setter
private Context parent;
/** 当前拦截器数量. */
private Integer current;
/** 拦截器总数. */
private Integer count;
/** 拦截器组. */
private CaptchaInterceptor group;
/** The previous interceptor returns data. */
@Setter
private Object preReturnData;
/** 传输数据. */
private AnyMap data = new AnyMap();
public Context(String name, Context parent, Integer current, Integer count, CaptchaInterceptor group) {
this.name = name;
this.parent = parent;
this.current = current;
this.count = count;
this.group = group;
}
public Object getPreReturnData() {
Object returnData = preReturnData;
if (returnData == null && parent != null) {
returnData = parent.getPreReturnData();
}
return returnData;
}
public void putCurrentData(String key, Object value) {
data.put(key, value);
}
public <T> T getCurrentData(String key, Class<T> type) {
return convert(data.get(key), type);
}
public void putData(String key, Object value) {
putCurrentData(key, value);
if (parent != null) {
parent.putData(key, value);
}
}
public <T> T getData(String key, Class<T> type) {
T result = getCurrentData(key, type);
if (result == null && parent != null) {
result = parent.getData(key, type);
}
return result;
}
private <T> T convert(Object data, Class<T> clazz) {
if (data == null || clazz == null) {
return null;
}
// 判断转换的类型是否是number类型
return (T) data;
}
public Integer next() {
current++;
return current;
}
public Integer end() {
current = count;
return count;
}
public Boolean isEnd() {
return current >= count;
}
public Boolean isStart() {
return current < 0;
}
public void allEnd() {
Context context = parent;
if (context != null) {
context.allEnd();
}
// 结束自身
end();
}
}
@@ -0,0 +1,7 @@
package cloud.tianai.captcha.interceptor;
public class EmptyCaptchaInterceptor implements CaptchaInterceptor{
public static EmptyCaptchaInterceptor INSTANCE = new EmptyCaptchaInterceptor();
}
@@ -0,0 +1,109 @@
package cloud.tianai.captcha.interceptor.impl;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.common.response.CodeDefinition;
import cloud.tianai.captcha.common.util.CaptchaTypeClassifier;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.interceptor.Context;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import java.util.List;
/**
* @Author: 天爱有情
* @date 2023/1/4 10:00
* @Description BasicCaptchaTrackValidator
*/
public class BasicTrackCaptchaInterceptor implements CaptchaInterceptor {
public static final CodeDefinition DEFINITION = new CodeDefinition(50001, "basic check fail");
@Override
public String getName() {
return "basic_track_check";
}
@Override
public ApiResponse<?> afterValid(Context context, String type, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData, ApiResponse<?> basicValid) {
if (!basicValid.isSuccess()) {
return context.getGroup().afterValid(context, type, imageCaptchaTrack, validData, basicValid);
}
if (!CaptchaTypeClassifier.isSliderCaptcha(type)) {
// 不是滑动验证码的话暂时跳过,点选验证码行为轨迹还没做
return ApiResponse.ofSuccess();
}
// 进行行为轨迹检测
long startSlidingTime = imageCaptchaTrack.getStartTime().getTime();
long endSlidingTime = imageCaptchaTrack.getStopTime().getTime();
Integer bgImageWidth = imageCaptchaTrack.getBgImageWidth();
List<ImageCaptchaTrack.Track> trackList = imageCaptchaTrack.getTrackList();
// 这里只进行基本检测, 用一些简单算法进行校验,如有需要可扩展
// 检测1: 滑动时间如果小于300毫秒 返回false
// 检测2: 轨迹数据要是少于背10,或者大于背景宽度的五倍 返回false
// 检测3: x轴和y轴应该是从0开始的,要是一开始x轴和y轴乱跑,返回false
// 检测4: 如果y轴是相同的,必然是机器操作,直接返回false
// 检测5: x轴或者y轴直接的区间跳跃过大的话返回 false
// 检测6: x轴应该是由快到慢的, 要是速率一致,返回false
// 检测7: 如果x轴超过图片宽度的频率过高,返回false
// 检测1
if (startSlidingTime + 300 > endSlidingTime) {
context.end();
return ApiResponse.ofMessage(DEFINITION);
}
// 检测2
if (trackList.size() < 10 || trackList.size() > bgImageWidth * 5) {
context.end();
return ApiResponse.ofMessage(DEFINITION);
}
// 检测3
ImageCaptchaTrack.Track firstTrack = trackList.get(0);
if (firstTrack.getX() > 10 || firstTrack.getX() < -10 || firstTrack.getY() > 10 || firstTrack.getY() < -10) {
context.end();
return ApiResponse.ofMessage(DEFINITION);
}
int check4 = 0;
int check7 = 0;
for (int i = 1; i < trackList.size(); i++) {
ImageCaptchaTrack.Track track = trackList.get(i);
float x = track.getX();
float y = track.getY();
// check4
if (firstTrack.getY() == y) {
check4++;
}
// check7
if (x >= bgImageWidth) {
check7++;
}
// check5
ImageCaptchaTrack.Track preTrack = trackList.get(i - 1);
if ((track.getX() - preTrack.getX()) > 50 || (track.getY() - preTrack.getY()) > 50) {
context.end();
return ApiResponse.ofMessage(DEFINITION);
}
}
if (check4 == trackList.size() || check7 > 200) {
context.end();
return ApiResponse.ofMessage(DEFINITION);
}
// check6
int splitPos = (int) (trackList.size() * 0.7);
ImageCaptchaTrack.Track splitPostTrack = trackList.get(splitPos - 1);
ImageCaptchaTrack.Track lastTrack = trackList.get(trackList.size() - 1);
// bugfix: wuhaochao
ImageCaptchaTrack.Track stepOneFirstTrack = trackList.get(0);
ImageCaptchaTrack.Track stepOneTwoTrack = trackList.get(splitPos);
float posTime = splitPostTrack.getT() - stepOneFirstTrack.getT();
double startAvgPosTime = posTime / (float) splitPos;
double endAvgPosTime = (lastTrack.getT() - stepOneTwoTrack.getT()) / (float) (trackList.size() - splitPos);
boolean check = endAvgPosTime > startAvgPosTime;
if (check) {
return ApiResponse.ofSuccess();
}
context.end();
return ApiResponse.ofMessage(DEFINITION);
}
}
@@ -0,0 +1,54 @@
package cloud.tianai.captcha.interceptor.impl;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.common.util.CollectionUtils;
import cloud.tianai.captcha.common.util.ObjectUtils;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.interceptor.Context;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
/**
* @Author: 天爱有情
* @date 2023/1/4 10:10
* @Description 轨迹参数校验, 如果轨迹参数为空抛异常
*/
public class ParamCheckCaptchaInterceptor implements CaptchaInterceptor {
@Override
public ApiResponse<?> beforeValid(Context context, String type, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData) {
checkParam(imageCaptchaTrack);
return ApiResponse.ofSuccess();
}
@Override
public String getName() {
return "param_check";
}
public void checkParam(ImageCaptchaTrack imageCaptchaTrack) {
if (ObjectUtils.isEmpty(imageCaptchaTrack.getBgImageWidth())) {
throw new IllegalArgumentException("bgImageWidth must not be null");
}
if (ObjectUtils.isEmpty(imageCaptchaTrack.getBgImageHeight())) {
throw new IllegalArgumentException("bgImageHeight must not be null");
}
if (ObjectUtils.isEmpty(imageCaptchaTrack.getStartTime())) {
throw new IllegalArgumentException("startTime must not be null");
}
if (ObjectUtils.isEmpty(imageCaptchaTrack.getStopTime())) {
throw new IllegalArgumentException("stopTime must not be null");
}
if (CollectionUtils.isEmpty(imageCaptchaTrack.getTrackList())) {
throw new IllegalArgumentException("trackList must not be null");
}
for (ImageCaptchaTrack.Track track : imageCaptchaTrack.getTrackList()) {
Float x = track.getX();
Float y = track.getY();
Float t = track.getT();
String type = track.getType();
if (x == null || y == null || t == null || ObjectUtils.isEmpty(type)) {
throw new IllegalArgumentException("track[x,y,t,type] must not be null");
}
}
}
}
@@ -43,4 +43,14 @@ public interface ResourceStore {
*/
ResourceMap randomGetTemplateByTypeAndTag(String type, String tag);
/**
* 清除所有内置模板
*/
void clearAllTemplates();
/**
* 清除所有内置资源
*/
void clearAllResources();
}
@@ -76,6 +76,7 @@ public class LocalMemoryResourceStore implements ResourceStore {
resourceTagMap.remove(mergeTypeAndTag(type, tag));
}
@Override
public void clearAllResources() {
resourceTagMap.clear();
}
@@ -101,6 +102,7 @@ public class LocalMemoryResourceStore implements ResourceStore {
}
@Override
public void clearAllTemplates() {
templateResourceTagMap.clear();
}
@@ -1,11 +1,10 @@
package cloud.tianai.captcha.validator;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import java.util.Map;
/**
* @Author: 天爱有情
* @date 2022/2/17 10:54
@@ -17,9 +16,9 @@ public interface ImageCaptchaValidator {
* 用于生成验证码校验时需要的回传参数
*
* @param imageCaptchaInfo 生成的验证码数据
* @return Map<String, Object>
* @return AnyMap
*/
Map<String, Object> generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo);
AnyMap generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo);
/**
* 校验用户滑动滑块是否正确
@@ -28,5 +27,5 @@ public interface ImageCaptchaValidator {
* @param imageCaptchaValidData generateImageCaptchaValidData(生成的数据)
* @return ApiResponse<?>
*/
ApiResponse<?> valid(ImageCaptchaTrack imageCaptchaTrack, Map<String, Object> imageCaptchaValidData);
ApiResponse<?> valid(ImageCaptchaTrack imageCaptchaTrack, AnyMap imageCaptchaValidData);
}
@@ -25,9 +25,11 @@ public class ImageCaptchaTrack {
/** 模板图片高度. */
private Integer templateImageHeight;
/** 滑动开始时间. */
private Date startSlidingTime;
private Date startTime;
/** 滑动结束时间. */
private Date endSlidingTime;
private Date stopTime;
private Integer left;
private Integer top;
/** 滑动的轨迹. */
private List<Track> trackList;
/** 扩展数据,用户传输加密数据等.*/
@@ -1,5 +1,6 @@
package cloud.tianai.captcha.validator.impl;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.common.response.CodeDefinition;
import cloud.tianai.captcha.common.util.CaptchaTypeClassifier;
@@ -8,7 +9,6 @@ import cloud.tianai.captcha.common.util.ObjectUtils;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import java.util.List;
import java.util.Map;
/**
* @Author: 天爱有情
@@ -26,21 +26,24 @@ public class BasicCaptchaTrackValidator extends SimpleImageCaptchaValidator {
}
@Override
public ApiResponse<?> beforeValid(ImageCaptchaTrack imageCaptchaTrack, Map<String, Object> captchaValidData, Float tolerant, String type) {
public ApiResponse<?> beforeValid(ImageCaptchaTrack imageCaptchaTrack, AnyMap captchaValidData, Float tolerant, String type) {
// 校验参数
checkParam(imageCaptchaTrack);
return ApiResponse.ofSuccess();
}
@Override
public ApiResponse<?> afterValid(ImageCaptchaTrack imageCaptchaTrack, Map<String, Object> captchaValidData, Float tolerant, String type) {
public ApiResponse<?> afterValid(Boolean basicValid, ImageCaptchaTrack imageCaptchaTrack, AnyMap captchaValidData, Float tolerant, String type) {
if (!basicValid){
return ApiResponse.ofSuccess();
}
if (!CaptchaTypeClassifier.isSliderCaptcha(type)) {
// 不是滑动验证码的话暂时跳过,点选验证码行为轨迹还没做
return ApiResponse.ofSuccess();
}
// 进行行为轨迹检测
long startSlidingTime = imageCaptchaTrack.getStartSlidingTime().getTime();
long endSlidingTime = imageCaptchaTrack.getEndSlidingTime().getTime();
long startSlidingTime = imageCaptchaTrack.getStartTime().getTime();
long endSlidingTime = imageCaptchaTrack.getStopTime().getTime();
Integer bgImageWidth = imageCaptchaTrack.getBgImageWidth();
List<ImageCaptchaTrack.Track> trackList = imageCaptchaTrack.getTrackList();
// 这里只进行基本检测, 用一些简单算法进行校验,如有需要可扩展
@@ -112,10 +115,10 @@ public class BasicCaptchaTrackValidator extends SimpleImageCaptchaValidator {
if (ObjectUtils.isEmpty(imageCaptchaTrack.getBgImageHeight())) {
throw new IllegalArgumentException("bgImageHeight must not be null");
}
if (ObjectUtils.isEmpty(imageCaptchaTrack.getStartSlidingTime())) {
if (ObjectUtils.isEmpty(imageCaptchaTrack.getStartTime())) {
throw new IllegalArgumentException("startSlidingTime must not be null");
}
if (ObjectUtils.isEmpty(imageCaptchaTrack.getEndSlidingTime())) {
if (ObjectUtils.isEmpty(imageCaptchaTrack.getStopTime())) {
throw new IllegalArgumentException("endSlidingTime must not be null");
}
if (CollectionUtils.isEmpty(imageCaptchaTrack.getTrackList())) {
@@ -1,5 +1,6 @@
package cloud.tianai.captcha.validator.impl;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.common.response.ApiResponseStatusConstant;
@@ -19,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -81,8 +81,8 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
}
@Override
public Map<String, Object> generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
Map<String, Object> map = new HashMap<>(8);
public AnyMap generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
AnyMap map = AnyMap.of(new HashMap<>(8));
if (beforeGenerateImageCaptchaValidData(imageCaptchaInfo, map)) {
doGenerateImageCaptchaValidData(map, imageCaptchaInfo);
}
@@ -90,7 +90,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
return map;
}
public boolean beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo, Map<String, Object> map) {
public boolean beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo, AnyMap map) {
// 容错值
Float tolerant = imageCaptchaInfo.getTolerant();
if (tolerant != null && tolerant > 0) {
@@ -105,11 +105,11 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
return true;
}
public void afterGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo, Map<String, Object> map) {
public void afterGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo, AnyMap map) {
}
public void doGenerateImageCaptchaValidData(Map<String, Object> map,
public void doGenerateImageCaptchaValidData(AnyMap map,
ImageCaptchaInfo imageCaptchaInfo) {
// type
String type = (String) map.getOrDefault(TYPE_KEY, CaptchaTypeConstant.SLIDER);
@@ -154,11 +154,11 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
}
@Override
public ApiResponse<?> valid(ImageCaptchaTrack imageCaptchaTrack, Map<String, Object> imageCaptchaValidData) {
public ApiResponse<?> valid(ImageCaptchaTrack imageCaptchaTrack, AnyMap imageCaptchaValidData) {
// 读容错值
Float tolerant = getFloatParam(TOLERANT_KEY, imageCaptchaValidData, defaultTolerant);
Float tolerant = imageCaptchaValidData.getFloat(TOLERANT_KEY, defaultTolerant);
// 读验证码类型
String type = getStringParam(TYPE_KEY, imageCaptchaValidData, CaptchaTypeConstant.SLIDER);
String type = imageCaptchaValidData.getString(TYPE_KEY, CaptchaTypeConstant.SLIDER);
// 验证前
// 在验证前必须读取 容错值 和验证码类型
ApiResponse<?> beforeValid = beforeValid(imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
@@ -178,14 +178,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
// 验证
ApiResponse<?> response;
boolean valid = doValid(imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
if (valid) {
// 验证后
response = afterValid(imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
} else {
// 缺口位置校验失败
response = ApiResponse.ofMessage(ApiResponseStatusConstant.BASIC_CHECK_FAIL);
}
return response;
return afterValid(valid, imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
}
/**
@@ -197,7 +190,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
* @param type type
* @return boolean
*/
public ApiResponse<?> beforeValid(ImageCaptchaTrack imageCaptchaTrack, Map<String, Object> captchaValidData, Float tolerant, String type) {
public ApiResponse<?> beforeValid(ImageCaptchaTrack imageCaptchaTrack, AnyMap captchaValidData, Float tolerant, String type) {
return ApiResponse.ofSuccess();
}
@@ -210,12 +203,15 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
* @param type type
* @return boolean
*/
public ApiResponse<?> afterValid(ImageCaptchaTrack imageCaptchaTrack, Map<String, Object> captchaValidData, Float tolerant, String type) {
public ApiResponse<?> afterValid(Boolean basicValid, ImageCaptchaTrack imageCaptchaTrack, AnyMap captchaValidData, Float tolerant, String type) {
if (!basicValid) {
return ApiResponse.ofMessage(ApiResponseStatusConstant.BASIC_CHECK_FAIL);
}
return ApiResponse.ofSuccess();
}
public boolean doValid(ImageCaptchaTrack imageCaptchaTrack,
Map<String, Object> imageCaptchaValidData,
AnyMap imageCaptchaValidData,
Float tolerant,
String type) {
if (CaptchaTypeClassifier.isSliderCaptcha(type)) {
@@ -233,12 +229,12 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
return false;
}
public boolean doValidJigsawCaptcha(ImageCaptchaTrack imageCaptchaTrack, Map<String, Object> imageCaptchaValidData, Float tolerant, String type) {
public boolean doValidJigsawCaptcha(ImageCaptchaTrack imageCaptchaTrack, AnyMap imageCaptchaValidData, Float tolerant, String type) {
if (imageCaptchaTrack.getData() == null || !(imageCaptchaTrack.getData() instanceof String)) {
throw new IllegalArgumentException("拼图验证码必须传data数据,且必须是字符串类型逗号分隔数据");
}
String posArr = (String) imageCaptchaTrack.getData();
String successPosStr = getStringParam(PERCENTAGE_KEY, imageCaptchaValidData, null);
String successPosStr = imageCaptchaValidData.getString(PERCENTAGE_KEY, null);
return successPosStr.equals(posArr);
}
@@ -252,10 +248,10 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
* @return boolean
*/
public boolean doValidClickCaptcha(ImageCaptchaTrack imageCaptchaTrack,
Map<String, Object> imageCaptchaValidData,
AnyMap imageCaptchaValidData,
Float tolerant,
String type) {
String validStr = getStringParam(PERCENTAGE_KEY, imageCaptchaValidData, null);
String validStr = imageCaptchaValidData.getString(PERCENTAGE_KEY, null);
if (ObjectUtils.isEmpty(validStr)) {
return false;
}
@@ -307,19 +303,20 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
* @return boolean
*/
public boolean doValidSliderCaptcha(ImageCaptchaTrack imageCaptchaTrack,
Map<String, Object> imageCaptchaValidData,
AnyMap imageCaptchaValidData,
Float tolerant,
String type) {
Float oriPercentage = getFloatParam(PERCENTAGE_KEY, imageCaptchaValidData);
Float oriPercentage = imageCaptchaValidData.getFloat(PERCENTAGE_KEY);
if (oriPercentage == null) {
// 没读取到百分比
return false;
}
List<ImageCaptchaTrack.Track> trackList = imageCaptchaTrack.getTrackList();
ImageCaptchaTrack.Track firstTrack = trackList.get(0);
// 取最后一个滑动轨迹
ImageCaptchaTrack.Track lastTrack = trackList.get(trackList.size() - 1);
// 计算百分比
float calcPercentage = calcPercentage(lastTrack.getX(), imageCaptchaTrack.getBgImageWidth());
float calcPercentage = calcPercentage(lastTrack.getX() - firstTrack.getX(), imageCaptchaTrack.getBgImageWidth());
// 校验百分比
boolean percentage = checkPercentage(calcPercentage, oriPercentage, tolerant);
if (percentage) {
@@ -331,48 +328,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
return percentage;
}
public Float getFloatParam(String key, Map<String, Object> imageCaptchaValidData) {
return getFloatParam(key, imageCaptchaValidData, null);
}
public Float getFloatParam(String key, Map<String, Object> imageCaptchaValidData, Float defaultData) {
Object data = imageCaptchaValidData.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) {
log.error("从 imageCaptchaValidData 读取到的 " + key + "无法转换成float类型, [{}]", data);
throw e;
}
}
return defaultData;
}
public String getStringParam(String key, Map<String, Object> imageCaptchaValidData, String defaultData) {
if (CollectionUtils.isEmpty(imageCaptchaValidData)) {
return defaultData;
}
Object data = imageCaptchaValidData.get(key);
if (data != null) {
if (data instanceof String) {
return (String) data;
}
try {
return String.valueOf(data);
} catch (NumberFormatException e) {
log.error("从 imageCaptchaValidData 读取到的 " + key + "无法转换成String类型, [{}]", data);
throw e;
}
}
return defaultData;
}
protected void addPercentage(ImageCaptchaInfo imageCaptchaInfo, Map<String, Object> imageCaptchaValidData) {
protected void addPercentage(ImageCaptchaInfo imageCaptchaInfo, AnyMap imageCaptchaValidData) {
float percentage = calcPercentage(imageCaptchaInfo.getRandomX(), imageCaptchaInfo.getBackgroundImageWidth());
imageCaptchaValidData.put(PERCENTAGE_KEY, percentage);
}
@@ -0,0 +1,40 @@
package example.readme;
import cloud.tianai.captcha.application.DefaultImageCaptchaApplication;
import cloud.tianai.captcha.application.ImageCaptchaApplication;
import cloud.tianai.captcha.application.ImageCaptchaProperties;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.cache.impl.LocalCacheStore;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.impl.MultiImageCaptchaGenerator;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.interceptor.CaptchaInterceptorGroup;
import cloud.tianai.captcha.interceptor.EmptyCaptchaInterceptor;
import cloud.tianai.captcha.interceptor.impl.BasicTrackCaptchaInterceptor;
import cloud.tianai.captcha.interceptor.impl.ParamCheckCaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.impl.DefaultImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator;
public class ApplicationTest {
public static void main(String[] args) {
ImageCaptchaResourceManager imageCaptchaResourceManager = new DefaultImageCaptchaResourceManager();
ImageCaptchaGenerator generator = new MultiImageCaptchaGenerator(imageCaptchaResourceManager);
generator.init(true);
ImageCaptchaValidator imageCaptchaValidator = new SimpleImageCaptchaValidator();
CacheStore cacheStore = new LocalCacheStore();
ImageCaptchaProperties prop = new ImageCaptchaProperties();
CaptchaInterceptorGroup group = new CaptchaInterceptorGroup();
group.addInterceptor(new ParamCheckCaptchaInterceptor());
group.addInterceptor(new BasicTrackCaptchaInterceptor());
ImageCaptchaApplication application = new DefaultImageCaptchaApplication(generator, imageCaptchaValidator, cacheStore, prop, group);
CaptchaResponse<ImageCaptchaVO> res = application.generateCaptcha("SLIDER");
System.out.println(res);
}
}
@@ -1,5 +1,6 @@
package example.readme;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageTransform;
@@ -26,7 +27,7 @@ public class SimpleDemo {
ImageCaptchaInfo imageCaptchaInfo = imageCaptchaGenerator.generateCaptchaImage(CaptchaTypeConstant.SLIDER);
// 这个数据是根据当前生成的这条验证码数据生成对应的验证数据, 该数据要存到缓存中
Map<String, Object> map = imageCaptchaValidator.generateImageCaptchaValidData(imageCaptchaInfo);
AnyMap map = imageCaptchaValidator.generateImageCaptchaValidData(imageCaptchaInfo);
+2 -1
View File
@@ -1,5 +1,6 @@
package example.readme;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import cloud.tianai.captcha.validator.impl.BasicCaptchaTrackValidator;
@@ -10,7 +11,7 @@ public class Test2 {
BasicCaptchaTrackValidator sliderCaptchaValidator = new BasicCaptchaTrackValidator();
ImageCaptchaTrack imageCaptchaTrack = null;
Map<String, Object> map = null;
AnyMap map = null;
Float percentage = null;
// 用户传来的行为轨迹和进行校验
// - imageCaptchaTrack为前端传来的滑动轨迹数据
+3 -3
View File
@@ -1,7 +1,7 @@
package example.readme;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant;
import cloud.tianai.captcha.generator.impl.StandardSliderImageCaptchaGenerator;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
@@ -16,8 +16,8 @@ public class Test6 {
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
// 添加滑块验证码模板.模板图片由三张图片组成
ResourceMap template1 = new ResourceMap("default", 4);
template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, "/active.png"));
template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, "/fixed.png"));
template1.put(StandardSliderImageCaptchaGenerator.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, "/active.png"));
template1.put(StandardSliderImageCaptchaGenerator.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, "/fixed.png"));
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template1);
// 模板与三张图片组成 滑块、凹槽、背景图
// 同样默认支持 classpath 和 url 两种获取图片资源, 如果想扩展可实现 ResourceProvider 接口,进行自定义扩展