refactor(resource): 重构资源存储和管理逻辑

- 移除了 AbstractResourceStore 类
- 新增了 CrudResourceStore 接口,定义了 CRUD操作
- 修改了 DefaultImageCaptchaResourceManager,支持批量获取资源和模板
- 重构了 FontCache 类,改为实现 ResourceStore 接口
- 更新了相关应用类,使用新的资源管理逻辑
This commit is contained in:
天爱有情
2025-06-30 16:34:24 +08:00
parent 12d290919a
commit cb92a224d5
25 changed files with 386 additions and 586 deletions
@@ -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;
@@ -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);
@@ -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);
}
@@ -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;
}
}
@@ -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