From cb92a224d536e7855ae3231f27dbd8565d8a92af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E7=88=B1=E6=9C=89=E6=83=85?= Date: Mon, 30 Jun 2025 16:34:24 +0800 Subject: [PATCH] =?UTF-8?q?refactor(resource):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E5=AD=98=E5=82=A8=E5=92=8C=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除了 AbstractResourceStore 类 - 新增了 CrudResourceStore 接口,定义了 CRUD操作 - 修改了 DefaultImageCaptchaResourceManager,支持批量获取资源和模板 - 重构了 FontCache 类,改为实现 ResourceStore 接口 - 更新了相关应用类,使用新的资源管理逻辑 --- pom.xml | 2 +- readme.md | 9 +- .../DefaultImageCaptchaApplication.java | 26 +- .../FilterImageCaptchaApplication.java | 11 +- .../application/ImageCaptchaApplication.java | 11 +- .../captcha/application/TACBuilder.java | 17 +- .../application/vo/ImageCaptchaVO.java | 3 + .../common/constant/CommonConstant.java | 7 +- .../captcha/common/response/ApiResponse.java | 2 +- .../common/model/dto/GenerateParam.java | 3 +- .../impl/MultiImageCaptchaGenerator.java | 4 + .../StandardSliderImageCaptchaGenerator.java | 12 +- .../interceptor/CaptchaInterceptor.java | 7 +- .../interceptor/CaptchaInterceptorGroup.java | 11 +- .../impl/BasicTrackCaptchaInterceptor.java | 2 +- .../resource/AbstractResourceStore.java | 203 ---------------- .../captcha/resource/CrudResourceStore.java | 77 ++++++ .../resource/DefaultBuiltInResources.java | 20 +- .../tianai/captcha/resource/FontCache.java | 65 ++--- .../resource/ImageCaptchaResourceManager.java | 21 ++ .../captcha/resource/ResourceListener.java | 50 ---- .../captcha/resource/ResourceStore.java | 74 +----- .../DefaultImageCaptchaResourceManager.java | 40 ++- .../impl/LocalMemoryResourceStore.java | 228 ++++++++---------- .../impl/SimpleImageCaptchaValidator.java | 67 ++++- 25 files changed, 386 insertions(+), 586 deletions(-) delete mode 100644 src/main/java/cloud/tianai/captcha/resource/AbstractResourceStore.java create mode 100644 src/main/java/cloud/tianai/captcha/resource/CrudResourceStore.java delete mode 100644 src/main/java/cloud/tianai/captcha/resource/ResourceListener.java diff --git a/pom.xml b/pom.xml index ab79290..1008997 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 cloud.tianai.captcha tianai-captcha - 1.5.2 + 1.5.3 tianai-captcha 行为验证码 diff --git a/readme.md b/readme.md index 6950efc..1f3eb06 100644 --- a/readme.md +++ b/readme.md @@ -27,12 +27,17 @@ > 注意: 如果你项目是使用的**Springboot**, > > -请使用SpringBoot脚手架工具[tianai-captcha-springboot-starter](https://gitcode.com/tiana/tianai-captcha-springboot-starter); +请使用SpringBoot脚手架工具 + - [tianai-captcha-springboot-starter(gitee)](https://gitee.com/tianai/tianai-captcha-springboot-starter); + - [tianai-captcha-springboot-starter(gitcode)](https://gitcode.com/tiana/tianai-captcha-springboot-starter); + - [tianai-captcha-springboot-starter(github)](https://github.com/tianaiyouqing/tianai-captcha-springboot-starter); > > 该工具对tianai-captcha验证码进行了封装,使其使用更加方便快捷 -> **写好的验证码demo移步 [tianai-captcha-demo](https://gitcode.com/tiana/tianai-captcha-demo)** +> **写好的验证码demo移步 +> - [tianai-captcha-demo(gitee)](https://gitee.com/tianai/tianai-captcha-demo) +> - [tianai-captcha-demo(gitcode)](https://gitcode.com/tiana/tianai-captcha-demo) ### 1. 导入xml diff --git a/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java b/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java index 946f1b9..a9947c5 100644 --- a/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java +++ b/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java @@ -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 generateCaptcha() { + public ApiResponse generateCaptcha() { // 生成滑块验证码 return generateCaptcha(CaptchaTypeConstant.SLIDER); } @Override - public CaptchaResponse generateCaptcha(String type) { + public ApiResponse generateCaptcha(String type) { GenerateParam generateParam = new GenerateParam(); generateParam.setType(type); return generateCaptcha(generateParam); } @Override - public CaptchaResponse generateCaptcha(GenerateParam param) { - CaptchaResponse captchaResponse = beforeGenerateCaptcha(param); + public ApiResponse generateCaptcha(GenerateParam param) { + ApiResponse captchaResponse = beforeGenerateCaptcha(param); if (captchaResponse != null) { return captchaResponse; } @@ -103,12 +102,12 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication { } @Override - public CaptchaResponse generateCaptcha(CaptchaImageType captchaImageType) { + public ApiResponse generateCaptcha(CaptchaImageType captchaImageType) { return generateCaptcha(CaptchaTypeConstant.SLIDER, captchaImageType); } @Override - public CaptchaResponse generateCaptcha(String type, CaptchaImageType captchaImageType) { + public ApiResponse 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 convertToCaptchaResponse(ImageCaptchaInfo imageCaptchaInfo) { + public ApiResponse convertToCaptchaResponse(ImageCaptchaInfo imageCaptchaInfo) { if (imageCaptchaInfo == null) { // 要是生成失败 throw new ImageCaptchaException("生成验证码失败,验证码生成为空"); } // 生成ID String id = generatorId(imageCaptchaInfo); - CaptchaResponse response = beforeGenerateImageCaptchaValidData(imageCaptchaInfo); + ApiResponse 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 captchaResponse) { + private void afterGenerateCaptcha(ImageCaptchaInfo imageCaptchaInfo, ApiResponse captchaResponse) { captchaInterceptor.afterGenerateCaptcha(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo, captchaResponse); } - private CaptchaResponse beforeGenerateCaptcha(GenerateParam param) { + private ApiResponse beforeGenerateCaptcha(GenerateParam param) { return captchaInterceptor.beforeGenerateCaptcha(captchaInterceptor.createContext(), param.getType(), param); } - private CaptchaResponse beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) { + private ApiResponse beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) { return captchaInterceptor.beforeGenerateImageCaptchaValidData(captchaInterceptor.createContext(), imageCaptchaInfo.getType(), imageCaptchaInfo); } diff --git a/src/main/java/cloud/tianai/captcha/application/FilterImageCaptchaApplication.java b/src/main/java/cloud/tianai/captcha/application/FilterImageCaptchaApplication.java index dcca9bb..55aedf6 100644 --- a/src/main/java/cloud/tianai/captcha/application/FilterImageCaptchaApplication.java +++ b/src/main/java/cloud/tianai/captcha/application/FilterImageCaptchaApplication.java @@ -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 generateCaptcha() { + public ApiResponse generateCaptcha() { return target.generateCaptcha(); } @Override - public CaptchaResponse generateCaptcha(String type) { + public ApiResponse generateCaptcha(String type) { return target.generateCaptcha(type); } @Override - public CaptchaResponse generateCaptcha(CaptchaImageType captchaImageType) { + public ApiResponse generateCaptcha(CaptchaImageType captchaImageType) { return target.generateCaptcha(captchaImageType); } @Override - public CaptchaResponse generateCaptcha(String type, CaptchaImageType captchaImageType) { + public ApiResponse generateCaptcha(String type, CaptchaImageType captchaImageType) { return target.generateCaptcha(type, captchaImageType); } @Override - public CaptchaResponse generateCaptcha(GenerateParam param) { + public ApiResponse generateCaptcha(GenerateParam param) { return target.generateCaptcha(param); } diff --git a/src/main/java/cloud/tianai/captcha/application/ImageCaptchaApplication.java b/src/main/java/cloud/tianai/captcha/application/ImageCaptchaApplication.java index c751d4a..a89502b 100644 --- a/src/main/java/cloud/tianai/captcha/application/ImageCaptchaApplication.java +++ b/src/main/java/cloud/tianai/captcha/application/ImageCaptchaApplication.java @@ -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 generateCaptcha(); + ApiResponse generateCaptcha(); /** * 生成滑块验证码 @@ -33,7 +32,7 @@ public interface ImageCaptchaApplication { * @param type type类型 * @return CaptchaResponse */ - CaptchaResponse generateCaptcha(String type); + ApiResponse generateCaptcha(String type); /** * 生成滑块验证码 @@ -41,7 +40,7 @@ public interface ImageCaptchaApplication { * @param captchaImageType 要生成webp还是jpg类型的图片 * @return CaptchaResponse */ - CaptchaResponse generateCaptcha(CaptchaImageType captchaImageType); + ApiResponse generateCaptcha(CaptchaImageType captchaImageType); /** * 生成验证码 @@ -50,7 +49,7 @@ public interface ImageCaptchaApplication { * @param captchaImageType CaptchaImageType * @return CaptchaResponse */ - CaptchaResponse generateCaptcha(String type, CaptchaImageType captchaImageType); + ApiResponse generateCaptcha(String type, CaptchaImageType captchaImageType); /** @@ -59,7 +58,7 @@ public interface ImageCaptchaApplication { * @param param param * @return CaptchaResponse */ - CaptchaResponse generateCaptcha(GenerateParam param); + ApiResponse generateCaptcha(GenerateParam param); /** * 匹配 diff --git a/src/main/java/cloud/tianai/captcha/application/TACBuilder.java b/src/main/java/cloud/tianai/captcha/application/TACBuilder.java index 147e39b..a86544a 100644 --- a/src/main/java/cloud/tianai/captcha/application/TACBuilder.java +++ b/src/main/java/cloud/tianai/captcha/application/TACBuilder.java @@ -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; } diff --git a/src/main/java/cloud/tianai/captcha/application/vo/ImageCaptchaVO.java b/src/main/java/cloud/tianai/captcha/application/vo/ImageCaptchaVO.java index 0754012..1aa3b08 100644 --- a/src/main/java/cloud/tianai/captcha/application/vo/ImageCaptchaVO.java +++ b/src/main/java/cloud/tianai/captcha/application/vo/ImageCaptchaVO.java @@ -10,6 +10,9 @@ import java.io.Serializable; @NoArgsConstructor @AllArgsConstructor public class ImageCaptchaVO implements Serializable { + + /** ID.*/ + private String id; /** 验证码类型.*/ private String type; /** 背景图.*/ diff --git a/src/main/java/cloud/tianai/captcha/common/constant/CommonConstant.java b/src/main/java/cloud/tianai/captcha/common/constant/CommonConstant.java index 6d7ac63..cb36923 100644 --- a/src/main/java/cloud/tianai/captcha/common/constant/CommonConstant.java +++ b/src/main/java/cloud/tianai/captcha/common/constant/CommonConstant.java @@ -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资源文件路径. diff --git a/src/main/java/cloud/tianai/captcha/common/response/ApiResponse.java b/src/main/java/cloud/tianai/captcha/common/response/ApiResponse.java index c0c2bf3..3f92e8c 100644 --- a/src/main/java/cloud/tianai/captcha/common/response/ApiResponse.java +++ b/src/main/java/cloud/tianai/captcha/common/response/ApiResponse.java @@ -7,7 +7,7 @@ import java.io.Serializable; /** * @Author: 天爱有情 * @date 2023/4/20 9:53 - * @Description 可能是最好用的API统一返回格式类 + * @Description API统一返回格式类 */ @Data @SuppressWarnings({"unchecked", "rawtypes"}) diff --git a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/GenerateParam.java b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/GenerateParam.java index 1637e80..808b02b 100644 --- a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/GenerateParam.java +++ b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/GenerateParam.java @@ -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: 天爱有情 diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/MultiImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/MultiImageCaptchaGenerator.java index 92ccb4c..3e674a7 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/MultiImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/MultiImageCaptchaGenerator.java @@ -28,6 +28,10 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator { protected Map imageCaptchaGeneratorMap = new ConcurrentHashMap<>(4); protected Map imageCaptchaGeneratorProviderMap = new HashMap<>(4); + // 点选类验证码字体 +// @Setter +// @Getter +// protected List fontWrappers; @Setter @Getter private String defaultCaptcha = SLIDER; diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java index b61ee70..e4ced7b 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java @@ -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, diff --git a/src/main/java/cloud/tianai/captcha/interceptor/CaptchaInterceptor.java b/src/main/java/cloud/tianai/captcha/interceptor/CaptchaInterceptor.java index b699565..207d78e 100644 --- a/src/main/java/cloud/tianai/captcha/interceptor/CaptchaInterceptor.java +++ b/src/main/java/cloud/tianai/captcha/interceptor/CaptchaInterceptor.java @@ -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 beforeGenerateCaptcha(Context context, String type, GenerateParam param) { + default ApiResponse beforeGenerateCaptcha(Context context, String type, GenerateParam param) { return null; } - default CaptchaResponse beforeGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo) { + default ApiResponse 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 captchaResponse) { + default void afterGenerateCaptcha(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, ApiResponse captchaResponse) { } default ApiResponse beforeValid(Context context, String type, MatchParam matchParam, AnyMap validData) { diff --git a/src/main/java/cloud/tianai/captcha/interceptor/CaptchaInterceptorGroup.java b/src/main/java/cloud/tianai/captcha/interceptor/CaptchaInterceptorGroup.java index 449f7db..48b1b1b 100644 --- a/src/main/java/cloud/tianai/captcha/interceptor/CaptchaInterceptorGroup.java +++ b/src/main/java/cloud/tianai/captcha/interceptor/CaptchaInterceptorGroup.java @@ -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 beforeGenerateCaptcha(Context context, String type, GenerateParam param) { + public ApiResponse beforeGenerateCaptcha(Context context, String type, GenerateParam param) { context = createContextIfNecessary(context); - CaptchaResponse captchaResponse = null; + ApiResponse 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 captchaResponse) { + public void afterGenerateCaptcha(Context context, String type, ImageCaptchaInfo imageCaptchaInfo, ApiResponse 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 beforeGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo) { + public ApiResponse beforeGenerateImageCaptchaValidData(Context context, String type, ImageCaptchaInfo imageCaptchaInfo) { context = createContextIfNecessary(context); - CaptchaResponse captchaResponse = null; + ApiResponse captchaResponse = null; while (context.next() < context.getCount()) { CaptchaInterceptor interceptor = validators.get(context.getCurrent()); captchaResponse = interceptor.beforeGenerateImageCaptchaValidData(context, type, imageCaptchaInfo); diff --git a/src/main/java/cloud/tianai/captcha/interceptor/impl/BasicTrackCaptchaInterceptor.java b/src/main/java/cloud/tianai/captcha/interceptor/impl/BasicTrackCaptchaInterceptor.java index 2838c65..8217f4d 100644 --- a/src/main/java/cloud/tianai/captcha/interceptor/impl/BasicTrackCaptchaInterceptor.java +++ b/src/main/java/cloud/tianai/captcha/interceptor/impl/BasicTrackCaptchaInterceptor.java @@ -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() { diff --git a/src/main/java/cloud/tianai/captcha/resource/AbstractResourceStore.java b/src/main/java/cloud/tianai/captcha/resource/AbstractResourceStore.java deleted file mode 100644 index cc1796f..0000000 --- a/src/main/java/cloud/tianai/captcha/resource/AbstractResourceStore.java +++ /dev/null @@ -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 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); - } - } - - - } -} diff --git a/src/main/java/cloud/tianai/captcha/resource/CrudResourceStore.java b/src/main/java/cloud/tianai/captcha/resource/CrudResourceStore.java new file mode 100644 index 0000000..a6621c7 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/resource/CrudResourceStore.java @@ -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 + */ + List listResourcesByTypeAndTag(String type, String tag); + + /** + * 获取某个模板列表 + * + * @param type 验证码类型 + * @param tag 资源标签(可为空) + * @return List + */ + List listTemplatesByTypeAndTag(String type, String tag); + + + /** + * 清除所有内置模板 + */ + void clearAllTemplates(); + + /** + * 清除所有内置资源 + */ + void clearAllResources(); +} diff --git a/src/main/java/cloud/tianai/captcha/resource/DefaultBuiltInResources.java b/src/main/java/cloud/tianai/captcha/resource/DefaultBuiltInResources.java index e4a4b7b..1636bf9 100644 --- a/src/main/java/cloud/tianai/captcha/resource/DefaultBuiltInResources.java +++ b/src/main/java/cloud/tianai/captcha/resource/DefaultBuiltInResources.java @@ -23,7 +23,7 @@ public class DefaultBuiltInResources { public static final String PATH_PREFIX = "classpath:META-INF/cut-image/template"; - private static Map> defaultTemplateResource = new HashMap<>(8); + private static Map> defaultTemplateResource = new HashMap<>(8); public DefaultBuiltInResources(String defaultPathPrefix) { @@ -75,17 +75,21 @@ public class DefaultBuiltInResources { public void addDefaultTemplate(String type, ResourceStore resourceStore) { - Consumer resourceStoreConsumer = defaultTemplateResource.get(type); - if (resourceStoreConsumer == null) { - return; + if (resourceStore instanceof CrudResourceStore) { + Consumer 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); + }); + } } } diff --git a/src/main/java/cloud/tianai/captcha/resource/FontCache.java b/src/main/java/cloud/tianai/captcha/resource/FontCache.java index bd6a007..2c46512 100644 --- a/src/main/java/cloud/tianai/captcha/resource/FontCache.java +++ b/src/main/java/cloud/tianai/captcha/resource/FontCache.java @@ -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 randomGetResourceByTypeAndTag(String type, String tag, Integer quantity) { + List 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 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 randomGetTemplateByTypeAndTag(String type, String tag, Integer quantity) { + return resourceStore.randomGetTemplateByTypeAndTag(type, tag, quantity); } } diff --git a/src/main/java/cloud/tianai/captcha/resource/ImageCaptchaResourceManager.java b/src/main/java/cloud/tianai/captcha/resource/ImageCaptchaResourceManager.java index 6ff62d9..255237c 100644 --- a/src/main/java/cloud/tianai/captcha/resource/ImageCaptchaResourceManager.java +++ b/src/main/java/cloud/tianai/captcha/resource/ImageCaptchaResourceManager.java @@ -31,6 +31,27 @@ public interface ImageCaptchaResourceManager { */ Resource randomGetResource(String type, String tag); + + /** + * 随机获取某个模板 + * + * @param type 验证码类型 + * @param tag 二级过滤,可以为空 + * @param quantity 一次性获取的数量 + * @return Map + */ + List randomGetTemplate(String type, String tag, Integer quantity); + + /** + * 随机获取某个资源对象 + * + * @param type 验证码类型 + * @param tag 二级过滤,可以为空 + * @param quantity 一次性获取的数量 + * @return Resource + */ + List randomGetResource(String type, String tag, Integer quantity); + /** * 获取真正的资源流通过资源对象 * diff --git a/src/main/java/cloud/tianai/captcha/resource/ResourceListener.java b/src/main/java/cloud/tianai/captcha/resource/ResourceListener.java deleted file mode 100644 index 09a14fc..0000000 --- a/src/main/java/cloud/tianai/captcha/resource/ResourceListener.java +++ /dev/null @@ -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) { - - } - - -} diff --git a/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java b/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java index 4d29c3b..069e17a 100644 --- a/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java +++ b/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java @@ -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 - */ - List listResourcesByTypeAndTag(String type, String tag); - - /** - * 获取某个模板列表 - * - * @param type 验证码类型 - * @param tag 资源标签(可为空) - * @return List - */ - List listTemplatesByTypeAndTag(String type, String tag); /** * 随机获取某个资源 @@ -79,7 +20,7 @@ public interface ResourceStore { * @param type type * @return Resource */ - Resource randomGetResourceByTypeAndTag(String type, String tag); + List randomGetResourceByTypeAndTag(String type, String tag, Integer quantity); /** * 随机获取某个模板通过type @@ -87,16 +28,5 @@ public interface ResourceStore { * @param type type * @return Map */ - ResourceMap randomGetTemplateByTypeAndTag(String type, String tag); - - /** - * 清除所有内置模板 - */ - void clearAllTemplates(); - - /** - * 清除所有内置资源 - */ - void clearAllResources(); - + List randomGetTemplateByTypeAndTag(String type, String tag,Integer quantity); } diff --git a/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java b/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java index ef554cb..f34d635 100644 --- a/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java +++ b/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java @@ -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 randomGetTemplate(String type, String tag, Integer quantity) { + List 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 randomGetResource(String type, String tag, Integer quantity) { + List 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); diff --git a/src/main/java/cloud/tianai/captcha/resource/impl/LocalMemoryResourceStore.java b/src/main/java/cloud/tianai/captcha/resource/impl/LocalMemoryResourceStore.java index ea31f43..fd047b3 100644 --- a/src/main/java/cloud/tianai/captcha/resource/impl/LocalMemoryResourceStore.java +++ b/src/main/java/cloud/tianai/captcha/resource/impl/LocalMemoryResourceStore.java @@ -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> templateResourceTagMap = new HashMap<>(2); - private Map> resourceTagMap = new HashMap<>(2); + private final Map>> templateResourceTagMap = new HashMap<>(2); + private final Map>> resourceTagMap = new HashMap<>(2); + + + private void ensureTypeTagMapExists(Map>> map, String type, String tag) { + map.computeIfAbsent(type, k -> new HashMap<>()) + .computeIfAbsent(tag, k -> new ArrayList<>(20)); + } + + private void ensureTypeTagMapExistsForTemplate(Map>> 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> entry : resourceTagMap.entrySet()) { - String k = entry.getKey(); - List v = entry.getValue(); - String splitType = splitTypeTag(k)[0]; - if (splitType.equals(type)) { - Iterator 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> tagMap = resourceTagMap.get(type); + if (tagMap == null) return null; + + for (List resources : tagMap.values()) { + Iterator 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> entry : templateResourceTagMap.entrySet()) { - String k = entry.getKey(); - List v = entry.getValue(); - String splitType = splitTypeTag(k)[0]; - if (splitType.equals(type)) { - Iterator 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> tagMap = templateResourceTagMap.get(type); + if (tagMap == null) return null; + + for (List templates : tagMap.values()) { + Iterator 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 listResourcesByTypeAndTag(String type, String tag) { if (!ObjectUtils.isEmpty(tag)) { - return resourceTagMap.get(mergeTypeAndTag(type, tag)); + Map> tagMap = resourceTagMap.get(type); + return tagMap == null ? Collections.emptyList() : tagMap.getOrDefault(tag, Collections.emptyList()); } - List resourceList = new ArrayList<>(); - resourceTagMap.forEach((k, v) -> { - String splitType = splitTypeTag(k)[0]; - if (splitType.equals(type)) { - resourceList.addAll(v); + List result = new ArrayList<>(); + Map> tagMap = resourceTagMap.get(type); + if (tagMap != null) { + for (List list : tagMap.values()) { + result.addAll(list); } - }); - return resourceList; + } + return result; } @Override public List listTemplatesByTypeAndTag(String type, String tag) { if (!ObjectUtils.isEmpty(tag)) { - return templateResourceTagMap.get(mergeTypeAndTag(type, tag)); + Map> tagMap = templateResourceTagMap.get(type); + return tagMap == null ? Collections.emptyList() : tagMap.getOrDefault(tag, Collections.emptyList()); } - List resourceMapList = new ArrayList<>(); - templateResourceTagMap.forEach((k, v) -> { - String splitType = splitTypeTag(k)[0]; - if (splitType.equals(type)) { - resourceMapList.addAll(v); + List result = new ArrayList<>(); + Map> tagMap = templateResourceTagMap.get(type); + if (tagMap != null) { + for (List list : tagMap.values()) { + result.addAll(list); } - }); - return resourceMapList; + } + return result; } @Override - public Resource doRandomGetResourceByTypeAndTag(String type, String tag) { - List resources = resourceTagMap.get(mergeTypeAndTag(type, tag)); + public void init(ImageCaptchaResourceManager resourceManager) { + + } + + @Override + public List randomGetResourceByTypeAndTag(String type, String tag, Integer quantity) { + List 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 indexes = new HashSet<>(quantity); + while (indexes.size() < quantity) { + indexes.add(ThreadLocalRandom.current().nextInt(size)); } + + List result = new ArrayList<>(quantity); + for (int index : indexes) { + result.add(resources.get(index)); + } + return result; } + @Override - public ResourceMap doRandomGetTemplateByTypeAndTag(String type, String tag) { - List templateList = templateResourceTagMap.get(mergeTypeAndTag(type, tag)); - if (CollectionUtils.isEmpty(templateList)) { + public List randomGetTemplateByTypeAndTag(String type, String tag, Integer quantity) { + List 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 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 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> listAllResources() { - return resourceTagMap; - } - - public List listResourcesByType(String type, String tag) { - return resourceTagMap.getOrDefault(mergeTypeAndTag(type, tag), Collections.emptyList()); - } - - public int getAllResourceCount() { - int count = 0; - for (List 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 listTemplatesByType(String type, String tag) { - return templateResourceTagMap.getOrDefault(mergeTypeAndTag(type, tag), Collections.emptyList()); - } - - public Map> listAllTemplates() { - return templateResourceTagMap; - } - } diff --git a/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java b/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java index 424743e..00951f8 100644 --- a/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java +++ b/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java @@ -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 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