[Add] CacheStore 增加supportedPersistence以让使用方知晓实现是否支持持久化;

[Change] CQProcess 使用RedisPoolCacheStore;
This commit is contained in:
LamGC 2020-03-30 10:10:53 +08:00
parent 056d9740b9
commit 18d96c73d6
7 changed files with 271 additions and 81 deletions

View File

@ -145,6 +145,11 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -21,8 +21,6 @@ import org.slf4j.LoggerFactory;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
@ -41,17 +39,19 @@ public class CQProcess {
.serializeNulls() .serializeNulls()
.create(); .create();
private final static URI redisServerUri = URI.create("redis://192.168.1.17");
private final static Hashtable<String, File> imageCache = new Hashtable<>(); private final static Hashtable<String, File> imageCache = new Hashtable<>();
private final static CacheStore<JsonObject> illustInfoCache = new LocalHashCacheStore<>(); private final static JsonRedisCacheDatabase illustInfoCache = new JsonRedisCacheDatabase(redisServerUri, "illustInfo", gson);
private final static CacheStore<JsonObject> illustPreLoadDataCache = new LocalHashCacheStore<>(); private final static JsonRedisCacheDatabase illustPreLoadDataCache = new JsonRedisCacheDatabase(redisServerUri, "illustPreLoadData", gson);
private final static CacheStore<JsonObject> searchBodyCache = new LocalHashCacheStore<>(); private final static JsonRedisCacheDatabase searchBodyCache = new JsonRedisCacheDatabase(redisServerUri, "searchBody", gson);
private final static CacheStore<List<String>> pagesCache = new LocalHashCacheStore<>(); private final static CacheStore<List<String>> pagesCache = new LocalHashCacheStore<>();
private final static CacheStore<JsonArray> rankingCache = new LocalHashCacheStore<>(); private final static JsonRedisCacheDatabase rankingCache = new JsonRedisCacheDatabase(redisServerUri, "ranking", gson);
private final static EventExecutor imageCacheExecutor = new EventExecutor(new ThreadPoolExecutor( private final static EventExecutor imageCacheExecutor = new EventExecutor(new ThreadPoolExecutor(
1, 1,
@ -226,7 +226,7 @@ public class CQProcess {
log.info("搜索缓存命中."); log.info("搜索缓存命中.");
} }
JsonObject resultBody = searchBodyCache.getCache(requestUrl).getAsJsonObject("body"); JsonObject resultBody = searchBodyCache.getCache(requestUrl).getAsJsonObject().getAsJsonObject("body");
StringBuilder result = new StringBuilder("内容 " + content + " 的搜索结果:\n"); StringBuilder result = new StringBuilder("内容 " + content + " 的搜索结果:\n");
log.info("正在处理信息..."); log.info("正在处理信息...");
@ -448,26 +448,12 @@ public class CQProcess {
if (!illustInfoCache.exists(illustIdStr)) { if (!illustInfoCache.exists(illustIdStr)) {
synchronized (illustInfoLock) { synchronized (illustInfoLock) {
if (!illustInfoCache.exists(illustIdStr)) { if (!illustInfoCache.exists(illustIdStr)) {
File cacheFile = new File(getImageStoreDir(), illustId + ".illustInfo.json"); JsonObject illustInfoObj = pixivDownload.getIllustInfoByIllustId(illustId);
log.info("IllustInfoFileName: {}", cacheFile.getName());
JsonObject illustInfoObj;
if (!cacheFile.exists()) {
try {
cacheFile.createNewFile();
illustInfoObj = pixivDownload.getIllustInfoByIllustId(illustId);
Files.write(cacheFile.toPath(), gson.toJson(illustInfoObj).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
} catch (IOException e) {
cacheFile.delete();
throw e;
}
} else {
illustInfoObj = gson.fromJson(new FileReader(cacheFile), JsonObject.class);
}
illustInfoCache.update(illustIdStr, illustInfoObj, null); illustInfoCache.update(illustIdStr, illustInfoObj, null);
} }
} }
} }
return illustInfoCache.getCache(illustIdStr); return illustInfoCache.getCache(illustIdStr).getAsJsonObject();
} }
private final static Object illustPreLoadDataLock = new Object(); private final static Object illustPreLoadDataLock = new Object();
@ -476,24 +462,10 @@ public class CQProcess {
if (!illustPreLoadDataCache.exists(illustIdStr)) { if (!illustPreLoadDataCache.exists(illustIdStr)) {
synchronized (illustPreLoadDataLock) { synchronized (illustPreLoadDataLock) {
if (!illustPreLoadDataCache.exists(illustIdStr)) { if (!illustPreLoadDataCache.exists(illustIdStr)) {
File cacheFile = new File(getImageStoreDir(), illustId + ".illustPreLoadData.json");
log.info("缓存失效, 正在更新..."); log.info("缓存失效, 正在更新...");
log.info("illustPreLoadDataFileName: {}", cacheFile.getName()); JsonObject preLoadDataObj = pixivDownload.getIllustPreLoadDataById(illustId)
JsonObject preLoadDataObj;
if (!cacheFile.exists()) {
try {
cacheFile.createNewFile();
preLoadDataObj = pixivDownload.getIllustPreLoadDataById(illustId)
.getAsJsonObject("illust") .getAsJsonObject("illust")
.getAsJsonObject(Integer.toString(illustId)); .getAsJsonObject(Integer.toString(illustId));
Files.write(cacheFile.toPath(), gson.toJson(preLoadDataObj).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
} catch(IOException e) {
cacheFile.delete();
throw e;
}
} else {
preLoadDataObj = gson.fromJson(new FileReader(cacheFile), JsonObject.class);
}
long expire = 7200 * 1000; long expire = 7200 * 1000;
String propValue = CQPluginMain.globalProp.getProperty("cache.illustPreLoadData.expire", "7200000"); String propValue = CQPluginMain.globalProp.getProperty("cache.illustPreLoadData.expire", "7200000");
@ -511,7 +483,7 @@ public class CQProcess {
} }
} }
} }
return illustPreLoadDataCache.getCache(illustIdStr); return illustPreLoadDataCache.getCache(illustIdStr).getAsJsonObject();
} }
private final static Object illustPagesLock = new Object(); private final static Object illustPagesLock = new Object();
@ -520,25 +492,7 @@ public class CQProcess {
if (!pagesCache.exists(pagesSign)) { if (!pagesCache.exists(pagesSign)) {
synchronized (illustPagesLock) { synchronized (illustPagesLock) {
if (!pagesCache.exists(pagesSign)) { if (!pagesCache.exists(pagesSign)) {
File cacheFile = new File(getImageStoreDir(), illustId + "." + quality.name() + ".illustPages.json"); List<String> linkList = PixivDownload.getIllustAllPageDownload(pixivDownload.getHttpClient(), pixivDownload.getCookieStore(), illustId, quality);
log.info("illustPagesFileName: {}", cacheFile.getName());
List<String> linkList;
if (!cacheFile.exists()) {
try {
cacheFile.createNewFile();
linkList = PixivDownload.getIllustAllPageDownload(pixivDownload.getHttpClient(), pixivDownload.getCookieStore(), illustId, quality);
JsonArray jsonArray = new JsonArray(linkList.size());
linkList.forEach(jsonArray::add);
Files.write(cacheFile.toPath(), gson.toJson(jsonArray).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
} catch (IOException e) {
cacheFile.delete();
throw e;
}
} else {
JsonArray jsonArray = gson.fromJson(new FileReader(cacheFile), JsonArray.class);
linkList = new ArrayList<>(jsonArray.size());
jsonArray.forEach(jsonElement -> linkList.add(jsonElement.getAsString()));
}
pagesCache.update(pagesSign, linkList, null); pagesCache.update(pagesSign, linkList, null);
} }
} }
@ -564,31 +518,20 @@ public class CQProcess {
synchronized(rankingLock) { synchronized(rankingLock) {
if(!rankingCache.exists(requestSign)) { if(!rankingCache.exists(requestSign)) {
log.info("Ranking缓存失效, 正在更新...(RequestSign: {})", requestSign); log.info("Ranking缓存失效, 正在更新...(RequestSign: {})", requestSign);
File cacheFile = new File(getImageStoreDir(), date + "." + contentType.name() + "." + mode.modeParam + ".ranking.json");
JsonArray rankingArr;
if(!cacheFile.exists()) {
List<JsonObject> rankingResult = pixivDownload.getRanking(contentType, mode, queryDate, 1, 500); List<JsonObject> rankingResult = pixivDownload.getRanking(contentType, mode, queryDate, 1, 500);
rankingArr = new JsonArray(rankingResult.size()); JsonArray rankingArr = new JsonArray(rankingResult.size());
rankingResult.forEach(rankingArr::add); rankingResult.forEach(rankingArr::add);
JsonObject cacheBody = new JsonObject(); JsonObject cacheBody = new JsonObject();
cacheBody.addProperty("updateTimestamp", new Date().getTime()); cacheBody.addProperty("updateTimestamp", new Date().getTime());
cacheBody.addProperty("ContentType", contentType.name()); cacheBody.addProperty("ContentType", contentType.name());
cacheBody.addProperty("RankingMode", mode.modeParam); cacheBody.addProperty("RankingMode", mode.modeParam);
cacheBody.add("ranking", rankingArr); cacheBody.add("ranking", rankingArr);
Files.write(cacheFile.toPath(), gson.toJson(cacheBody).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
log.info("已从Pixiv获取数据并缓存到文件.");
} else {
JsonObject cacheBody = gson.fromJson(new FileReader(cacheFile), JsonObject.class);
rankingArr = cacheBody.getAsJsonArray("ranking");
log.info("已从文件获取缓存数据.");
}
rankingCache.update(requestSign, rankingArr, null); rankingCache.update(requestSign, rankingArr, null);
} }
} }
} }
return PixivDownload.getRanking(rankingCache.getCache(requestSign), start, range); return PixivDownload.getRanking(rankingCache.getCache(requestSign).getAsJsonArray(), start, range);
} }
} }

View File

@ -40,4 +40,10 @@ public interface CacheStore<T> {
*/ */
boolean clear(); boolean clear();
/**
* 是否支持持久化
* @return 如果支持返回true
*/
boolean supportedPersistence();
} }

View File

@ -0,0 +1,26 @@
package net.lamgc.cgj.cache;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import java.net.URI;
public class JsonRedisCacheDatabase extends RedisPoolCacheStore<JsonElement> {
private final Gson gson;
public JsonRedisCacheDatabase(URI redisServerUri, String prefix, Gson gson) {
super(redisServerUri, prefix);
this.gson = gson;
}
@Override
protected String parse(JsonElement data) {
return this.gson.toJson(data);
}
@Override
protected JsonElement analysis(String dataStr) {
return this.gson.fromJson(dataStr, JsonElement.class);
}
}

View File

@ -6,7 +6,27 @@ import java.util.Objects;
public class LocalHashCacheStore<T> implements CacheStore<T> { public class LocalHashCacheStore<T> implements CacheStore<T> {
private final Hashtable<String, CacheObject<T>> cache = new Hashtable<>(); private final Hashtable<String, CacheObject<T>> cache;
public LocalHashCacheStore() {
this(0);
}
public LocalHashCacheStore(int initialCapacity) {
this(initialCapacity, 0F);
}
public LocalHashCacheStore(int initialCapacity, float loadFactor) {
if(initialCapacity != 0) {
if(loadFactor <= 0F) {
cache = new Hashtable<>(initialCapacity);
} else {
cache = new Hashtable<>(initialCapacity, loadFactor);
}
} else {
cache = new Hashtable<>();
}
}
@Override @Override
public void update(String key, T value, Date expire) { public void update(String key, T value, Date expire) {
@ -54,4 +74,9 @@ public class LocalHashCacheStore<T> implements CacheStore<T> {
cache.clear(); cache.clear();
return true; return true;
} }
@Override
public boolean supportedPersistence() {
return false;
}
} }

View File

@ -0,0 +1,95 @@
package net.lamgc.cgj.cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.net.URI;
import java.util.Date;
public abstract class RedisCacheDatabase<T> implements CacheStore<T> {
private final Jedis jedis;
private final Logger log;
private final String keyPrefix;
public RedisCacheDatabase(URI redisServerUri, String prefix) {
this(redisServerUri, null, prefix);
}
/**
* 创建一个Redis缓存数据库对象
* @param redisServerUri 数据库链接
* @param password 登录密码(如果有)
* @throws JedisConnectionException 当连接失败时抛出
*/
public RedisCacheDatabase(URI redisServerUri, String password, String prefix) throws JedisConnectionException {
this.jedis = new Jedis(redisServerUri.getHost(), redisServerUri.getPort() <= 0 ? 6379 : redisServerUri.getPort());
log = LoggerFactory.getLogger("RedisCacheDatabase@" + Integer.toHexString(jedis.hashCode()));
log.info("Redis数据库连接状态: {}", jedis.ping());
if(password != null) {
this.jedis.auth(password);
}
keyPrefix = prefix.endsWith(".") ? prefix : prefix + ".";
}
public void connect() {
if(!"PONE".equals(jedis.ping())) {
jedis.connect();
}
}
@Override
public void update(String key, T value, Date expire) {
Transaction multi = jedis.multi();
multi.set(keyPrefix + key, parse(value));
if(expire != null) {
multi.expireAt(key, expire.getTime());
log.debug("已设置Key {} 的过期时间(Expire: {})", key, expire.getTime());
}
multi.exec();
}
@Override
public T getCache(String key) {
return analysis(jedis.get(keyPrefix + key));
}
@Override
public boolean exists(String key) {
return jedis.exists(keyPrefix + key);
}
@Override
public boolean exists(String key, Date date) {
return exists(key);
}
@Override
public boolean clear() {
String result = jedis.flushDB();
log.info("flushDB返回结果: {}", result);
return true;
}
/**
* 转换方法
* @param dataObj 原数据
* @return 文本型数据
*/
protected abstract String parse(T dataObj);
/**
* 将String数据转换成指定类型的对象
* @param dataStr String数据
* @return 泛型指定类型的对象
*/
protected abstract T analysis(String dataStr);
@Override
public boolean supportedPersistence() {
return true;
}
}

View File

@ -0,0 +1,90 @@
package net.lamgc.cgj.cache;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.*;
import java.net.URI;
import java.util.Date;
public abstract class RedisPoolCacheStore<T> implements CacheStore<T> {
private final JedisPool jedisPool;
private final String keyPrefix;
private final Logger log;
public RedisPoolCacheStore(URI redisServerUri, String prefix) {
this(redisServerUri, null, 0, null, prefix);
}
public RedisPoolCacheStore(URI redisServerUri, JedisPoolConfig config, int timeout, String password, String prefix) {
jedisPool = new JedisPool(config == null ? new GenericObjectPoolConfig<JedisPool>() : config, redisServerUri.getHost(),
redisServerUri.getPort() <= 0 ? 6379 : redisServerUri.getPort(),
timeout <= 0 ? Protocol.DEFAULT_TIMEOUT : timeout, password);
log = LoggerFactory.getLogger("RedisPoolCacheStore@" + Integer.toHexString(jedisPool.hashCode()));
this.keyPrefix = prefix;
}
@Override
public void update(String key, T value, Date expire) {
Jedis jedis = jedisPool.getResource();
Transaction multi = jedis.multi();
multi.set(keyPrefix + key, parse(value));
if(expire != null) {
multi.expireAt(key, expire.getTime());
log.debug("已设置Key {} 的过期时间(Expire: {})", key, expire.getTime());
}
multi.exec();
jedis.close();
}
@Override
public T getCache(String key) {
Jedis jedis = jedisPool.getResource();
T result = analysis(jedis.get(keyPrefix + key));
jedis.close();
return result;
}
@Override
public boolean exists(String key) {
Jedis jedis = jedisPool.getResource();
boolean result = jedis.exists(keyPrefix + key);
jedis.close();
return result;
}
@Override
public boolean exists(String key, Date date) {
return exists(key);
}
@Override
public boolean clear() {
Jedis jedis = jedisPool.getResource();
String result = jedis.flushDB();
jedis.close();
log.info("flushDB返回结果: {}", result);
return true;
}
/**
* 转换方法
* @param dataObj 原数据
* @return 文本型数据
*/
protected abstract String parse(T dataObj);
/**
* 将String数据转换成指定类型的对象
* @param dataStr String数据
* @return 泛型指定类型的对象
*/
protected abstract T analysis(String dataStr);
@Override
public boolean supportedPersistence() {
return true;
}
}