feat(resource): 重构资源存储和加载机制

- 新增 AbstractResourceStore 类,实现 ResourceStore 接口的通用逻辑
- 创建 FontCache 类,用于缓存和管理字体资源
- 重构 DefaultImageCaptchaResourceManager 类,支持资源提供者和监听器
- 更新 Resource 和 ResourceMap 类,增加唯一 ID 字段
- 新增 ResourceListener 接口,用于扩展资源存储功能
- 创建 ResourceProviders 类,统一管理资源提供者
- 更新 TACBuilder 类,支持新的资源存储和加载机制
This commit is contained in:
天爱有情
2024-11-22 11:54:21 +08:00
parent 2be22591bf
commit 600878f6bd
20 changed files with 661 additions and 107 deletions
@@ -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() {
@@ -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());
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());
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();
}
@@ -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);
+1 -1
View File
@@ -20,7 +20,7 @@ public class Test7 {
}
@Override
public boolean supported(String type) {
public boolean supported(Resource type) {
return false;
}