mirror of
https://github.com/dromara/tianai-captcha.git
synced 2026-05-07 14:13:05 +08:00
refactor(resource): 重构资源存储和管理逻辑
- 移除了 AbstractResourceStore 类 - 新增了 CrudResourceStore 接口,定义了 CRUD操作 - 修改了 DefaultImageCaptchaResourceManager,支持批量获取资源和模板 - 重构了 FontCache 类,改为实现 ResourceStore 接口 - 更新了相关应用类,使用新的资源管理逻辑
This commit is contained in:
+13
-13
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -78,21 +77,21 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha() {
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha() {
|
||||
// 生成滑块验证码
|
||||
return generateCaptcha(CaptchaTypeConstant.SLIDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type) {
|
||||
public ApiResponse<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);
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param) {
|
||||
ApiResponse<ImageCaptchaVO> captchaResponse = beforeGenerateCaptcha(param);
|
||||
if (captchaResponse != null) {
|
||||
return captchaResponse;
|
||||
}
|
||||
@@ -103,12 +102,12 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType) {
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType) {
|
||||
return generateCaptcha(CaptchaTypeConstant.SLIDER, captchaImageType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType) {
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType) {
|
||||
GenerateParam param = new GenerateParam();
|
||||
if (CaptchaImageType.WEBP.equals(captchaImageType)) {
|
||||
param.setBackgroundFormatName("webp");
|
||||
@@ -122,14 +121,14 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication {
|
||||
}
|
||||
|
||||
|
||||
public CaptchaResponse<ImageCaptchaVO> convertToCaptchaResponse(ImageCaptchaInfo imageCaptchaInfo) {
|
||||
public ApiResponse<ImageCaptchaVO> convertToCaptchaResponse(ImageCaptchaInfo imageCaptchaInfo) {
|
||||
if (imageCaptchaInfo == null) {
|
||||
// 要是生成失败
|
||||
throw new ImageCaptchaException("生成验证码失败,验证码生成为空");
|
||||
}
|
||||
// 生成ID
|
||||
String id = generatorId(imageCaptchaInfo);
|
||||
CaptchaResponse<ImageCaptchaVO> response = beforeGenerateImageCaptchaValidData(imageCaptchaInfo);
|
||||
ApiResponse<ImageCaptchaVO> response = beforeGenerateImageCaptchaValidData(imageCaptchaInfo);
|
||||
if (response != null) {
|
||||
return response;
|
||||
}
|
||||
@@ -151,7 +150,8 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication {
|
||||
verificationVO.setTemplateImageWidth(imageCaptchaInfo.getTemplateImageWidth());
|
||||
verificationVO.setTemplateImageHeight(imageCaptchaInfo.getTemplateImageHeight());
|
||||
verificationVO.setData(imageCaptchaInfo.getData() == null ? null : imageCaptchaInfo.getData().getViewData());
|
||||
return CaptchaResponse.of(id, verificationVO);
|
||||
verificationVO.setId(id);
|
||||
return ApiResponse.ofSuccess(verificationVO);
|
||||
}
|
||||
|
||||
|
||||
@@ -286,15 +286,15 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication {
|
||||
|
||||
// ============== 一些模板方法 ================
|
||||
|
||||
private void afterGenerateCaptcha(ImageCaptchaInfo imageCaptchaInfo, CaptchaResponse<ImageCaptchaVO> captchaResponse) {
|
||||
private void afterGenerateCaptcha(ImageCaptchaInfo imageCaptchaInfo, ApiResponse<ImageCaptchaVO> captchaResponse) {
|
||||
captchaInterceptor.afterGenerateCaptcha(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo, captchaResponse);
|
||||
}
|
||||
|
||||
private CaptchaResponse<ImageCaptchaVO> beforeGenerateCaptcha(GenerateParam param) {
|
||||
private ApiResponse<ImageCaptchaVO> beforeGenerateCaptcha(GenerateParam param) {
|
||||
return captchaInterceptor.beforeGenerateCaptcha(captchaInterceptor.createContext(), param.getType(), param);
|
||||
}
|
||||
|
||||
private CaptchaResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
|
||||
private ApiResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
|
||||
return captchaInterceptor.beforeGenerateImageCaptchaValidData(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -27,27 +26,27 @@ public class FilterImageCaptchaApplication implements ImageCaptchaApplication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha() {
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha() {
|
||||
return target.generateCaptcha();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type) {
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha(String type) {
|
||||
return target.generateCaptcha(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType) {
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType) {
|
||||
return target.generateCaptcha(captchaImageType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType) {
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType) {
|
||||
return target.generateCaptcha(type, captchaImageType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param) {
|
||||
public ApiResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param) {
|
||||
return target.generateCaptcha(param);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -25,7 +24,7 @@ public interface ImageCaptchaApplication {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
CaptchaResponse<ImageCaptchaVO> generateCaptcha();
|
||||
ApiResponse<ImageCaptchaVO> generateCaptcha();
|
||||
|
||||
/**
|
||||
* 生成滑块验证码
|
||||
@@ -33,7 +32,7 @@ public interface ImageCaptchaApplication {
|
||||
* @param type type类型
|
||||
* @return CaptchaResponse<SliderCaptchaVO>
|
||||
*/
|
||||
CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type);
|
||||
ApiResponse<ImageCaptchaVO> generateCaptcha(String type);
|
||||
|
||||
/**
|
||||
* 生成滑块验证码
|
||||
@@ -41,7 +40,7 @@ public interface ImageCaptchaApplication {
|
||||
* @param captchaImageType 要生成webp还是jpg类型的图片
|
||||
* @return CaptchaResponse<SliderCaptchaVO>
|
||||
*/
|
||||
CaptchaResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType);
|
||||
ApiResponse<ImageCaptchaVO> generateCaptcha(CaptchaImageType captchaImageType);
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
@@ -50,7 +49,7 @@ public interface ImageCaptchaApplication {
|
||||
* @param captchaImageType CaptchaImageType
|
||||
* @return CaptchaResponse<ImageCaptchaVO>
|
||||
*/
|
||||
CaptchaResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType);
|
||||
ApiResponse<ImageCaptchaVO> generateCaptcha(String type, CaptchaImageType captchaImageType);
|
||||
|
||||
|
||||
/**
|
||||
@@ -59,7 +58,7 @@ public interface ImageCaptchaApplication {
|
||||
* @param param param
|
||||
* @return CaptchaResponse<SliderCaptchaVO>
|
||||
*/
|
||||
CaptchaResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param);
|
||||
ApiResponse<ImageCaptchaVO> generateCaptcha(GenerateParam param);
|
||||
|
||||
/**
|
||||
* 匹配
|
||||
|
||||
@@ -7,10 +7,7 @@ import cloud.tianai.captcha.generator.ImageTransform;
|
||||
import cloud.tianai.captcha.generator.impl.MultiImageCaptchaGenerator;
|
||||
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
|
||||
import cloud.tianai.captcha.interceptor.EmptyCaptchaInterceptor;
|
||||
import cloud.tianai.captcha.resource.DefaultBuiltInResources;
|
||||
import cloud.tianai.captcha.resource.FontCache;
|
||||
import cloud.tianai.captcha.resource.ResourceProviders;
|
||||
import cloud.tianai.captcha.resource.ResourceStore;
|
||||
import cloud.tianai.captcha.resource.*;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
|
||||
import cloud.tianai.captcha.resource.impl.DefaultImageCaptchaResourceManager;
|
||||
@@ -49,8 +46,8 @@ public class TACBuilder {
|
||||
}
|
||||
|
||||
public TACBuilder addDefaultTemplate(String defaultPathPrefix) {
|
||||
DefaultBuiltInResources defaultBuiltInResources = new DefaultBuiltInResources(defaultPathPrefix);
|
||||
defaultBuiltInResources.addDefaultTemplate(resourceStore);
|
||||
// DefaultBuiltInResources defaultBuiltInResources = new DefaultBuiltInResources(defaultPathPrefix);
|
||||
// defaultBuiltInResources.addDefaultTemplate(resourceStore);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -115,12 +112,16 @@ public class TACBuilder {
|
||||
|
||||
|
||||
public TACBuilder addResource(String captchaType, Resource imageResource) {
|
||||
this.resourceStore.addResource(captchaType, imageResource);
|
||||
if (resourceStore instanceof CrudResourceStore) {
|
||||
((CrudResourceStore) resourceStore).addResource(captchaType, imageResource);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public TACBuilder addTemplate(String captchaType, ResourceMap resourceMap) {
|
||||
this.resourceStore.addTemplate(captchaType, resourceMap);
|
||||
if (resourceStore instanceof CrudResourceStore) {
|
||||
((CrudResourceStore) resourceStore).addTemplate(captchaType, resourceMap);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ import java.io.Serializable;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ImageCaptchaVO implements Serializable {
|
||||
|
||||
/** ID.*/
|
||||
private String id;
|
||||
/** 验证码类型.*/
|
||||
private String type;
|
||||
/** 背景图.*/
|
||||
|
||||
@@ -3,13 +3,18 @@ package cloud.tianai.captcha.common.constant;
|
||||
public interface CommonConstant {
|
||||
|
||||
String DEFAULT_TAG = "default";
|
||||
|
||||
|
||||
/** 图标点选资源存储类型. */
|
||||
String IMAGE_CLICK_ICON = "ICON";
|
||||
String IMAGE_ICON = "ICON";
|
||||
/** 蜂窝点选.*/
|
||||
String HONEYCOMB_CLICK_ICON = "HONEYCOMB_ICON";
|
||||
/** 刮刮卡图标. */
|
||||
String SCRAPE_ICON = "SCRAPE_ICON";
|
||||
|
||||
// String IMAGE_CLICK_ICON = "IMAGE_CLICK_ICON";
|
||||
String IMAGE_TIP_ICON = "IMAGE_TIP_ICON";
|
||||
String IMAGE_CLICK_ICON = "IMAGE_CLICK_ICON";
|
||||
|
||||
/**
|
||||
* 默认的resource资源文件路径.
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.io.Serializable;
|
||||
/**
|
||||
* @Author: 天爱有情
|
||||
* @date 2023/4/20 9:53
|
||||
* @Description 可能是最好用的API统一返回格式类
|
||||
* @Description API统一返回格式类
|
||||
*/
|
||||
@Data
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
|
||||
@@ -2,7 +2,8 @@ package cloud.tianai.captcha.generator.common.model.dto;
|
||||
|
||||
import cloud.tianai.captcha.common.AnyMap;
|
||||
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
|
||||
import lombok.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @Author: 天爱有情
|
||||
|
||||
@@ -28,6 +28,10 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
|
||||
|
||||
protected Map<String, ImageCaptchaGenerator> imageCaptchaGeneratorMap = new ConcurrentHashMap<>(4);
|
||||
protected Map<String, ImageCaptchaGeneratorProvider> imageCaptchaGeneratorProviderMap = new HashMap<>(4);
|
||||
// 点选类验证码字体
|
||||
// @Setter
|
||||
// @Getter
|
||||
// protected List<FontWrapper> fontWrappers;
|
||||
@Setter
|
||||
@Getter
|
||||
private String defaultCaptcha = SLIDER;
|
||||
|
||||
+2
-10
@@ -88,14 +88,11 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen
|
||||
BufferedImage matrixTemplate = CaptchaImageUtils.createTransparentImage(activeTemplate.getWidth(), background.getHeight());
|
||||
CaptchaImageUtils.overlayImage(matrixTemplate, cutImage, 0, randomY);
|
||||
|
||||
XandY xandY = new XandY();
|
||||
xandY.x = randomX;
|
||||
xandY.y = randomY;
|
||||
captchaExchange.setBackgroundImage(background);
|
||||
captchaExchange.setTemplateImage(matrixTemplate);
|
||||
captchaExchange.setTemplateResource(templateResource);
|
||||
captchaExchange.setResourceImage(resourceImage);
|
||||
captchaExchange.setTransferData(xandY);
|
||||
captchaExchange.setTransferData(new Point(randomX,randomY));
|
||||
// 后处理
|
||||
// applyPostProcessorBeforeWrapImageCaptchaInfo(captchaExchange, this);
|
||||
// imageCaptchaInfo = wrapSliderCaptchaInfo(randomX, randomY, captchaExchange);
|
||||
@@ -125,11 +122,6 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen
|
||||
}
|
||||
|
||||
|
||||
public static class XandY {
|
||||
int x;
|
||||
int y;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public SliderImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaExchange captchaExchange) {
|
||||
@@ -139,7 +131,7 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen
|
||||
Resource resourceImage = captchaExchange.getResourceImage();
|
||||
ResourceMap templateResource = captchaExchange.getTemplateResource();
|
||||
CustomData customData = captchaExchange.getCustomData();
|
||||
XandY data = (XandY) captchaExchange.getTransferData();
|
||||
Point data = (Point) captchaExchange.getTransferData();
|
||||
ImageTransformData transform = getImageTransform().transform(param, backgroundImage, sliderImage, resourceImage, templateResource, customData);
|
||||
|
||||
SliderImageCaptchaInfo imageCaptchaInfo = SliderImageCaptchaInfo.of(data.x, data.y,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -41,18 +40,18 @@ public interface CaptchaInterceptor {
|
||||
return new Context(getName(), null, -1, 1, EmptyCaptchaInterceptor.INSTANCE);
|
||||
}
|
||||
|
||||
default CaptchaResponse<ImageCaptchaVO> beforeGenerateCaptcha(Context context, String type, GenerateParam param) {
|
||||
default ApiResponse<ImageCaptchaVO> beforeGenerateCaptcha(Context context, String type, GenerateParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default CaptchaResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo) {
|
||||
default ApiResponse<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 void afterGenerateCaptcha(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, ApiResponse<ImageCaptchaVO> captchaResponse) {
|
||||
}
|
||||
|
||||
default ApiResponse<?> beforeValid(Context context, String type, MatchParam matchParam, AnyMap validData) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -66,9 +65,9 @@ public class CaptchaInterceptorGroup implements CaptchaInterceptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> beforeGenerateCaptcha(Context context, String type, GenerateParam param) {
|
||||
public ApiResponse<ImageCaptchaVO> beforeGenerateCaptcha(Context context, String type, GenerateParam param) {
|
||||
context = createContextIfNecessary(context);
|
||||
CaptchaResponse<ImageCaptchaVO> captchaResponse = null;
|
||||
ApiResponse<ImageCaptchaVO> captchaResponse = null;
|
||||
while (context.next() < context.getCount()) {
|
||||
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
|
||||
captchaResponse = interceptor.beforeGenerateCaptcha(context, type, param);
|
||||
@@ -78,7 +77,7 @@ public class CaptchaInterceptorGroup implements CaptchaInterceptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterGenerateCaptcha(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, CaptchaResponse<ImageCaptchaVO> captchaResponse) {
|
||||
public void afterGenerateCaptcha(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, ApiResponse<ImageCaptchaVO> captchaResponse) {
|
||||
context = createContextIfNecessary(context);
|
||||
while (context.next() < context.getCount()) {
|
||||
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
|
||||
@@ -111,9 +110,9 @@ public class CaptchaInterceptorGroup implements CaptchaInterceptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptchaResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo) {
|
||||
public ApiResponse<ImageCaptchaVO> beforeGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo) {
|
||||
context = createContextIfNecessary(context);
|
||||
CaptchaResponse<ImageCaptchaVO> captchaResponse = null;
|
||||
ApiResponse<ImageCaptchaVO> captchaResponse = null;
|
||||
while (context.next() < context.getCount()) {
|
||||
CaptchaInterceptor interceptor = validators.get(context.getCurrent());
|
||||
captchaResponse = interceptor.beforeGenerateImageCaptchaValidData(context, type, imageCaptchaInfo);
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import java.util.List;
|
||||
* @Description BasicCaptchaTrackValidator
|
||||
*/
|
||||
public class BasicTrackCaptchaInterceptor implements CaptchaInterceptor {
|
||||
public static final CodeDefinition DEFINITION = new CodeDefinition(50001, "basic check fail");
|
||||
public static final CodeDefinition DEFINITION = new CodeDefinition(50001, "basic track check fail");
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
package cloud.tianai.captcha.resource;
|
||||
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractResourceStore implements ResourceStore {
|
||||
|
||||
@Getter
|
||||
protected ChainListener listener = new ChainListener();
|
||||
|
||||
boolean isInit = false;
|
||||
|
||||
@Override
|
||||
public void init(ImageCaptchaResourceManager resourceManager) {
|
||||
if (isInit) {
|
||||
return;
|
||||
}
|
||||
doInit();
|
||||
isInit = true;
|
||||
listener.onInit(this, resourceManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(ResourceListener listener) {
|
||||
this.listener.removeListener(listener);
|
||||
this.listener.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResource(String type, Resource resource) {
|
||||
doAddResource(type, resource);
|
||||
if (isInit) {
|
||||
listener.onAddResource(type, resource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTemplate(String type, ResourceMap template) {
|
||||
doAddTemplate(type, template);
|
||||
if (isInit) {
|
||||
listener.onAddTemplate(type, template);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource deleteResource(String type, String id) {
|
||||
Resource resource = doDeleteResource(type, id);
|
||||
if (isInit && resource != null) {
|
||||
listener.onDeleteResource(type, resource);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceMap deleteTemplate(String type, String id) {
|
||||
ResourceMap resourceMap = doDeleteTemplate(type, id);
|
||||
if (isInit && resourceMap != null) {
|
||||
listener.onDeleteTemplate(type, resourceMap);
|
||||
}
|
||||
return resourceMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource randomGetResourceByTypeAndTag(String type, String tag) {
|
||||
Resource resource = doRandomGetResourceByTypeAndTag(type, tag);
|
||||
if (isInit && resource != null) {
|
||||
listener.onRandomGetResourceByTypeAndTag(type, tag, resource);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceMap randomGetTemplateByTypeAndTag(String type, String tag) {
|
||||
ResourceMap resourceMap = doRandomGetTemplateByTypeAndTag(type, tag);
|
||||
if (isInit && resourceMap != null) {
|
||||
listener.onRandomGetTemplateByTypeAndTag(type, tag, resourceMap);
|
||||
}
|
||||
return resourceMap;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void clearAllResources() {
|
||||
doClearAllResources();
|
||||
if (isInit) {
|
||||
listener.onClearAllResources();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllTemplates() {
|
||||
doClearAllTemplates();
|
||||
if (isInit) {
|
||||
listener.onClearAllTemplates();
|
||||
}
|
||||
}
|
||||
|
||||
public void doInit() {
|
||||
|
||||
}
|
||||
|
||||
public abstract void doClearAllResources();
|
||||
|
||||
public abstract void doClearAllTemplates();
|
||||
|
||||
public abstract Resource doRandomGetResourceByTypeAndTag(String type, String tag);
|
||||
|
||||
public abstract ResourceMap doRandomGetTemplateByTypeAndTag(String type, String tag);
|
||||
|
||||
public abstract ResourceMap doDeleteTemplate(String type, String id);
|
||||
|
||||
public abstract Resource doDeleteResource(String type, String id);
|
||||
|
||||
public abstract void doAddResource(String type, Resource resource);
|
||||
|
||||
public abstract void doAddTemplate(String type, ResourceMap template);
|
||||
|
||||
|
||||
public static class ChainListener implements ResourceListener {
|
||||
protected List<ResourceListener> listeners = new ArrayList<>();
|
||||
|
||||
public ChainListener() {
|
||||
|
||||
}
|
||||
|
||||
public void addListener(ResourceListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(ResourceListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInit(ResourceStore resourceStore, ImageCaptchaResourceManager resourceManager) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onInit(resourceStore, resourceManager);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddResource(String type, Resource resource) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onAddResource(type, resource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddTemplate(String type, ResourceMap template) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onAddTemplate(type, template);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearAllResources() {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onClearAllResources();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearAllTemplates() {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onClearAllTemplates();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRandomGetResourceByTypeAndTag(String type, String tag, Resource resource) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onRandomGetResourceByTypeAndTag(type, tag, resource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRandomGetTemplateByTypeAndTag(String type, String tag, ResourceMap template) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onRandomGetTemplateByTypeAndTag(type, tag, template);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteResource(String type, Resource resource) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onDeleteResource(type, resource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteTemplate(String type, ResourceMap template) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onDeleteTemplate(type, template);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cloud.tianai.captcha.resource;
|
||||
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author: 天爱有情
|
||||
* @date 2025/6/13 16:43
|
||||
* @Description 具有CRUD属性的资源存储器
|
||||
*/
|
||||
public interface CrudResourceStore extends ResourceStore {
|
||||
/**
|
||||
* 添加资源
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param resource 资源
|
||||
*/
|
||||
void addResource(String type, Resource resource);
|
||||
|
||||
|
||||
/**
|
||||
* 添加模板
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param template 模板
|
||||
*/
|
||||
void addTemplate(String type, ResourceMap template);
|
||||
|
||||
/**
|
||||
* 删除资源
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param id 资源ID
|
||||
* @return Resource
|
||||
*/
|
||||
Resource deleteResource(String type, String id);
|
||||
|
||||
/**
|
||||
* 删除模板
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param id 资源ID
|
||||
* @return ResourceMap
|
||||
*/
|
||||
ResourceMap deleteTemplate(String type, String id);
|
||||
|
||||
/**
|
||||
* 获取某个资源列表
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param tag 资源标签(可为空)
|
||||
* @return List<Resource>
|
||||
*/
|
||||
List<Resource> listResourcesByTypeAndTag(String type, String tag);
|
||||
|
||||
/**
|
||||
* 获取某个模板列表
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param tag 资源标签(可为空)
|
||||
* @return List<ResourceMap>
|
||||
*/
|
||||
List<ResourceMap> listTemplatesByTypeAndTag(String type, String tag);
|
||||
|
||||
|
||||
/**
|
||||
* 清除所有内置模板
|
||||
*/
|
||||
void clearAllTemplates();
|
||||
|
||||
/**
|
||||
* 清除所有内置资源
|
||||
*/
|
||||
void clearAllResources();
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public class DefaultBuiltInResources {
|
||||
|
||||
public static final String PATH_PREFIX = "classpath:META-INF/cut-image/template";
|
||||
|
||||
private static Map<String, Consumer<ResourceStore>> defaultTemplateResource = new HashMap<>(8);
|
||||
private static Map<String, Consumer<CrudResourceStore>> defaultTemplateResource = new HashMap<>(8);
|
||||
|
||||
|
||||
public DefaultBuiltInResources(String defaultPathPrefix) {
|
||||
@@ -75,17 +75,21 @@ public class DefaultBuiltInResources {
|
||||
|
||||
|
||||
public void addDefaultTemplate(String type, ResourceStore resourceStore) {
|
||||
Consumer<ResourceStore> resourceStoreConsumer = defaultTemplateResource.get(type);
|
||||
if (resourceStoreConsumer == null) {
|
||||
return;
|
||||
if (resourceStore instanceof CrudResourceStore) {
|
||||
Consumer<CrudResourceStore> resourceStoreConsumer = defaultTemplateResource.get(type);
|
||||
if (resourceStoreConsumer == null) {
|
||||
return;
|
||||
}
|
||||
resourceStoreConsumer.accept((CrudResourceStore) resourceStore);
|
||||
}
|
||||
resourceStoreConsumer.accept(resourceStore);
|
||||
}
|
||||
|
||||
public void addDefaultTemplate(ResourceStore resourceStore) {
|
||||
defaultTemplateResource.forEach((type, consumer) -> {
|
||||
consumer.accept(resourceStore);
|
||||
});
|
||||
if (resourceStore instanceof CrudResourceStore) {
|
||||
defaultTemplateResource.forEach((type, consumer) -> {
|
||||
consumer.accept((CrudResourceStore) resourceStore);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package cloud.tianai.captcha.resource;
|
||||
|
||||
import cloud.tianai.captcha.generator.common.FontWrapper;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import lombok.Data;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -20,7 +20,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @Description 一个用于统一缓存字体文件的对象
|
||||
*/
|
||||
@Slf4j
|
||||
public class FontCache implements ResourceListener {
|
||||
public class FontCache implements ResourceStore {
|
||||
|
||||
|
||||
public static final String FONT_TYPE = "font";
|
||||
@@ -31,19 +31,20 @@ public class FontCache implements ResourceListener {
|
||||
@Setter
|
||||
@Getter
|
||||
private int fontSize = 70;
|
||||
public static FontCache getInstance() {
|
||||
return INSTANCE.INSTANCE;
|
||||
}
|
||||
|
||||
public FontCache() {
|
||||
|
||||
|
||||
public FontCache(ResourceStore resourceStore) {
|
||||
this.resourceStore = resourceStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInit(ResourceStore resourceStore, ImageCaptchaResourceManager resourceManager) {
|
||||
this.resourceStore = resourceStore;
|
||||
public void init(ImageCaptchaResourceManager resourceManager) {
|
||||
resourceStore.init(resourceManager);
|
||||
this.resourceManager = resourceManager;
|
||||
}
|
||||
|
||||
|
||||
public FontWrapper getFont(Resource resource) {
|
||||
try (InputStream stream = resourceManager.getResourceInputStream(resource)) {
|
||||
Font font = Font.createFont(0, stream);
|
||||
@@ -53,43 +54,27 @@ public class FontCache implements ResourceListener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String calcId(Resource resource) {
|
||||
// 缓存id, 避免重复加载。 多个验证码可能使用同一个字体, 这里不使用资源ID作为缓存ID, 而是使用type+data作为缓存ID。
|
||||
return resource.getType() + "_" + resource.getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddResource(String type, Resource resource) {
|
||||
public List<Resource> randomGetResourceByTypeAndTag(String type, String tag, Integer quantity) {
|
||||
List<Resource> resources = resourceStore.randomGetResourceByTypeAndTag(type, tag, quantity);
|
||||
// 字体增强
|
||||
if (FONT_TYPE.equalsIgnoreCase(type)) {
|
||||
fontMap.computeIfAbsent(resource.getId(), v -> getFont(resource));
|
||||
for (Resource resource : resources) {
|
||||
FontWrapper fontWrapper = fontMap.computeIfAbsent(calcId(resource), v -> getFont(resource));
|
||||
resource.setExtra(fontWrapper);
|
||||
}
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteResource(String type, Resource resource) {
|
||||
if (FONT_TYPE.equalsIgnoreCase(type)) {
|
||||
fontMap.remove(resource.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearAllResources() {
|
||||
fontMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRandomGetResourceByTypeAndTag(String type, String tag, Resource resource) {
|
||||
if (FONT_TYPE.equalsIgnoreCase(type)) {
|
||||
FontWrapper fontWrapper = fontMap.computeIfAbsent(resource.getId(), v -> getFont(resource));
|
||||
|
||||
resource.setExtra(fontWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadAllFonts() {
|
||||
List<Resource> resources = resourceStore.listResourcesByTypeAndTag(FONT_TYPE, null);
|
||||
for (Resource resource : resources) {
|
||||
fontMap.computeIfAbsent(resource.getId(), v -> getFont(resource));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class INSTANCE {
|
||||
private static final FontCache INSTANCE = new FontCache();
|
||||
public List<ResourceMap> randomGetTemplateByTypeAndTag(String type, String tag, Integer quantity) {
|
||||
return resourceStore.randomGetTemplateByTypeAndTag(type, tag, quantity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,27 @@ public interface ImageCaptchaResourceManager {
|
||||
*/
|
||||
Resource randomGetResource(String type, String tag);
|
||||
|
||||
|
||||
/**
|
||||
* 随机获取某个模板
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param tag 二级过滤,可以为空
|
||||
* @param quantity 一次性获取的数量
|
||||
* @return Map<String, Resource>
|
||||
*/
|
||||
List<ResourceMap> randomGetTemplate(String type, String tag, Integer quantity);
|
||||
|
||||
/**
|
||||
* 随机获取某个资源对象
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param tag 二级过滤,可以为空
|
||||
* @param quantity 一次性获取的数量
|
||||
* @return Resource
|
||||
*/
|
||||
List<Resource> randomGetResource(String type, String tag, Integer quantity);
|
||||
|
||||
/**
|
||||
* 获取真正的资源流通过资源对象
|
||||
*
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package cloud.tianai.captcha.resource;
|
||||
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
|
||||
|
||||
/**
|
||||
* @Author: 天爱有情
|
||||
* @date 2024/11/19 9:26
|
||||
* @Description 此类负责对 ResourceStore 进行一下扩展增强, 对ResourceStore的相关方法添加一个hook回调
|
||||
*/
|
||||
public interface ResourceListener {
|
||||
|
||||
default void onInit(ResourceStore resourceStore, ImageCaptchaResourceManager resourceManager) {
|
||||
}
|
||||
|
||||
|
||||
default void onAddResource(String type, Resource resource) {
|
||||
|
||||
}
|
||||
|
||||
default void onAddTemplate(String type, ResourceMap template) {
|
||||
|
||||
}
|
||||
|
||||
default void onDeleteResource(String type, Resource resource) {
|
||||
|
||||
}
|
||||
|
||||
default void onDeleteTemplate(String type, ResourceMap template) {
|
||||
|
||||
}
|
||||
|
||||
default void onClearAllResources() {
|
||||
|
||||
}
|
||||
|
||||
default void onClearAllTemplates() {
|
||||
|
||||
}
|
||||
|
||||
default void onRandomGetResourceByTypeAndTag(String type, String tag, Resource resource) {
|
||||
|
||||
}
|
||||
|
||||
default void onRandomGetTemplateByTypeAndTag(String type, String tag, ResourceMap template) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -13,65 +13,6 @@ import java.util.List;
|
||||
public interface ResourceStore {
|
||||
|
||||
void init(ImageCaptchaResourceManager resourceManager);
|
||||
/**
|
||||
* 给ResourceStore添加hook,用于一些扩展
|
||||
*
|
||||
* @param hook
|
||||
*/
|
||||
void addListener(ResourceListener hook);
|
||||
|
||||
/**
|
||||
* 添加资源
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param resource 资源
|
||||
*/
|
||||
void addResource(String type, Resource resource);
|
||||
|
||||
|
||||
/**
|
||||
* 添加模板
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param template 模板
|
||||
*/
|
||||
void addTemplate(String type, ResourceMap template);
|
||||
|
||||
/**
|
||||
* 删除资源
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param id 资源ID
|
||||
* @return Resource
|
||||
*/
|
||||
Resource deleteResource(String type, String id);
|
||||
|
||||
/**
|
||||
* 删除模板
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param id 资源ID
|
||||
* @return ResourceMap
|
||||
*/
|
||||
ResourceMap deleteTemplate(String type, String id);
|
||||
|
||||
/**
|
||||
* 获取某个资源列表
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param tag 资源标签(可为空)
|
||||
* @return List<Resource>
|
||||
*/
|
||||
List<Resource> listResourcesByTypeAndTag(String type, String tag);
|
||||
|
||||
/**
|
||||
* 获取某个模板列表
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param tag 资源标签(可为空)
|
||||
* @return List<ResourceMap>
|
||||
*/
|
||||
List<ResourceMap> listTemplatesByTypeAndTag(String type, String tag);
|
||||
|
||||
/**
|
||||
* 随机获取某个资源
|
||||
@@ -79,7 +20,7 @@ public interface ResourceStore {
|
||||
* @param type type
|
||||
* @return Resource
|
||||
*/
|
||||
Resource randomGetResourceByTypeAndTag(String type, String tag);
|
||||
List<Resource> randomGetResourceByTypeAndTag(String type, String tag, Integer quantity);
|
||||
|
||||
/**
|
||||
* 随机获取某个模板通过type
|
||||
@@ -87,16 +28,5 @@ public interface ResourceStore {
|
||||
* @param type type
|
||||
* @return Map<String, Resource>
|
||||
*/
|
||||
ResourceMap randomGetTemplateByTypeAndTag(String type, String tag);
|
||||
|
||||
/**
|
||||
* 清除所有内置模板
|
||||
*/
|
||||
void clearAllTemplates();
|
||||
|
||||
/**
|
||||
* 清除所有内置资源
|
||||
*/
|
||||
void clearAllResources();
|
||||
|
||||
List<ResourceMap> randomGetTemplateByTypeAndTag(String type, String tag,Integer quantity);
|
||||
}
|
||||
|
||||
+29
-11
@@ -1,12 +1,15 @@
|
||||
package cloud.tianai.captcha.resource.impl;
|
||||
|
||||
import cloud.tianai.captcha.common.util.CollectionUtils;
|
||||
import cloud.tianai.captcha.resource.*;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @Author: 天爱有情
|
||||
@@ -36,28 +39,43 @@ public class DefaultImageCaptchaResourceManager implements ImageCaptchaResourceM
|
||||
this.resourceStore = new LocalMemoryResourceStore();
|
||||
}
|
||||
// 在这里临时加上字体缓存器
|
||||
resourceStore.addListener(FontCache.getInstance());
|
||||
resourceStore = new FontCache(resourceStore);
|
||||
resourceStore.init(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceMap randomGetTemplate(String type, String tag) {
|
||||
ResourceMap resourceMap = resourceStore.randomGetTemplateByTypeAndTag(type, tag);
|
||||
if (resourceMap == null) {
|
||||
throw new IllegalStateException("随机获取模板错误,store中模板为空, type:" + type);
|
||||
}
|
||||
return resourceMap;
|
||||
return randomGetTemplate(type, tag, 1).get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource randomGetResource(String type, String tag) {
|
||||
Resource resource = resourceStore.randomGetResourceByTypeAndTag(type, tag);
|
||||
if (resource == null) {
|
||||
throw new IllegalStateException("随机获取资源错误,store中资源为空, type:" + type);
|
||||
}
|
||||
return resource;
|
||||
return randomGetResource(type, tag, 1).get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceMap> randomGetTemplate(String type, String tag, Integer quantity) {
|
||||
List<ResourceMap> resourceMaps = resourceStore.randomGetTemplateByTypeAndTag(type, tag, quantity);
|
||||
if (CollectionUtils.isEmpty(resourceMaps) || resourceMaps.size() != quantity) {
|
||||
throw new IllegalStateException("随机获取**模板**错误,获取到的数量和指定数量不一致," +
|
||||
" 指定获取数量[" + quantity + "],获取到的数据:[" + Optional.ofNullable(resourceMaps).orElse(Collections.emptyList()).size() + "], " +
|
||||
"[type:" + type + ",tag:" + tag + "]");
|
||||
}
|
||||
return resourceMaps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> randomGetResource(String type, String tag, Integer quantity) {
|
||||
List<Resource> resources = resourceStore.randomGetResourceByTypeAndTag(type, tag, quantity);
|
||||
if (CollectionUtils.isEmpty(resources) || resources.size() != quantity) {
|
||||
throw new IllegalStateException("随机获取**资源**错误,获取到的数量和指定数量不一致," +
|
||||
" 指定获取数量[" + quantity + "],获取到的数据:[" + Optional.ofNullable(resources).orElse(Collections.emptyList()).size() + "], " +
|
||||
"[type:" + type + ",tag:" + tag + "]");
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InputStream getResourceInputStream(Resource resource) {
|
||||
return resourceProviders.getResourceInputStream(resource);
|
||||
|
||||
@@ -3,7 +3,8 @@ package cloud.tianai.captcha.resource.impl;
|
||||
import cloud.tianai.captcha.common.constant.CommonConstant;
|
||||
import cloud.tianai.captcha.common.util.CollectionUtils;
|
||||
import cloud.tianai.captcha.common.util.ObjectUtils;
|
||||
import cloud.tianai.captcha.resource.AbstractResourceStore;
|
||||
import cloud.tianai.captcha.resource.CrudResourceStore;
|
||||
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
|
||||
|
||||
@@ -15,43 +16,52 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
* @date 2021/8/7 15:43
|
||||
* @Description 默认的资源存储
|
||||
*/
|
||||
public class LocalMemoryResourceStore extends AbstractResourceStore {
|
||||
private static final String TYPE_TAG_SPLIT_FLAG = "|";
|
||||
|
||||
public class LocalMemoryResourceStore implements CrudResourceStore {
|
||||
/** 用于检索 type和tag. */
|
||||
private Map<String, List<ResourceMap>> templateResourceTagMap = new HashMap<>(2);
|
||||
private Map<String, List<Resource>> resourceTagMap = new HashMap<>(2);
|
||||
private final Map<String, Map<String, List<ResourceMap>>> templateResourceTagMap = new HashMap<>(2);
|
||||
private final Map<String, Map<String, List<Resource>>> resourceTagMap = new HashMap<>(2);
|
||||
|
||||
|
||||
private void ensureTypeTagMapExists(Map<String, Map<String, List<Resource>>> map, String type, String tag) {
|
||||
map.computeIfAbsent(type, k -> new HashMap<>())
|
||||
.computeIfAbsent(tag, k -> new ArrayList<>(20));
|
||||
}
|
||||
|
||||
private void ensureTypeTagMapExistsForTemplate(Map<String, Map<String, List<ResourceMap>>> map, String type, String tag) {
|
||||
map.computeIfAbsent(type, k -> new HashMap<>())
|
||||
.computeIfAbsent(tag, k -> new ArrayList<>(2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAddResource(String type, Resource resource) {
|
||||
public void addResource(String type, Resource resource) {
|
||||
if (ObjectUtils.isEmpty(resource.getTag())) {
|
||||
resource.setTag(CommonConstant.DEFAULT_TAG);
|
||||
}
|
||||
resourceTagMap.computeIfAbsent(mergeTypeAndTag(type, resource.getTag()), k -> new ArrayList<>(20)).add(resource);
|
||||
ensureTypeTagMapExists(resourceTagMap, type, resource.getTag());
|
||||
resourceTagMap.get(type).get(resource.getTag()).add(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAddTemplate(String type, ResourceMap template) {
|
||||
public void addTemplate(String type, ResourceMap template) {
|
||||
if (ObjectUtils.isEmpty(template.getTag())) {
|
||||
template.setTag(CommonConstant.DEFAULT_TAG);
|
||||
}
|
||||
templateResourceTagMap.computeIfAbsent(mergeTypeAndTag(type, template.getTag()), k -> new ArrayList<>(2)).add(template);
|
||||
ensureTypeTagMapExistsForTemplate(templateResourceTagMap, type, template.getTag());
|
||||
templateResourceTagMap.get(type).get(template.getTag()).add(template);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource doDeleteResource(String type, String id) {
|
||||
for (Map.Entry<String, List<Resource>> entry : resourceTagMap.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
List<Resource> v = entry.getValue();
|
||||
String splitType = splitTypeTag(k)[0];
|
||||
if (splitType.equals(type)) {
|
||||
Iterator<Resource> iterator = v.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Resource next = iterator.next();
|
||||
if (next.getId().equals(id)) {
|
||||
iterator.remove();
|
||||
return next;
|
||||
}
|
||||
public Resource deleteResource(String type, String id) {
|
||||
Map<String, List<Resource>> tagMap = resourceTagMap.get(type);
|
||||
if (tagMap == null) return null;
|
||||
|
||||
for (List<Resource> resources : tagMap.values()) {
|
||||
Iterator<Resource> iterator = resources.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Resource res = iterator.next();
|
||||
if (res.getId().equals(id)) {
|
||||
iterator.remove();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,19 +69,17 @@ public class LocalMemoryResourceStore extends AbstractResourceStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceMap doDeleteTemplate(String type, String id) {
|
||||
for (Map.Entry<String, List<ResourceMap>> entry : templateResourceTagMap.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
List<ResourceMap> v = entry.getValue();
|
||||
String splitType = splitTypeTag(k)[0];
|
||||
if (splitType.equals(type)) {
|
||||
Iterator<ResourceMap> iterator = v.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ResourceMap next = iterator.next();
|
||||
if (next.getId().equals(id)) {
|
||||
iterator.remove();
|
||||
return next;
|
||||
}
|
||||
public ResourceMap deleteTemplate(String type, String id) {
|
||||
Map<String, List<ResourceMap>> tagMap = templateResourceTagMap.get(type);
|
||||
if (tagMap == null) return null;
|
||||
|
||||
for (List<ResourceMap> templates : tagMap.values()) {
|
||||
Iterator<ResourceMap> iterator = templates.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ResourceMap temp = iterator.next();
|
||||
if (temp.getId().equals(id)) {
|
||||
iterator.remove();
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,135 +89,97 @@ public class LocalMemoryResourceStore extends AbstractResourceStore {
|
||||
@Override
|
||||
public List<Resource> listResourcesByTypeAndTag(String type, String tag) {
|
||||
if (!ObjectUtils.isEmpty(tag)) {
|
||||
return resourceTagMap.get(mergeTypeAndTag(type, tag));
|
||||
Map<String, List<Resource>> tagMap = resourceTagMap.get(type);
|
||||
return tagMap == null ? Collections.emptyList() : tagMap.getOrDefault(tag, Collections.emptyList());
|
||||
}
|
||||
List<Resource> resourceList = new ArrayList<>();
|
||||
resourceTagMap.forEach((k, v) -> {
|
||||
String splitType = splitTypeTag(k)[0];
|
||||
if (splitType.equals(type)) {
|
||||
resourceList.addAll(v);
|
||||
List<Resource> result = new ArrayList<>();
|
||||
Map<String, List<Resource>> tagMap = resourceTagMap.get(type);
|
||||
if (tagMap != null) {
|
||||
for (List<Resource> list : tagMap.values()) {
|
||||
result.addAll(list);
|
||||
}
|
||||
});
|
||||
return resourceList;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceMap> listTemplatesByTypeAndTag(String type, String tag) {
|
||||
if (!ObjectUtils.isEmpty(tag)) {
|
||||
return templateResourceTagMap.get(mergeTypeAndTag(type, tag));
|
||||
Map<String, List<ResourceMap>> tagMap = templateResourceTagMap.get(type);
|
||||
return tagMap == null ? Collections.emptyList() : tagMap.getOrDefault(tag, Collections.emptyList());
|
||||
}
|
||||
List<ResourceMap> resourceMapList = new ArrayList<>();
|
||||
templateResourceTagMap.forEach((k, v) -> {
|
||||
String splitType = splitTypeTag(k)[0];
|
||||
if (splitType.equals(type)) {
|
||||
resourceMapList.addAll(v);
|
||||
List<ResourceMap> result = new ArrayList<>();
|
||||
Map<String, List<ResourceMap>> tagMap = templateResourceTagMap.get(type);
|
||||
if (tagMap != null) {
|
||||
for (List<ResourceMap> list : tagMap.values()) {
|
||||
result.addAll(list);
|
||||
}
|
||||
});
|
||||
return resourceMapList;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource doRandomGetResourceByTypeAndTag(String type, String tag) {
|
||||
List<Resource> resources = resourceTagMap.get(mergeTypeAndTag(type, tag));
|
||||
public void init(ImageCaptchaResourceManager resourceManager) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> randomGetResourceByTypeAndTag(String type, String tag, Integer quantity) {
|
||||
List<Resource> resources = listResourcesByTypeAndTag(type, tag);
|
||||
if (CollectionUtils.isEmpty(resources)) {
|
||||
throw new IllegalStateException("随机获取资源错误,store中资源为空, type:" + type + ",tag:" + tag);
|
||||
}
|
||||
if (resources.size() == 1) {
|
||||
return resources.get(0);
|
||||
int size = resources.size();
|
||||
if (quantity > size) {
|
||||
throw new IllegalArgumentException("请求的资源数量超过可用资源总数");
|
||||
}
|
||||
int randomIndex = ThreadLocalRandom.current().nextInt(resources.size());
|
||||
try {
|
||||
return resources.get(randomIndex);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
try {
|
||||
Thread.sleep(0);
|
||||
} catch (InterruptedException ex) {
|
||||
// ignore
|
||||
}
|
||||
return doRandomGetResourceByTypeAndTag(type, tag);
|
||||
|
||||
Set<Integer> indexes = new HashSet<>(quantity);
|
||||
while (indexes.size() < quantity) {
|
||||
indexes.add(ThreadLocalRandom.current().nextInt(size));
|
||||
}
|
||||
|
||||
List<Resource> result = new ArrayList<>(quantity);
|
||||
for (int index : indexes) {
|
||||
result.add(resources.get(index));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResourceMap doRandomGetTemplateByTypeAndTag(String type, String tag) {
|
||||
List<ResourceMap> templateList = templateResourceTagMap.get(mergeTypeAndTag(type, tag));
|
||||
if (CollectionUtils.isEmpty(templateList)) {
|
||||
public List<ResourceMap> randomGetTemplateByTypeAndTag(String type, String tag, Integer quantity) {
|
||||
List<ResourceMap> templates = listTemplatesByTypeAndTag(type, tag);
|
||||
if (CollectionUtils.isEmpty(templates)) {
|
||||
throw new IllegalStateException("随机获取模板错误,store中模板为空, type:" + type + ",tag:" + tag);
|
||||
}
|
||||
if (templateList.size() == 1) {
|
||||
return templateList.get(0);
|
||||
int size = templates.size();
|
||||
if (quantity > size) {
|
||||
throw new IllegalArgumentException("请求的模板数量超过可用模板总数");
|
||||
}
|
||||
int randomIndex = ThreadLocalRandom.current().nextInt(templateList.size());
|
||||
try {
|
||||
return templateList.get(randomIndex);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
try {
|
||||
Thread.sleep(0);
|
||||
} catch (InterruptedException ex) {
|
||||
// ignore
|
||||
}
|
||||
return doRandomGetTemplateByTypeAndTag(type, tag);
|
||||
|
||||
Set<Integer> indexes = new HashSet<>(quantity);
|
||||
while (indexes.size() < quantity) {
|
||||
indexes.add(ThreadLocalRandom.current().nextInt(size));
|
||||
}
|
||||
}
|
||||
|
||||
public String mergeTypeAndTag(String type, String tag) {
|
||||
if (tag == null) {
|
||||
tag = CommonConstant.DEFAULT_TAG;
|
||||
List<ResourceMap> result = new ArrayList<>(quantity);
|
||||
for (int index : indexes) {
|
||||
result.add(templates.get(index));
|
||||
}
|
||||
return type + TYPE_TAG_SPLIT_FLAG + tag;
|
||||
}
|
||||
|
||||
public String[] splitTypeTag(String k) {
|
||||
return k.split("\\" + TYPE_TAG_SPLIT_FLAG);
|
||||
}
|
||||
|
||||
|
||||
public void clearResources(String type, String tag) {
|
||||
resourceTagMap.remove(mergeTypeAndTag(type, tag));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doClearAllResources() {
|
||||
public void clearAllResources() {
|
||||
resourceTagMap.clear();
|
||||
}
|
||||
|
||||
public Map<String, List<Resource>> listAllResources() {
|
||||
return resourceTagMap;
|
||||
}
|
||||
|
||||
public List<Resource> listResourcesByType(String type, String tag) {
|
||||
return resourceTagMap.getOrDefault(mergeTypeAndTag(type, tag), Collections.emptyList());
|
||||
}
|
||||
|
||||
public int getAllResourceCount() {
|
||||
int count = 0;
|
||||
for (List<Resource> value : resourceTagMap.values()) {
|
||||
count += value.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getResourceCount(String type, String tag) {
|
||||
return resourceTagMap.getOrDefault(mergeTypeAndTag(type, tag), Collections.emptyList()).size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doClearAllTemplates() {
|
||||
public void clearAllTemplates() {
|
||||
templateResourceTagMap.clear();
|
||||
}
|
||||
|
||||
public void clearTemplates(String type, String tag) {
|
||||
templateResourceTagMap.remove(mergeTypeAndTag(type, tag));
|
||||
}
|
||||
|
||||
public List<ResourceMap> listTemplatesByType(String type, String tag) {
|
||||
return templateResourceTagMap.getOrDefault(mergeTypeAndTag(type, tag), Collections.emptyList());
|
||||
}
|
||||
|
||||
public Map<String, List<ResourceMap>> listAllTemplates() {
|
||||
return templateResourceTagMap;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+54
-13
@@ -38,6 +38,8 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
public static final String TOLERANT_KEY = "tolerant";
|
||||
/** 类型 key, 标识是哪张类型的验证码. */
|
||||
public static final String TYPE_KEY = "type";
|
||||
/** 点选类验证码验证时判断是否需要校验顺序. */
|
||||
public static final String CLICK_IMAGE_CHECK_ORDER_KEY = "click_image_check_order";
|
||||
/** 计算当前验证码用户滑动的百分比率 - 生成时的百分比率, 多个的话取均值. */
|
||||
public static final String USER_CURRENT_PERCENTAGE_STD = "user_current_percentage_std";
|
||||
public static final String USER_CURRENT_PERCENTAGE = "user_current_percentage";
|
||||
@@ -145,6 +147,12 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
map.put(TOLERANT_KEY, tolerant);
|
||||
}
|
||||
}
|
||||
// 新增是否判断顺序
|
||||
if (imageCaptchaInfo.getData() != null && imageCaptchaInfo.getData().getData() != null) {
|
||||
map.put(CLICK_IMAGE_CHECK_ORDER_KEY, imageCaptchaInfo.getData().getData().getOrDefault(CLICK_IMAGE_CHECK_ORDER_KEY, true));
|
||||
} else {
|
||||
map.put(CLICK_IMAGE_CHECK_ORDER_KEY, true);
|
||||
}
|
||||
// 添加点选验证数据
|
||||
map.put(PERCENTAGE_KEY, sb.toString());
|
||||
} else if (CaptchaTypeClassifier.isJigsawCaptcha(type)) {
|
||||
@@ -156,7 +164,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
@Override
|
||||
public ApiResponse<?> valid(ImageCaptchaTrack imageCaptchaTrack, AnyMap imageCaptchaValidData) {
|
||||
// 读容错值
|
||||
Float tolerant = imageCaptchaValidData.getFloat(TOLERANT_KEY, defaultTolerant);
|
||||
Float tolerant = recalculateTolerant(imageCaptchaValidData.getFloat(TOLERANT_KEY, defaultTolerant), imageCaptchaTrack, imageCaptchaValidData);
|
||||
// 读验证码类型
|
||||
String type = imageCaptchaValidData.getString(TYPE_KEY, CaptchaTypeConstant.SLIDER);
|
||||
// 验证前
|
||||
@@ -176,15 +184,26 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
return ApiResponse.ofCheckError("没有解析到滑动轨迹");
|
||||
}
|
||||
// 验证
|
||||
ApiResponse<?> response;
|
||||
boolean valid = doValid(imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
|
||||
return afterValid(valid, imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个模板方法, 用于自定义处理容错值
|
||||
*
|
||||
* @param tolerant 容错值
|
||||
* @param imageCaptchaTrack imageCaptchaTrack
|
||||
* @param imageCaptchaValidData captchaValidData
|
||||
* @return
|
||||
*/
|
||||
public Float recalculateTolerant(Float tolerant, ImageCaptchaTrack imageCaptchaTrack, AnyMap imageCaptchaValidData) {
|
||||
return tolerant;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证前
|
||||
*
|
||||
* @param imageCaptchaTrack sliderCaptchaTrack
|
||||
* @param imageCaptchaTrack imageCaptchaTrack
|
||||
* @param captchaValidData captchaValidData
|
||||
* @param tolerant tolerant
|
||||
* @param type type
|
||||
@@ -197,7 +216,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
/**
|
||||
* 验证后
|
||||
*
|
||||
* @param imageCaptchaTrack sliderCaptchaTrack
|
||||
* @param imageCaptchaTrack imageCaptchaTrack
|
||||
* @param captchaValidData captchaValidData
|
||||
* @param tolerant tolerant
|
||||
* @param type type
|
||||
@@ -241,7 +260,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
/**
|
||||
* 校验点选验证码
|
||||
*
|
||||
* @param imageCaptchaTrack sliderCaptchaTrack
|
||||
* @param imageCaptchaTrack imageCaptchaTrack
|
||||
* @param imageCaptchaValidData imageCaptchaValidData
|
||||
* @param tolerant tolerant
|
||||
* @param type type
|
||||
@@ -252,6 +271,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
Float tolerant,
|
||||
String type) {
|
||||
String validStr = imageCaptchaValidData.getString(PERCENTAGE_KEY, null);
|
||||
Object checkOrder = imageCaptchaValidData.getOrDefault(CLICK_IMAGE_CHECK_ORDER_KEY, true);
|
||||
if (ObjectUtils.isEmpty(validStr)) {
|
||||
return false;
|
||||
}
|
||||
@@ -271,17 +291,38 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<Double> percentages = new ArrayList<>();
|
||||
for (int i = 0; i < splitArr.length; i++) {
|
||||
ImageCaptchaTrack.Track track = clickTrackList.get(i);
|
||||
String posStr = splitArr[i];
|
||||
String[] posArr = posStr.split(",");
|
||||
float xPercentage = Float.parseFloat(posArr[0]);
|
||||
float yPercentage = Float.parseFloat(posArr[1]);
|
||||
|
||||
float calcXPercentage = calcPercentage(track.getX(), imageCaptchaTrack.getBgImageWidth());
|
||||
float calcYPercentage = calcPercentage(track.getY(), imageCaptchaTrack.getBgImageHeight());
|
||||
if (!checkPercentage(calcXPercentage, xPercentage, tolerant)
|
||||
|| !checkPercentage(calcYPercentage, yPercentage, tolerant)) {
|
||||
return false;
|
||||
float calcXPercentage = 0f;
|
||||
float calcYPercentage = 0f;
|
||||
if (Boolean.TRUE.equals(checkOrder)) {
|
||||
ImageCaptchaTrack.Track track = clickTrackList.get(0);
|
||||
calcXPercentage = calcPercentage(track.getX(), imageCaptchaTrack.getBgImageWidth());
|
||||
calcYPercentage = calcPercentage(track.getY(), imageCaptchaTrack.getBgImageHeight());
|
||||
if (!checkPercentage(calcXPercentage, xPercentage, tolerant)
|
||||
|| !checkPercentage(calcYPercentage, yPercentage, tolerant)) {
|
||||
return false;
|
||||
}
|
||||
clickTrackList.remove(0);
|
||||
} else {
|
||||
boolean flag = false;
|
||||
for (int a = 0; a < clickTrackList.size(); a++) {
|
||||
ImageCaptchaTrack.Track track = clickTrackList.get(a);
|
||||
calcXPercentage = calcPercentage(track.getX(), imageCaptchaTrack.getBgImageWidth());
|
||||
calcYPercentage = calcPercentage(track.getY(), imageCaptchaTrack.getBgImageHeight());
|
||||
if (checkPercentage(calcXPercentage, xPercentage, tolerant)
|
||||
&& checkPercentage(calcYPercentage, yPercentage, tolerant)) {
|
||||
// 验证命中
|
||||
clickTrackList.remove(a);
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (i > 0) {
|
||||
sb.append("|");
|
||||
@@ -296,7 +337,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
|
||||
/**
|
||||
* 校验滑动验证码
|
||||
*
|
||||
* @param imageCaptchaTrack sliderCaptchaTrack
|
||||
* @param imageCaptchaTrack imageCaptchaTrack
|
||||
* @param imageCaptchaValidData imageCaptchaValidData
|
||||
* @param tolerant tolerant
|
||||
* @param type type
|
||||
|
||||
Reference in New Issue
Block a user