1.5.0重构;

修复一大堆已知问题,
优化代码,
增加 application 类,
重构拦截器组件,
添加缓存模块,
This commit is contained in:
天爱有情
2024-07-12 14:54:25 +08:00
parent db2f409783
commit 2cfb72349d
42 changed files with 2311 additions and 192 deletions
@@ -0,0 +1,27 @@
package cloud.tianai.captcha.application;
import lombok.Getter;
/**
* @Author: 天爱有情
* @date 2022/2/24 16:01
* @Description 验证码图片类型
*/
@Getter
public enum CaptchaImageType {
/** webp类型. */
WEBP,
/** jpg+png类型. */
JPEG_PNG;
public static CaptchaImageType getType(String bgImageType, String sliderImageType) {
if ("webp".equalsIgnoreCase(bgImageType) && "webp".equalsIgnoreCase(sliderImageType)) {
return WEBP;
}
if (("jpeg".equalsIgnoreCase(bgImageType) || "jpg".equalsIgnoreCase(bgImageType)) && "png".equalsIgnoreCase(sliderImageType)) {
return JPEG_PNG;
}
return null;
}
}
@@ -0,0 +1,297 @@
package cloud.tianai.captcha.application;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.exception.ImageCaptchaException;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.common.response.ApiResponseStatusConstant;
import cloud.tianai.captcha.common.util.CollectionUtils;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.interceptor.EmptyCaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator;
import lombok.extern.slf4j.Slf4j;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Author: 天爱有情
* @Date 2020/5/29 8:52
* @Description 默认的 图片验证码应用程序
*/
@Slf4j
public class DefaultImageCaptchaApplication implements ImageCaptchaApplication {
private CaptchaInterceptor captchaInterceptor;
/** 图片验证码生成器. */
private ImageCaptchaGenerator captchaGenerator;
/** 图片验证码校验器. */
private ImageCaptchaValidator imageCaptchaValidator;
/** 缓冲存储. */
private CacheStore cacheStore;
/** 验证码配置属性. */
private final ImageCaptchaProperties prop;
/** 默认的过期时间. */
private long defaultExpire = 20000L;
public static final String ID_SPLIT = "_";
public DefaultImageCaptchaApplication(ImageCaptchaGenerator captchaGenerator,
ImageCaptchaValidator imageCaptchaValidator,
CacheStore cacheStore,
ImageCaptchaProperties prop,
CaptchaInterceptor captchaInterceptor) {
this.prop = prop;
setImageCaptchaGenerator(captchaGenerator);
setImageCaptchaValidator(imageCaptchaValidator);
setCacheStore(cacheStore);
// 默认过期时间
Long defaultExpire = prop.getExpire().get("default");
if (defaultExpire != null && defaultExpire > 0) {
this.defaultExpire = defaultExpire;
}
if (captchaInterceptor == null) {
this.captchaInterceptor = EmptyCaptchaInterceptor.INSTANCE;
} else {
this.captchaInterceptor = captchaInterceptor;
}
captchaGenerator.setInterceptor(this.captchaInterceptor);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha() {
// 生成滑块验证码
return generateCaptcha(CaptchaTypeConstant.SLIDER);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type) {
GenerateParam generateParam = new GenerateParam();
generateParam.setType(type);
return generateCaptcha(generateParam);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param) {
CaptchaResponse<ImageCaptchaVO> captchaResponse = beforeGenerateCaptcha(param);
if (captchaResponse != null) {
return captchaResponse;
}
ImageCaptchaInfo imageCaptchaInfo = getImageCaptchaGenerator().generateCaptchaImage(param);
captchaResponse = convertToCaptchaResponse(imageCaptchaInfo);
afterGenerateCaptcha(imageCaptchaInfo, captchaResponse);
return captchaResponse;
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType) {
return generateCaptcha(CaptchaTypeConstant.SLIDER, captchaImageType);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType) {
GenerateParam param = new GenerateParam();
if (CaptchaImageType.WEBP.equals(captchaImageType)) {
param.setBackgroundFormatName("webp");
param.setTemplateFormatName("webp");
} else {
param.setBackgroundFormatName("jpeg");
param.setTemplateFormatName("png");
}
param.setType(type);
return generateCaptcha(param);
}
public CaptchaResponse<ImageCaptchaVO> convertToCaptchaResponse(ImageCaptchaInfo imageCaptchaInfo) {
if (imageCaptchaInfo == null) {
// 要是生成失败
throw new ImageCaptchaException("生成验证码失败,验证码生成为空");
}
// 生成ID
String id = generatorId(imageCaptchaInfo);
CaptchaResponse<ImageCaptchaVO> response = beforeGenerateImageCaptchaValidData(imageCaptchaInfo);
if (response != null) {
return response;
}
// 生成校验数据
AnyMap validData = getImageCaptchaValidator().generateImageCaptchaValidData(imageCaptchaInfo);
afterGenerateImageCaptchaValidData(imageCaptchaInfo, validData);
if (!CollectionUtils.isEmpty(validData)) {
// 存到缓存里
cacheVerification(id, imageCaptchaInfo.getType(), validData);
}
ImageCaptchaVO verificationVO = new ImageCaptchaVO();
verificationVO.setType(imageCaptchaInfo.getType());
verificationVO.setBackgroundImage(imageCaptchaInfo.getBackgroundImage());
verificationVO.setTemplateImage(imageCaptchaInfo.getTemplateImage());
verificationVO.setBackgroundImageTag(imageCaptchaInfo.getBackgroundImageTag());
verificationVO.setTemplateImageTag(imageCaptchaInfo.getTemplateImageTag());
verificationVO.setBackgroundImageWidth(imageCaptchaInfo.getBackgroundImageWidth());
verificationVO.setBackgroundImageHeight(imageCaptchaInfo.getBackgroundImageHeight());
verificationVO.setTemplateImageWidth(imageCaptchaInfo.getTemplateImageWidth());
verificationVO.setTemplateImageHeight(imageCaptchaInfo.getTemplateImageHeight());
verificationVO.setData(imageCaptchaInfo.getData() == null ? null : imageCaptchaInfo.getData().getViewData());
return CaptchaResponse.of(id, verificationVO);
}
@Override
public ApiResponse<?> matching(String id, ImageCaptchaTrack imageCaptchaTrack) {
AnyMap validData = getVerification(id);
if (validData == null) {
return ApiResponse.ofMessage(ApiResponseStatusConstant.EXPIRED);
}
ApiResponse<?> response = beforeValid(id, imageCaptchaTrack, validData);
if (!response.isSuccess()) {
return response;
}
ApiResponse<?> basicValid = getImageCaptchaValidator().valid(imageCaptchaTrack, validData);
response = afterValid(id, imageCaptchaTrack, validData, basicValid);
if (!response.isSuccess()) {
return response;
}
return basicValid;
}
@Override
public boolean matching(String id, Float percentage) {
AnyMap cachePercentage = getVerification(id);
if (cachePercentage == null) {
return false;
}
ImageCaptchaValidator imageCaptchaValidator = getImageCaptchaValidator();
if (!(imageCaptchaValidator instanceof SimpleImageCaptchaValidator)) {
return false;
}
SimpleImageCaptchaValidator simpleImageCaptchaValidator = (SimpleImageCaptchaValidator) imageCaptchaValidator;
Float oriPercentage = cachePercentage.getFloat(SimpleImageCaptchaValidator.PERCENTAGE_KEY);
// 读容错值
Float tolerant = cachePercentage.getFloat(SimpleImageCaptchaValidator.TOLERANT_KEY, simpleImageCaptchaValidator.getDefaultTolerant());
return simpleImageCaptchaValidator.checkPercentage(percentage, oriPercentage, tolerant);
}
@Override
public String getCaptchaTypeById(String id) {
String[] split = id.split(ID_SPLIT);
if (split.length >= 2) {
return split[0];
}
return null;
}
protected String generatorId(ImageCaptchaInfo imageCaptchaInfo) {
return imageCaptchaInfo.getType() + ID_SPLIT + UUID.randomUUID().toString().replace("-", "");
}
/**
* 通过缓存获取百分比
*
* @param id 验证码ID
* @return AnyMap
*/
protected AnyMap getVerification(String id) {
return getCacheStore().getAndRemoveCache(getKey(id));
}
/**
* 缓存验证码
*
* @param id id
* @param type
* @param validData validData
*/
protected void cacheVerification(String id, String type, AnyMap validData) {
Long expire = prop.getExpire().getOrDefault(type, defaultExpire);
if (!getCacheStore().setCache(getKey(id), validData, expire, TimeUnit.MILLISECONDS)) {
log.error("缓存验证码数据失败, id={}, validData={}", id, validData);
throw new ImageCaptchaException("缓存验证码数据失败" + type);
}
}
protected String getKey(String id) {
return prop.getPrefix().concat(":").concat(id);
}
@Override
public ImageCaptchaResourceManager getImageCaptchaResourceManager() {
return getImageCaptchaGenerator().getImageResourceManager();
}
@Override
public void setImageCaptchaValidator(ImageCaptchaValidator imageCaptchaValidator) {
this.imageCaptchaValidator = imageCaptchaValidator;
}
@Override
public void setImageCaptchaGenerator(ImageCaptchaGenerator imageCaptchaGenerator) {
this.captchaGenerator = imageCaptchaGenerator;
}
@Override
public CaptchaInterceptor getCaptchaInterceptor() {
return this.captchaInterceptor;
}
@Override
public void setCaptchaInterceptor(CaptchaInterceptor captchaInterceptor) {
this.captchaGenerator = captchaGenerator;
}
@Override
public void setCacheStore(CacheStore cacheStore) {
this.cacheStore = cacheStore;
}
@Override
public ImageCaptchaValidator getImageCaptchaValidator() {
return this.imageCaptchaValidator;
}
@Override
public ImageCaptchaGenerator getImageCaptchaGenerator() {
return this.captchaGenerator;
}
@Override
public CacheStore getCacheStore() {
return this.cacheStore;
}
// ============== 一些模板方法 ================
private void afterGenerateCaptcha(ImageCaptchaInfo imageCaptchaInfo, CaptchaResponse<ImageCaptchaVO> captchaResponse) {
captchaInterceptor.afterGenerateCaptcha(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo, captchaResponse);
}
private CaptchaResponse<ImageCaptchaVO> beforeGenerateCaptcha(GenerateParam param) {
return captchaInterceptor.beforeGenerateCaptcha(captchaInterceptor.createContext(), param.getType(), param);
}
private CaptchaResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
return captchaInterceptor.beforeGenerateImageCaptchaValidData(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo);
}
private void afterGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo, AnyMap validData) {
captchaInterceptor.afterGenerateImageCaptchaValidData(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo, validData);
}
private ApiResponse<?> beforeValid(String id, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData) {
return captchaInterceptor.beforeValid(captchaInterceptor.createContext(), getCaptchaTypeById(id), imageCaptchaTrack, validData);
}
private ApiResponse<?> afterValid(String id, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData, ApiResponse<?> basicValid) {
return captchaInterceptor.afterValid(captchaInterceptor.createContext(), getCaptchaTypeById(id), imageCaptchaTrack, validData, basicValid);
}
}
@@ -0,0 +1,112 @@
package cloud.tianai.captcha.application;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
/**
* @Author: 天爱有情
* @date 2022/3/2 14:22
* @Description 用于SliderCaptchaApplication增加附属功能
*/
public class FilterImageCaptchaApplication implements ImageCaptchaApplication {
protected ImageCaptchaApplication target;
public FilterImageCaptchaApplication(ImageCaptchaApplication target) {
this.target = target;
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha() {
return target.generateCaptcha();
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type) {
return target.generateCaptcha(type);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType) {
return target.generateCaptcha(captchaImageType);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType) {
return target.generateCaptcha(type, captchaImageType);
}
@Override
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param) {
return target.generateCaptcha(param);
}
@Override
public ApiResponse<?> matching(String id, ImageCaptchaTrack ImageCaptchaTrack) {
return target.matching(id, ImageCaptchaTrack);
}
@Override
public boolean matching(String id, Float percentage) {
return target.matching(id, percentage);
}
@Override
public String getCaptchaTypeById(String id) {
return target.getCaptchaTypeById(id);
}
@Override
public ImageCaptchaResourceManager getImageCaptchaResourceManager() {
return target.getImageCaptchaResourceManager();
}
@Override
public void setImageCaptchaValidator(ImageCaptchaValidator sliderCaptchaValidator) {
target.setImageCaptchaValidator(sliderCaptchaValidator);
}
@Override
public void setImageCaptchaGenerator(ImageCaptchaGenerator imageCaptchaGenerator) {
target.setImageCaptchaGenerator(imageCaptchaGenerator);
}
@Override
public CaptchaInterceptor getCaptchaInterceptor() {
return target.getCaptchaInterceptor();
}
@Override
public void setCaptchaInterceptor(CaptchaInterceptor captchaInterceptor) {
target.setCaptchaInterceptor(captchaInterceptor);
}
@Override
public void setCacheStore(CacheStore cacheStore) {
target.setCacheStore(cacheStore);
}
@Override
public ImageCaptchaValidator getImageCaptchaValidator() {
return target.getImageCaptchaValidator();
}
@Override
public ImageCaptchaGenerator getImageCaptchaGenerator() {
return target.getImageCaptchaGenerator();
}
@Override
public CacheStore getCacheStore() {
return target.getCacheStore();
}
}
@@ -0,0 +1,152 @@
package cloud.tianai.captcha.application;
import cloud.tianai.captcha.application.vo.CaptchaResponse;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.cache.CacheStore;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
/**
* @Author: 天爱有情
* @Date 2020/5/29 8:33
* @Description 滑块验证码应用程序
*/
public interface ImageCaptchaApplication {
/**
* 生成滑块验证码
*
* @return
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha();
/**
* 生成滑块验证码
*
* @param type type类型
* @return CaptchaResponse<SliderCaptchaVO>
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type);
/**
* 生成滑块验证码
*
* @param captchaImageType 要生成webp还是jpg类型的图片
* @return CaptchaResponse<SliderCaptchaVO>
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType);
/**
* 生成验证码
*
* @param type type
* @param captchaImageType CaptchaImageType
* @return CaptchaResponse<ImageCaptchaVO>
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType);
/**
* 生成滑块验证码
*
* @param param param
* @return CaptchaResponse<SliderCaptchaVO>
*/
CaptchaResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param);
/**
* 匹配
*
* @param id 验证码的ID
* @param imageCaptchaTrack 滑动轨迹
* @return 匹配成功返回true 否则返回false
*/
ApiResponse<?> matching(String id, ImageCaptchaTrack imageCaptchaTrack);
/**
* 兼容一下旧版本,新版本建议使用 {@link ImageCaptchaApplication#matching(String, ImageCaptchaTrack)}
*
* @param id id
* @param percentage 百分比数据
* @return boolean
*/
@Deprecated
boolean matching(String id, Float percentage);
/**
* 查询该ID是属于哪个验证码类型
*
* @param id id
* @return String
*/
String getCaptchaTypeById(String id);
/**
* 获取验证码资源管理器
*
* @return SliderCaptchaResourceManager
*/
ImageCaptchaResourceManager getImageCaptchaResourceManager();
/**
* 设置 SliderCaptchaValidator 验证码验证器
*
* @param imageCaptchaValidator imageCaptchaValidator
*/
void setImageCaptchaValidator(ImageCaptchaValidator imageCaptchaValidator);
/**
* 设置 ImageCaptchaGenerator 验证码生成器
*
* @param imageCaptchaGenerator SliderCaptchaGenerator
*/
void setImageCaptchaGenerator(ImageCaptchaGenerator imageCaptchaGenerator);
/**
* 获取拦截器
*
* @return CaptchaInterceptor
*/
CaptchaInterceptor getCaptchaInterceptor();
/**
* 设置 拦截器
*
* @param captchaInterceptor captchaInterceptor
*/
void setCaptchaInterceptor(CaptchaInterceptor captchaInterceptor);
/**
* 设置 缓存存储器
*
* @param cacheStore cacheStore
*/
void setCacheStore(CacheStore cacheStore);
/**
* 获取验证码验证器
*
* @return SliderCaptchaValidator
*/
ImageCaptchaValidator getImageCaptchaValidator();
/**
* 获取验证码生成器
*
* @return SliderCaptchaTemplate
*/
ImageCaptchaGenerator getImageCaptchaGenerator();
/**
* 获取缓存存储器
*
* @return CacheStore
*/
CacheStore getCacheStore();
}
@@ -0,0 +1,19 @@
package cloud.tianai.captcha.application;
import lombok.Data;
import java.util.Collections;
import java.util.Map;
/**
* @Author: 天爱有情
* @date 2020/10/19 18:41
* @Description 滑块验证码属性
*/
@Data
public class ImageCaptchaProperties {
/** 过期key prefix. */
private String prefix = "captcha";
/** 过期时间. */
private Map<String, Long> expire = Collections.emptyMap();
}
@@ -0,0 +1,24 @@
package cloud.tianai.captcha.application.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author: 天爱有情
* @Date 2020/5/29 8:31
* @Description 验证码返回对象
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CaptchaResponse<T> implements Serializable {
private String id;
private T captcha;
public static <T> CaptchaResponse<T> of(String id, T data) {
return new CaptchaResponse<T>(id, data);
}
}
@@ -0,0 +1,33 @@
package cloud.tianai.captcha.application.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ImageCaptchaVO implements Serializable {
/** 验证码类型.*/
private String type;
/** 背景图.*/
private String backgroundImage;
/** 移动图.*/
private String templateImage;
/** 背景图片所属标签. */
private String backgroundImageTag;
/** 模板图片所属标签. */
private String templateImageTag;
/** 背景图片宽度.*/
private Integer backgroundImageWidth;
/** 背景图片高度.*/
private Integer backgroundImageHeight;
/** 滑动图片宽度.*/
private Integer templateImageWidth;
/** 滑动图片高度.*/
private Integer templateImageHeight;
/** data 扩展数据.*/
private Object data;
}