mirror of
https://github.com/dromara/tianai-captcha.git
synced 2026-05-06 21:53:10 +08:00
feat(resource): 重构资源存储和加载机制
- 新增 AbstractResourceStore 类,实现 ResourceStore 接口的通用逻辑 - 创建 FontCache 类,用于缓存和管理字体资源 - 重构 DefaultImageCaptchaResourceManager 类,支持资源提供者和监听器 - 更新 Resource 和 ResourceMap 类,增加唯一 ID 字段 - 新增 ResourceListener 接口,用于扩展资源存储功能 - 创建 ResourceProviders 类,统一管理资源提供者 - 更新 TACBuilder 类,支持新的资源存储和加载机制
This commit is contained in:
@@ -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<FontWrapper> fontWrappers = new ArrayList<>();
|
||||
// private List<FontWrapper> 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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package cloud.tianai.captcha.common.util;
|
||||
|
||||
public class UUIDUtils {
|
||||
|
||||
public static String getUUID() {
|
||||
return java.util.UUID.randomUUID().toString().replace("-", "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <T> void addParam(ParamKey<T> paramKey, T value) {
|
||||
addParam(paramKey.getKey(), value);
|
||||
}
|
||||
|
||||
public <T> T getParam(ParamKey<T> paramKey) {
|
||||
return (T) getParam(paramKey.getKey());
|
||||
}
|
||||
|
||||
public <T> T getOrDefault(ParamKey<T> paramKey, T defaultValue) {
|
||||
return (T) getOrDefault(paramKey.getKey(), defaultValue);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
|
||||
String getKey();
|
||||
|
||||
}
|
||||
@@ -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<ResourceListener> listeners = new ArrayList<>();
|
||||
|
||||
public ChainListener() {
|
||||
|
||||
}
|
||||
|
||||
public void addListener(ResourceListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(ResourceListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInit(ResourceStore resourceStore, ImageCaptchaResourceManager resourceManager) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onInit(resourceStore, resourceManager);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddResource(String type, Resource resource) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onAddResource(type, resource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddTemplate(String type, ResourceMap template) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onAddTemplate(type, template);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearAllResources() {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onClearAllResources();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearAllTemplates() {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onClearAllTemplates();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRandomGetResourceByTypeAndTag(String type, String tag, Resource resource) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onRandomGetResourceByTypeAndTag(type, tag, resource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRandomGetTemplateByTypeAndTag(String type, String tag, ResourceMap template) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onRandomGetTemplateByTypeAndTag(type, tag, template);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteResource(String type, Resource resource) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onDeleteResource(type, resource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteTemplate(String type, ResourceMap template) {
|
||||
for (ResourceListener listener : listeners) {
|
||||
listener.onDeleteTemplate(type, template);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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<String, FontWrapper> 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<Resource> resources = resourceStore.listResourcesByTypeAndTag(FONT_TYPE, null);
|
||||
for (Resource resource : resources) {
|
||||
fontMap.computeIfAbsent(resource.getId(), v -> getFont(resource));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class INSTANCE {
|
||||
private static final FontCache INSTANCE = new FontCache();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -22,10 +22,10 @@ public interface ResourceProvider {
|
||||
/**
|
||||
* 是否支持
|
||||
*
|
||||
* @param type type
|
||||
* @param resource resource
|
||||
* @return boolean
|
||||
*/
|
||||
boolean supported(String type);
|
||||
boolean supported(Resource resource);
|
||||
|
||||
/**
|
||||
* 放弃资源提供者名称
|
||||
|
||||
@@ -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<ResourceProvider> 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<ResourceProvider> 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() + "]对应的资源提供者");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Resource>
|
||||
*/
|
||||
List<Resource> listResourcesByTypeAndTag(String type, String tag);
|
||||
|
||||
/**
|
||||
* 获取某个模板列表
|
||||
*
|
||||
* @param type 验证码类型
|
||||
* @param tag 资源标签(可为空)
|
||||
* @return List<ResourceMap>
|
||||
*/
|
||||
List<ResourceMap> listTemplatesByTypeAndTag(String type, String tag);
|
||||
|
||||
/**
|
||||
* 随机获取某个资源
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Resource> 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<String, Resource> getResourceMapOfCreate() {
|
||||
|
||||
+13
-30
@@ -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<ResourceProvider> 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<ResourceProvider> 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
|
||||
|
||||
@@ -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<String, List<Resource>> 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<String, List<Resource>> entry : resourceTagMap.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
List<Resource> v = entry.getValue();
|
||||
String splitType = splitTypeTag(k)[0];
|
||||
if (splitType.equals(type)) {
|
||||
Iterator<Resource> iterator = v.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Resource next = iterator.next();
|
||||
if (next.getId().equals(id)) {
|
||||
iterator.remove();
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceMap doDeleteTemplate(String type, String id) {
|
||||
for (Map.Entry<String, List<ResourceMap>> entry : templateResourceTagMap.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
List<ResourceMap> v = entry.getValue();
|
||||
String splitType = splitTypeTag(k)[0];
|
||||
if (splitType.equals(type)) {
|
||||
Iterator<ResourceMap> iterator = v.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ResourceMap next = iterator.next();
|
||||
if (next.getId().equals(id)) {
|
||||
iterator.remove();
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> listResourcesByTypeAndTag(String type, String tag) {
|
||||
if (!ObjectUtils.isEmpty(tag)) {
|
||||
return resourceTagMap.get(mergeTypeAndTag(type, tag));
|
||||
}
|
||||
List<Resource> resourceList = new ArrayList<>();
|
||||
resourceTagMap.forEach((k, v) -> {
|
||||
String splitType = splitTypeTag(k)[0];
|
||||
if (splitType.equals(type)) {
|
||||
resourceList.addAll(v);
|
||||
}
|
||||
});
|
||||
return resourceList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceMap> listTemplatesByTypeAndTag(String type, String tag) {
|
||||
if (!ObjectUtils.isEmpty(tag)) {
|
||||
return templateResourceTagMap.get(mergeTypeAndTag(type, tag));
|
||||
}
|
||||
List<ResourceMap> 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<Resource> 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<ResourceMap> 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();
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<ImageCaptchaVO> response = application.generateCaptcha("WORD_IMAGE_CLICK");
|
||||
System.out.println(response);
|
||||
|
||||
@@ -20,7 +20,7 @@ public class Test7 {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supported(String type) {
|
||||
public boolean supported(Resource type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user