diff --git a/src/main/java/net/lamgc/cgj/bot/BotCommandProcess.java b/src/main/java/net/lamgc/cgj/bot/BotCommandProcess.java index 7d5f315..685fa57 100644 --- a/src/main/java/net/lamgc/cgj/bot/BotCommandProcess.java +++ b/src/main/java/net/lamgc/cgj/bot/BotCommandProcess.java @@ -50,7 +50,7 @@ public class BotCommandProcess { private final static CacheStore illustInfoCache = new JsonRedisCacheStore(BotEventHandler.redisServer, "illustInfo", gson); private final static CacheStore illustPreLoadDataCache = new HotDataCacheStore<>( new JsonRedisCacheStore(BotEventHandler.redisServer, "illustPreLoadData", gson), - new LocalHashCacheStore<>(), 3600000, 900000); + new LocalHashCacheStore<>(), 3600000, 900000, true); private final static CacheStore searchBodyCache = new JsonRedisCacheStore(BotEventHandler.redisServer, "searchBody", gson); private final static CacheStore> rankingCache = new JsonObjectRedisListCacheStore(BotEventHandler.redisServer, "ranking", gson); private final static CacheStore> pagesCache = new StringListRedisCacheStore(BotEventHandler.redisServer, "imagePages"); @@ -408,7 +408,7 @@ public class BotCommandProcess { StringBuilder builder = new StringBuilder("["); illustObj.get("tags").getAsJsonArray().forEach(el -> builder.append(el.getAsString()).append(", ")); builder.replace(builder.length() - 2, builder.length(), "]"); - log.debug("{} ({} / {})\n\t作品id: {}, \n\t作者名(作者id): {} ({}), \n\t作品标题: {}, \n\t作品Tags: {}, \n\t页数: {}, \n\t作品链接: {}", + log.debug("{} ({} / {})\n\t作品id: {}, \n\t作者名(作者id): {} ({}), \n\t作品标题: {}, \n\t作品Tags: {}, \n\t页数: {}页, \n\t作品链接: {}", searchArea.name(), count, illustsList.size(), @@ -435,7 +435,7 @@ public class BotCommandProcess { result.append(searchArea.name()).append(" (").append(count).append(" / ").append(limit).append(")\n\t作品id: ").append(illustId) .append(", \n\t作者名: ").append(illustObj.get("userName").getAsString()) .append("\n\t作品标题: ").append(illustObj.get("illustTitle").getAsString()) - .append("\n\t作品页数: ").append(illustObj.get("pageCount").getAsInt()) + .append("\n\t作品页数: ").append(illustObj.get("pageCount").getAsInt()).append("页") .append("\n").append(imageMsg).append("\n"); count++; } diff --git a/src/main/java/net/lamgc/cgj/bot/cache/AutoCleanTimer.java b/src/main/java/net/lamgc/cgj/bot/cache/AutoCleanTimer.java new file mode 100644 index 0000000..fc6ee6a --- /dev/null +++ b/src/main/java/net/lamgc/cgj/bot/cache/AutoCleanTimer.java @@ -0,0 +1,52 @@ +package net.lamgc.cgj.bot.cache; + +import com.google.common.base.Throwables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CopyOnWriteArraySet; + +public class AutoCleanTimer extends TimerTask { + + private final static Set cleanSet = new CopyOnWriteArraySet<>(); + + private final static Timer cleanTimer = new Timer("Thread-AutoClean", true); + + private final static Logger log = LoggerFactory.getLogger(AutoCleanTimer.class.getName()); + + static { + cleanTimer.schedule(new AutoCleanTimer(), 100L); + } + + /** + * 增加需要定时执行清理的缓存库 + * @param store 已实现Cleanable的对象 + */ + public static void add(Cleanable store) { + cleanSet.add(store); + } + + /** + * 移除已添加的缓存库 + * @param store 需要从AutoCleanTimer移除的对象 + */ + public static void remove(Cleanable store) { + cleanSet.remove(store); + } + + private AutoCleanTimer() {} + + @Override + public void run() { + cleanSet.forEach(cleanable -> { + try { + cleanable.clean(); + } catch (Exception e) { + log.error("{} 执行清理动作时发生异常:\n{}", cleanable.toString(), Throwables.getStackTraceAsString(e)); + } + }); + } +} diff --git a/src/main/java/net/lamgc/cgj/bot/cache/Cleanable.java b/src/main/java/net/lamgc/cgj/bot/cache/Cleanable.java new file mode 100644 index 0000000..9daadb2 --- /dev/null +++ b/src/main/java/net/lamgc/cgj/bot/cache/Cleanable.java @@ -0,0 +1,10 @@ +package net.lamgc.cgj.bot.cache; + +/** + * 可清理接口, 实现该接口代表该类拥有清理动作. + */ +public interface Cleanable { + + void clean() throws Exception; + +} diff --git a/src/main/java/net/lamgc/cgj/bot/cache/HotDataCacheStore.java b/src/main/java/net/lamgc/cgj/bot/cache/HotDataCacheStore.java index a0cdf2e..8a5fc73 100644 --- a/src/main/java/net/lamgc/cgj/bot/cache/HotDataCacheStore.java +++ b/src/main/java/net/lamgc/cgj/bot/cache/HotDataCacheStore.java @@ -10,7 +10,7 @@ import java.util.*; * @param 存储类型 * @author LamGC */ -public class HotDataCacheStore implements CacheStore { +public class HotDataCacheStore implements CacheStore, Cleanable { private final CacheStore parent; private final CacheStore current; @@ -24,17 +24,22 @@ public class HotDataCacheStore implements CacheStore { * @param parent 上级缓存存储库 * @param current 热点缓存存储库, 最好使用本地缓存(例如 {@linkplain LocalHashCacheStore LocalHashCacheStore}) * @param expireTime 本地缓存库的缓存项过期时间, 单位毫秒; - * 该时间并不是所有缓存项的最终过期时间, 还需要根据expireFloatRange的设定随机设置, 公式: - * {@code expireTime + new Random().nextInt(expireFloatRange)} + * 该时间并不是所有缓存项的最终过期时间, 还需要根据expireFloatRange的设定随机设置, 公式: + * {@code expireTime + new Random().nextInt(expireFloatRange)} * @param expireFloatRange 过期时间的浮动范围(单位毫秒), 用于防止短时间内大量缓存项失效导致的缓存雪崩 + * @param autoClean 是否交由{@link AutoCleanTimer}自动执行清理 */ - public HotDataCacheStore(CacheStore parent, CacheStore current, long expireTime, int expireFloatRange) { + public HotDataCacheStore(CacheStore parent, CacheStore current, long expireTime, int expireFloatRange, boolean autoClean) { this.parent = parent; this.current = current; this.expireTime = expireTime; this.expireFloatRange = expireFloatRange; - log.debug("HotDataCacheStore初始化完成. (Parent: {}, Current: {}, expireTime: {}, expireFloatRange: {})", - parent, current, expireTime, expireFloatRange); + if(autoClean) { + AutoCleanTimer.add(this); + } + + log.debug("HotDataCacheStore初始化完成. (Parent: {}, Current: {}, expireTime: {}, expireFloatRange: {}, autoClean: {})", + parent, current, expireTime, expireFloatRange, autoClean); } @Override @@ -121,4 +126,13 @@ public class HotDataCacheStore implements CacheStore { public boolean supportedList() { return false; } + + @Override + public void clean() { + for(String key : this.current.keys()) { + if(current.exists(key)) { + current.remove(key); + } + } + } } diff --git a/src/main/java/net/lamgc/cgj/bot/cache/RedisPoolCacheStore.java b/src/main/java/net/lamgc/cgj/bot/cache/RedisPoolCacheStore.java index dcf19c5..dea8d63 100644 --- a/src/main/java/net/lamgc/cgj/bot/cache/RedisPoolCacheStore.java +++ b/src/main/java/net/lamgc/cgj/bot/cache/RedisPoolCacheStore.java @@ -13,7 +13,7 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; -public abstract class RedisPoolCacheStore implements CacheStore { +abstract class RedisPoolCacheStore implements CacheStore { private final JedisPool jedisPool; private final String keyPrefix; diff --git a/src/main/java/net/lamgc/cgj/bot/framework/mirai/message/MiraiMessageSender.java b/src/main/java/net/lamgc/cgj/bot/framework/mirai/message/MiraiMessageSender.java index 2a187a2..149f306 100644 --- a/src/main/java/net/lamgc/cgj/bot/framework/mirai/message/MiraiMessageSender.java +++ b/src/main/java/net/lamgc/cgj/bot/framework/mirai/message/MiraiMessageSender.java @@ -33,7 +33,7 @@ public class MiraiMessageSender implements MessageSender { private final static CacheStore imageIdCache = new HotDataCacheStore<>( new StringRedisCacheStore(BotEventHandler.redisServer, "mirai.imageId"), new LocalHashCacheStore<>(), - 5400000, 1800000); + 5400000, 1800000, true); /** * 使用id构造发送器