mirror of
https://github.com/dromara/tianai-captcha.git
synced 2026-05-07 06:04:34 +08:00
1.5.0重构;
修复一大堆已知问题, 优化代码, 增加 application 类, 重构拦截器组件, 添加缓存模块,
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
+7
@@ -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 +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) {
|
||||
|
||||
+8
@@ -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) {
|
||||
|
||||
+11
-1
@@ -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;
|
||||
|
||||
+72
-81
@@ -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
-2
@@ -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();
|
||||
|
||||
}
|
||||
+109
@@ -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);
|
||||
}
|
||||
|
||||
+4
-2
@@ -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())) {
|
||||
|
||||
+25
-69
@@ -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);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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为前端传来的滑动轨迹数据
|
||||
|
||||
@@ -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 接口,进行自定义扩展
|
||||
|
||||
Reference in New Issue
Block a user