diff --git a/src/main/java/cloud/tianai/captcha/application/TACBuilder.java b/src/main/java/cloud/tianai/captcha/application/TACBuilder.java index dbda9a0..147e39b 100644 --- a/src/main/java/cloud/tianai/captcha/application/TACBuilder.java +++ b/src/main/java/cloud/tianai/captcha/application/TACBuilder.java @@ -2,29 +2,22 @@ package cloud.tianai.captcha.application; import cloud.tianai.captcha.cache.CacheStore; import cloud.tianai.captcha.cache.impl.LocalCacheStore; -import cloud.tianai.captcha.common.util.CollectionUtils; import cloud.tianai.captcha.generator.ImageCaptchaGenerator; import cloud.tianai.captcha.generator.ImageTransform; -import cloud.tianai.captcha.generator.common.FontWrapper; 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.common.model.dto.Resource; import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; import cloud.tianai.captcha.resource.impl.DefaultImageCaptchaResourceManager; import cloud.tianai.captcha.resource.impl.LocalMemoryResourceStore; -import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider; import cloud.tianai.captcha.validator.ImageCaptchaValidator; import cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator; -import java.awt.*; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - /** * @Author: 天爱有情 * @date 2024/7/14 16:41 @@ -39,18 +32,20 @@ public class TACBuilder { private ImageCaptchaProperties prop = new ImageCaptchaProperties(); private ResourceStore resourceStore; private ImageTransform imageTransform; - private List fontWrappers = new ArrayList<>(); +// private List fontWrappers = new ArrayList<>(); public static TACBuilder builder() { - TACBuilder builder = new TACBuilder(); - // 默认设置本地的 - LocalMemoryResourceStore resourceStore = new LocalMemoryResourceStore(); - builder.resourceStore = resourceStore; + return TACBuilder.builder(new LocalMemoryResourceStore()); + } + + public static TACBuilder builder(ResourceStore resourceStore) { + TACBuilder builder = new TACBuilder(resourceStore); builder.prop = new ImageCaptchaProperties(); return builder; } - private TACBuilder() { + private TACBuilder(ResourceStore resourceStore) { + this.resourceStore = resourceStore; } public TACBuilder addDefaultTemplate(String defaultPathPrefix) { @@ -83,15 +78,11 @@ public class TACBuilder { return this; } - public TACBuilder addFont(FontWrapper fontWrapper) { - this.fontWrappers.add(fontWrapper); + public TACBuilder addFont(Resource resource) { + this.addResource(FontCache.FONT_TYPE, resource); return this; } - public TACBuilder addFont(Font font) { - return addFont(new FontWrapper(font)); - } - public TACBuilder cached(int size, int waitTime, int period, Long expireTime) { prop.setLocalCacheEnabled(true); @@ -117,10 +108,10 @@ public class TACBuilder { return this; } - public TACBuilder setResourceStore(ResourceStore resourceStore) { - this.resourceStore = resourceStore; - return this; - } +// public TACBuilder setResourceStore(ResourceStore resourceStore) { +// this.resourceStore = resourceStore; +// return this; +// } public TACBuilder addResource(String captchaType, Resource imageResource) { @@ -143,31 +134,19 @@ public class TACBuilder { cacheStore = new LocalCacheStore(); } if (generator == null) { - DefaultImageCaptchaResourceManager resourceManager = new DefaultImageCaptchaResourceManager(resourceStore); + ResourceProviders resourceProviders = new ResourceProviders(); + DefaultImageCaptchaResourceManager resourceManager = new DefaultImageCaptchaResourceManager(resourceStore, resourceProviders); generator = new MultiImageCaptchaGenerator(resourceManager, imageTransform); } - if (generator instanceof MultiImageCaptchaGenerator) { - if (CollectionUtils.isEmpty(fontWrappers)) { - // 添加默认字体 - try { - ClassPathResourceProvider resourceProvider = new ClassPathResourceProvider(); - InputStream stream = resourceProvider.getResourceInputStream(new Resource("classpath", "META-INF/fonts/SIMSUN.TTC")); - Font font = Font.createFont(Font.TRUETYPE_FONT, stream); - stream.close(); - fontWrappers.add(new FontWrapper(font)); - } catch (Exception e) { - throw new RuntimeException("读取默认字体包报错",e); - } - } - ((MultiImageCaptchaGenerator) generator).setFontWrappers(fontWrappers); - } +// if (generator instanceof MultiImageCaptchaGenerator) { +// ((MultiImageCaptchaGenerator) generator).setFontWrappers(fontWrappers); +// } if (validator == null) { validator = new SimpleImageCaptchaValidator(); } if (interceptor == null) { interceptor = EmptyCaptchaInterceptor.INSTANCE; } - DefaultImageCaptchaApplication application = new DefaultImageCaptchaApplication(generator, validator, cacheStore, prop, interceptor); return application; } diff --git a/src/main/java/cloud/tianai/captcha/common/util/UUIDUtils.java b/src/main/java/cloud/tianai/captcha/common/util/UUIDUtils.java new file mode 100644 index 0000000..996721f --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/common/util/UUIDUtils.java @@ -0,0 +1,9 @@ +package cloud.tianai.captcha.common.util; + +public class UUIDUtils { + + public static String getUUID() { + return java.util.UUID.randomUUID().toString().replace("-", ""); + } + +} 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 605368b..06ae069 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 @@ -62,4 +62,23 @@ public class GenerateParam { return param.getOrDefault(key, defaultValue); } + + public Object putIfAbsent(String key, Object value) { + return doGetOrCreateParam().putIfAbsent(key, value); + } + + + public void addParam(ParamKey paramKey, T value) { + addParam(paramKey.getKey(), value); + } + + public T getParam(ParamKey paramKey) { + return (T) getParam(paramKey.getKey()); + } + + public T getOrDefault(ParamKey paramKey, T defaultValue) { + return (T) getOrDefault(paramKey.getKey(), defaultValue); + } + + } diff --git a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ParamKey.java b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ParamKey.java new file mode 100644 index 0000000..a5d4780 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ParamKey.java @@ -0,0 +1,12 @@ +package cloud.tianai.captcha.generator.common.model.dto; + +/** + * @Author: 天爱有情 + * @date 2024/11/20 11:34 + * @Description 此接口的作用是在给 {@link GenerateParam} 添加/获取参数时做一个类型限制和转换 + */ +public interface ParamKey { + + String getKey(); + +} diff --git a/src/main/java/cloud/tianai/captcha/resource/AbstractResourceStore.java b/src/main/java/cloud/tianai/captcha/resource/AbstractResourceStore.java new file mode 100644 index 0000000..cc1796f --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/resource/AbstractResourceStore.java @@ -0,0 +1,203 @@ +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/FontCache.java b/src/main/java/cloud/tianai/captcha/resource/FontCache.java new file mode 100644 index 0000000..bd6a007 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/resource/FontCache.java @@ -0,0 +1,95 @@ +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 lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.awt.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Author: 天爱有情 + * @date 2024/11/19 11:25 + * @Description 一个用于统一缓存字体文件的对象 + */ +@Slf4j +public class FontCache implements ResourceListener { + + + public static final String FONT_TYPE = "font"; + private final Map fontMap = new ConcurrentHashMap<>(); + + private ResourceStore resourceStore; + private ImageCaptchaResourceManager resourceManager; + @Setter + @Getter + private int fontSize = 70; + public static FontCache getInstance() { + return INSTANCE.INSTANCE; + } + + public FontCache() { + } + + @Override + public void onInit(ResourceStore resourceStore, ImageCaptchaResourceManager resourceManager) { + this.resourceStore = resourceStore; + this.resourceManager = resourceManager; + } + + public FontWrapper getFont(Resource resource) { + try (InputStream stream = resourceManager.getResourceInputStream(resource)) { + Font font = Font.createFont(0, stream); + return new FontWrapper(font, fontSize); + } catch (FontFormatException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void onAddResource(String type, Resource resource) { + if (FONT_TYPE.equalsIgnoreCase(type)) { + fontMap.computeIfAbsent(resource.getId(), v -> getFont(resource)); + } + } + + @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(); + } +} diff --git a/src/main/java/cloud/tianai/captcha/resource/ResourceListener.java b/src/main/java/cloud/tianai/captcha/resource/ResourceListener.java new file mode 100644 index 0000000..09a14fc --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/resource/ResourceListener.java @@ -0,0 +1,50 @@ +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/ResourceProvider.java b/src/main/java/cloud/tianai/captcha/resource/ResourceProvider.java index ad78dde..2cf59de 100644 --- a/src/main/java/cloud/tianai/captcha/resource/ResourceProvider.java +++ b/src/main/java/cloud/tianai/captcha/resource/ResourceProvider.java @@ -22,10 +22,10 @@ public interface ResourceProvider { /** * 是否支持 * - * @param type type + * @param resource resource * @return boolean */ - boolean supported(String type); + boolean supported(Resource resource); /** * 放弃资源提供者名称 diff --git a/src/main/java/cloud/tianai/captcha/resource/ResourceProviders.java b/src/main/java/cloud/tianai/captcha/resource/ResourceProviders.java new file mode 100644 index 0000000..e98f3b7 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/resource/ResourceProviders.java @@ -0,0 +1,52 @@ +package cloud.tianai.captcha.resource; + +import cloud.tianai.captcha.resource.common.model.dto.Resource; +import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider; +import cloud.tianai.captcha.resource.impl.provider.FileResourceProvider; +import cloud.tianai.captcha.resource.impl.provider.URLResourceProvider; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ResourceProviders { + + private final List resourceProviderList = new ArrayList<>(8); + + + public ResourceProviders() { + registerResourceProvider(new URLResourceProvider()); + registerResourceProvider(new ClassPathResourceProvider()); + registerResourceProvider(new FileResourceProvider()); + } + + public void registerResourceProvider(ResourceProvider resourceProvider) { + deleteResourceProviderByName(resourceProvider.getName()); + resourceProviderList.add(resourceProvider); + } + + public boolean deleteResourceProviderByName(String name) { + return resourceProviderList.removeIf(r -> r.getName().equals(name)); + } + + public List listResourceProviders() { + return Collections.unmodifiableList(resourceProviderList); + } + + + public InputStream getResourceInputStream(Resource resource) { + for (ResourceProvider resourceProvider : resourceProviderList) { + if (resourceProvider.supported(resource)) { + InputStream resourceInputStream = resourceProvider.getResourceInputStream(resource); + if (resourceInputStream == null) { + throw new IllegalArgumentException("滑块验证码 ResourceProvider 读到的图片资源为空,providerName=[" + + resourceProvider.getName() + "], resource=[" + resource + "]"); + } + return resourceInputStream; + } + } + throw new IllegalStateException("没有找到Resource [" + resource.getType() + "]对应的资源提供者"); + } + +} diff --git a/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java b/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java index 308eb57..4d29c3b 100644 --- a/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java +++ b/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java @@ -3,6 +3,8 @@ 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 2022/5/7 9:04 @@ -10,6 +12,14 @@ import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; */ public interface ResourceStore { + void init(ImageCaptchaResourceManager resourceManager); + /** + * 给ResourceStore添加hook,用于一些扩展 + * + * @param hook + */ + void addListener(ResourceListener hook); + /** * 添加资源 * @@ -27,6 +37,42 @@ public interface ResourceStore { */ 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); + /** * 随机获取某个资源 * diff --git a/src/main/java/cloud/tianai/captcha/resource/common/model/dto/Resource.java b/src/main/java/cloud/tianai/captcha/resource/common/model/dto/Resource.java index a1f322c..0e23648 100644 --- a/src/main/java/cloud/tianai/captcha/resource/common/model/dto/Resource.java +++ b/src/main/java/cloud/tianai/captcha/resource/common/model/dto/Resource.java @@ -1,5 +1,6 @@ package cloud.tianai.captcha.resource.common.model.dto; +import cloud.tianai.captcha.common.util.UUIDUtils; import cloud.tianai.captcha.resource.ResourceProvider; import lombok.Data; import lombok.NoArgsConstructor; @@ -12,31 +13,37 @@ import lombok.NoArgsConstructor; @Data @NoArgsConstructor public class Resource { + /** 唯一ID. */ + private String id; /** 类型. */ private String type; /** 数据,传输给 {@link ResourceProvider} 的参数 */ public String data; - /** 标签.*/ + /** 标签. */ private String tag; - /** 提示.*/ + /** 提示. */ private String tip; - /** 扩展.*/ + /** 扩展. */ private Object extra; + public Resource(String type, String data) { - this.type = type; - this.data = data; + this(type, data, null); } public Resource(String type, String data, String tag) { - this.type = type; - this.data = data; - this.tag = tag; + this(type, data, tag, null); } public Resource(String type, String data, String tag, String tip) { + this(UUIDUtils.getUUID(), type, data, tag, tip); + } + + public Resource(String id, String type, String data, String tag, String tip) { + this.id = id; this.type = type; this.data = data; this.tag = tag; this.tip = tip; } + } diff --git a/src/main/java/cloud/tianai/captcha/resource/common/model/dto/ResourceMap.java b/src/main/java/cloud/tianai/captcha/resource/common/model/dto/ResourceMap.java index 6fa8aaa..bfd7fde 100644 --- a/src/main/java/cloud/tianai/captcha/resource/common/model/dto/ResourceMap.java +++ b/src/main/java/cloud/tianai/captcha/resource/common/model/dto/ResourceMap.java @@ -1,5 +1,6 @@ package cloud.tianai.captcha.resource.common.model.dto; +import cloud.tianai.captcha.common.util.UUIDUtils; import lombok.Data; import lombok.EqualsAndHashCode; @@ -16,25 +17,31 @@ import java.util.function.BiConsumer; @Data @EqualsAndHashCode public class ResourceMap { - + /** 唯一ID. */ + private String id; private Map resourceMap; private String tag; public ResourceMap(String tag) { - this.tag = tag; - this.resourceMap = new HashMap<>(); + this(tag, 10); } public ResourceMap(String tag, int initialCapacity) { + this(UUIDUtils.getUUID(), tag, initialCapacity); + } + + public ResourceMap(String id, String tag, int initialCapacity) { this.tag = tag; this.resourceMap = new HashMap<>(initialCapacity); + this.id = id; } public ResourceMap(int initialCapacity) { - this.resourceMap = new HashMap<>(initialCapacity); + this(null, initialCapacity); } public ResourceMap() { + this(null); } private Map getResourceMapOfCreate() { 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 9ff1277..ef554cb 100644 --- a/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java +++ b/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java @@ -1,17 +1,11 @@ package cloud.tianai.captcha.resource.impl; -import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; -import cloud.tianai.captcha.resource.ResourceProvider; -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.provider.ClassPathResourceProvider; -import cloud.tianai.captcha.resource.impl.provider.FileResourceProvider; -import cloud.tianai.captcha.resource.impl.provider.URLResourceProvider; +import lombok.Getter; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -24,15 +18,16 @@ public class DefaultImageCaptchaResourceManager implements ImageCaptchaResourceM /** 资源存储. */ private ResourceStore resourceStore; /** 资源转换 转换为stream流. */ - private final List resourceProviderList = new ArrayList<>(8); - + @Getter + private ResourceProviders resourceProviders; public DefaultImageCaptchaResourceManager() { init(); } - public DefaultImageCaptchaResourceManager(ResourceStore resourceStore) { + public DefaultImageCaptchaResourceManager(ResourceStore resourceStore, ResourceProviders resourceProviders) { this.resourceStore = resourceStore; + this.resourceProviders = resourceProviders; init(); } @@ -40,10 +35,9 @@ public class DefaultImageCaptchaResourceManager implements ImageCaptchaResourceM if (this.resourceStore == null) { this.resourceStore = new LocalMemoryResourceStore(); } - // 注入一些默认的提供者 - registerResourceProvider(new URLResourceProvider()); - registerResourceProvider(new ClassPathResourceProvider()); - registerResourceProvider(new FileResourceProvider()); + // 在这里临时加上字体缓存器 + resourceStore.addListener(FontCache.getInstance()); + resourceStore.init(this); } @Override @@ -66,33 +60,22 @@ public class DefaultImageCaptchaResourceManager implements ImageCaptchaResourceM @Override public InputStream getResourceInputStream(Resource resource) { - for (ResourceProvider resourceProvider : resourceProviderList) { - if (resourceProvider.supported(resource.getType())) { - InputStream resourceInputStream = resourceProvider.getResourceInputStream(resource); - if (resourceInputStream == null) { - throw new IllegalArgumentException("滑块验证码 ResourceProvider 读到的图片资源为空,providerName=[" - + resourceProvider.getName() + "], resource=[" + resource + "]"); - } - return resourceInputStream; - } - } - throw new IllegalStateException("没有找到Resource [" + resource.getType() + "]对应的资源提供者"); + return resourceProviders.getResourceInputStream(resource); } @Override public List listResourceProviders() { - return Collections.unmodifiableList(resourceProviderList); + return resourceProviders.listResourceProviders(); } @Override public void registerResourceProvider(ResourceProvider resourceProvider) { - deleteResourceProviderByName(resourceProvider.getName()); - resourceProviderList.add(resourceProvider); + resourceProviders.registerResourceProvider(resourceProvider); } @Override public boolean deleteResourceProviderByName(String name) { - return resourceProviderList.removeIf(r -> r.getName().equals(name)); + return resourceProviders.deleteResourceProviderByName(name); } @Override 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 a67834e..ea31f43 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,7 @@ 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.ResourceStore; +import cloud.tianai.captcha.resource.AbstractResourceStore; import cloud.tianai.captcha.resource.common.model.dto.Resource; import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; @@ -15,7 +15,7 @@ import java.util.concurrent.ThreadLocalRandom; * @date 2021/8/7 15:43 * @Description 默认的资源存储 */ -public class LocalMemoryResourceStore implements ResourceStore { +public class LocalMemoryResourceStore extends AbstractResourceStore { private static final String TYPE_TAG_SPLIT_FLAG = "|"; /** 用于检索 type和tag. */ @@ -23,7 +23,7 @@ public class LocalMemoryResourceStore implements ResourceStore { private Map> resourceTagMap = new HashMap<>(2); @Override - public void addResource(String type, Resource resource) { + public void doAddResource(String type, Resource resource) { if (ObjectUtils.isEmpty(resource.getTag())) { resource.setTag(CommonConstant.DEFAULT_TAG); } @@ -31,7 +31,7 @@ public class LocalMemoryResourceStore implements ResourceStore { } @Override - public void addTemplate(String type, ResourceMap template) { + public void doAddTemplate(String type, ResourceMap template) { if (ObjectUtils.isEmpty(template.getTag())) { template.setTag(CommonConstant.DEFAULT_TAG); } @@ -39,7 +39,77 @@ public class LocalMemoryResourceStore implements ResourceStore { } @Override - public Resource randomGetResourceByTypeAndTag(String type, String tag) { + 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; + } + } + } + } + return null; + } + + @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; + } + } + } + } + return null; + } + + @Override + public List listResourcesByTypeAndTag(String type, String tag) { + if (!ObjectUtils.isEmpty(tag)) { + return resourceTagMap.get(mergeTypeAndTag(type, tag)); + } + List resourceList = new ArrayList<>(); + resourceTagMap.forEach((k, v) -> { + String splitType = splitTypeTag(k)[0]; + if (splitType.equals(type)) { + resourceList.addAll(v); + } + }); + return resourceList; + } + + @Override + public List listTemplatesByTypeAndTag(String type, String tag) { + if (!ObjectUtils.isEmpty(tag)) { + return templateResourceTagMap.get(mergeTypeAndTag(type, tag)); + } + List resourceMapList = new ArrayList<>(); + templateResourceTagMap.forEach((k, v) -> { + String splitType = splitTypeTag(k)[0]; + if (splitType.equals(type)) { + resourceMapList.addAll(v); + } + }); + return resourceMapList; + } + + @Override + public Resource doRandomGetResourceByTypeAndTag(String type, String tag) { List resources = resourceTagMap.get(mergeTypeAndTag(type, tag)); if (CollectionUtils.isEmpty(resources)) { throw new IllegalStateException("随机获取资源错误,store中资源为空, type:" + type + ",tag:" + tag); @@ -48,11 +118,20 @@ public class LocalMemoryResourceStore implements ResourceStore { return resources.get(0); } int randomIndex = ThreadLocalRandom.current().nextInt(resources.size()); - return resources.get(randomIndex); + try { + return resources.get(randomIndex); + } catch (IndexOutOfBoundsException e) { + try { + Thread.sleep(0); + } catch (InterruptedException ex) { + // ignore + } + return doRandomGetResourceByTypeAndTag(type, tag); + } } @Override - public ResourceMap randomGetTemplateByTypeAndTag(String type, String tag) { + public ResourceMap doRandomGetTemplateByTypeAndTag(String type, String tag) { List templateList = templateResourceTagMap.get(mergeTypeAndTag(type, tag)); if (CollectionUtils.isEmpty(templateList)) { throw new IllegalStateException("随机获取模板错误,store中模板为空, type:" + type + ",tag:" + tag); @@ -61,7 +140,16 @@ public class LocalMemoryResourceStore implements ResourceStore { return templateList.get(0); } int randomIndex = ThreadLocalRandom.current().nextInt(templateList.size()); - return templateList.get(randomIndex); + try { + return templateList.get(randomIndex); + } catch (IndexOutOfBoundsException e) { + try { + Thread.sleep(0); + } catch (InterruptedException ex) { + // ignore + } + return doRandomGetTemplateByTypeAndTag(type, tag); + } } public String mergeTypeAndTag(String type, String tag) { @@ -71,13 +159,17 @@ public class LocalMemoryResourceStore implements ResourceStore { 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)); } @Override - public void clearAllResources() { + public void doClearAllResources() { resourceTagMap.clear(); } @@ -103,7 +195,7 @@ public class LocalMemoryResourceStore implements ResourceStore { @Override - public void clearAllTemplates() { + public void doClearAllTemplates() { templateResourceTagMap.clear(); } diff --git a/src/main/java/cloud/tianai/captcha/resource/impl/provider/ClassPathResourceProvider.java b/src/main/java/cloud/tianai/captcha/resource/impl/provider/ClassPathResourceProvider.java index 7cb0d51..1b99af6 100644 --- a/src/main/java/cloud/tianai/captcha/resource/impl/provider/ClassPathResourceProvider.java +++ b/src/main/java/cloud/tianai/captcha/resource/impl/provider/ClassPathResourceProvider.java @@ -20,8 +20,8 @@ public class ClassPathResourceProvider extends AbstractResourceProvider { } @Override - public boolean supported(String type) { - return NAME.equalsIgnoreCase(type); + public boolean supported(Resource resource) { + return NAME.equalsIgnoreCase(resource.getType()); } @Override diff --git a/src/main/java/cloud/tianai/captcha/resource/impl/provider/FileResourceProvider.java b/src/main/java/cloud/tianai/captcha/resource/impl/provider/FileResourceProvider.java index 065eda5..00186d7 100644 --- a/src/main/java/cloud/tianai/captcha/resource/impl/provider/FileResourceProvider.java +++ b/src/main/java/cloud/tianai/captcha/resource/impl/provider/FileResourceProvider.java @@ -24,8 +24,8 @@ public class FileResourceProvider extends AbstractResourceProvider { } @Override - public boolean supported(String type) { - return NAME.equalsIgnoreCase(type); + public boolean supported(Resource resource) { + return NAME.equalsIgnoreCase(resource.getType()); } @Override diff --git a/src/main/java/cloud/tianai/captcha/resource/impl/provider/URLResourceProvider.java b/src/main/java/cloud/tianai/captcha/resource/impl/provider/URLResourceProvider.java index 507598b..d7f0112 100644 --- a/src/main/java/cloud/tianai/captcha/resource/impl/provider/URLResourceProvider.java +++ b/src/main/java/cloud/tianai/captcha/resource/impl/provider/URLResourceProvider.java @@ -24,8 +24,8 @@ public class URLResourceProvider extends AbstractResourceProvider { } @Override - public boolean supported(String type) { - return NAME.equalsIgnoreCase(type); + public boolean supported(Resource resource) { + return NAME.equalsIgnoreCase(resource.getType()); } @Override diff --git a/src/main/test/java/example/readme/TACBuilderTest.java b/src/main/test/java/example/readme/TACBuilderTest.java index 4dbd150..4807d28 100644 --- a/src/main/test/java/example/readme/TACBuilderTest.java +++ b/src/main/test/java/example/readme/TACBuilderTest.java @@ -26,7 +26,7 @@ public class TACBuilderTest { template1.put(StandardSliderImageCaptchaGenerator.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, "/active.png")); template1.put(StandardSliderImageCaptchaGenerator.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, "/fixed.png")); - ImageCaptchaApplication application = TACBuilder.builder() + ImageCaptchaApplication application = TACBuilder.builder(new LocalMemoryResourceStore()) // 加载系统自带的默认资源 .addDefaultTemplate() // 设置验证码过期时间 @@ -43,11 +43,11 @@ public class TACBuilderTest { // 设置缓冲器,可提前生成验证码,用于增加并发性 .cached(10, 1000, 5000, 10000L) // 添加字体包,用于给文字点选验证码提供字体 - .addFont(font) + .addFont(new Resource("file", "C:\\Users\\Thinkpad\\Desktop\\captcha\\手写字体\\ttf\\千图小兔体.ttf")) // 设置缓存存储器,如果要支持分布式,需要把这里改成分布式缓存,比如通过redis实现的 CacheStore 缓存 .setCacheStore(new LocalCacheStore()) // 设置资源存储器,如果想在分布式环境或者想统一管理以及扩展 实现 ResourceStore 接口,自定义 - .setResourceStore(new LocalMemoryResourceStore()) +// .setResourceStore(new LocalMemoryResourceStore()) // 图片转换器,默认是将图片转换成base64格式, 背景图为jpg, 模板图为png, 如果想要扩展,可替换成自己实现的 .setTransform(new Base64ImageTransform()) .build(); diff --git a/src/main/test/java/example/readme/TACBuilderTest2.java b/src/main/test/java/example/readme/TACBuilderTest2.java index 5cf3e99..a8fb8e9 100644 --- a/src/main/test/java/example/readme/TACBuilderTest2.java +++ b/src/main/test/java/example/readme/TACBuilderTest2.java @@ -34,7 +34,7 @@ public class TACBuilderTest2 { return CaptchaInterceptor.super.beforeGenerateCaptcha(context, type, param); } }) - .addFont(font) + .addFont(new Resource("file", "C:\\Users\\Thinkpad\\Desktop\\captcha\\手写字体\\ttf\\千图小兔体.ttf")) .build(); CaptchaResponse response = application.generateCaptcha("WORD_IMAGE_CLICK"); System.out.println(response); diff --git a/src/main/test/java/example/readme/Test7.java b/src/main/test/java/example/readme/Test7.java index 2f9505c..467af26 100644 --- a/src/main/test/java/example/readme/Test7.java +++ b/src/main/test/java/example/readme/Test7.java @@ -20,7 +20,7 @@ public class Test7 { } @Override - public boolean supported(String type) { + public boolean supported(Resource type) { return false; }