mirror of
https://github.com/LamGC/ContentGrabbingJi.git
synced 2025-04-30 06:37:36 +00:00
[Add][Update] CacheStore-redis 添加 Redis 缓存存储容器的实现模块;
[Add] META-INF/services/* 支持 SPI 机制; [Add] RedisCacheStore, RedisCacheStoreFactory, RedisConnectionPool, RedisUtils 添加相关父类和辅助类; [Add] RedisMapCacheStore, RedisMapCacheStoreTest 添加映射表缓存相关实现及完整单元测试; [Add] RedisSingleCacheStore, RedisSingleCacheStoreTest 添加单项缓存相关实现及完整单元测试; [Update] redis.clients:jedis 更新依赖项版本(3.2.0 -> 3.3.0);
This commit is contained in:
parent
862ddc871e
commit
c856d94384
@ -38,7 +38,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>redis.clients</groupId>
|
<groupId>redis.clients</groupId>
|
||||||
<artifactId>jedis</artifactId>
|
<artifactId>jedis</artifactId>
|
||||||
<version>3.2.0</version>
|
<version>3.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
102
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/RedisCacheStore.java
vendored
Normal file
102
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/RedisCacheStore.java
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache.redis;
|
||||||
|
|
||||||
|
import net.lamgc.cgj.bot.cache.CacheKey;
|
||||||
|
import net.lamgc.cgj.bot.cache.CacheStore;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public abstract class RedisCacheStore<V> implements CacheStore<V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Key 前缀.
|
||||||
|
* <p>key = getKeyPrefix() + key
|
||||||
|
* @param cacheKey CacheKey 对象.
|
||||||
|
* @return 返回 Key 前缀.
|
||||||
|
*/
|
||||||
|
protected String getKeyString(CacheKey cacheKey) {
|
||||||
|
return getKeyPrefix() + cacheKey.join(RedisUtils.KEY_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Key 的完整前缀.
|
||||||
|
* @return 返回完整前缀.
|
||||||
|
*/
|
||||||
|
protected abstract String getKeyPrefix();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setTimeToLive(CacheKey key, long ttl) {
|
||||||
|
String keyString = getKeyString(key);
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> {
|
||||||
|
Long result;
|
||||||
|
if (ttl >= 0) {
|
||||||
|
result = jedis.pexpire(keyString, ttl);
|
||||||
|
} else {
|
||||||
|
result = jedis.persist(keyString);
|
||||||
|
}
|
||||||
|
return result == RedisUtils.RETURN_CODE_OK;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeToLive(CacheKey key) {
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> {
|
||||||
|
Long ttl = jedis.pttl(getKeyString(key));
|
||||||
|
return ttl < 0 ? -1 : ttl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long size() {
|
||||||
|
return RedisConnectionPool.executeRedis(Jedis::dbSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean clear() {
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> RedisUtils.isOk(jedis.flushDB()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(CacheKey key) {
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> jedis.exists(getKeyString(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(CacheKey key) {
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> jedis.del(getKeyString(key)) == RedisUtils.RETURN_CODE_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
Set<String> keys = RedisConnectionPool.executeRedis(jedis -> jedis.keys(RedisUtils.KEY_PATTERN_ALL));
|
||||||
|
final int prefixLength = getKeyPrefix().length();
|
||||||
|
Set<String> newKeys = new HashSet<>();
|
||||||
|
for (String key : keys) {
|
||||||
|
newKeys.add(key.substring(prefixLength));
|
||||||
|
}
|
||||||
|
return newKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache.redis;
|
||||||
|
|
||||||
|
import net.lamgc.cgj.bot.cache.*;
|
||||||
|
import net.lamgc.cgj.bot.cache.convert.StringConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
@Factory(name = "Redis")
|
||||||
|
public class RedisCacheStoreFactory implements CacheStoreFactory {
|
||||||
|
@Override
|
||||||
|
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
|
||||||
|
return new RedisSingleCacheStore<>(identify, converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
|
||||||
|
return new RedisMapCacheStore<>(identify, converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canGetCacheStore() {
|
||||||
|
return RedisConnectionPool.available();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache.redis;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
import redis.clients.jedis.JedisPoolConfig;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一的 Redis 连接池.
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public class RedisConnectionPool {
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(RedisConnectionPool.class);
|
||||||
|
private final static AtomicReference<JedisPool> POOL = new AtomicReference<>();
|
||||||
|
|
||||||
|
public static synchronized void reconnectRedis() {
|
||||||
|
JedisPool jedisPool = POOL.get();
|
||||||
|
if (jedisPool != null && !jedisPool.isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JedisPoolConfig config = new JedisPoolConfig();
|
||||||
|
config.setTestOnBorrow(true);
|
||||||
|
config.setTestOnReturn(true);
|
||||||
|
jedisPool = new JedisPool(config);
|
||||||
|
POOL.set(jedisPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个 Jedis 对象.
|
||||||
|
* <p>注意, 需回收 Jedis 对象, 否则可能会耗尽连接池导致后续操作受到影响.
|
||||||
|
* @return 返回可用的 Jedis 连接.
|
||||||
|
*/
|
||||||
|
public static Jedis getConnection() {
|
||||||
|
JedisPool pool = POOL.get();
|
||||||
|
if (pool == null || pool.isClosed()) {
|
||||||
|
reconnectRedis();
|
||||||
|
pool = POOL.get();
|
||||||
|
if (pool == null) {
|
||||||
|
throw new IllegalStateException("Redis connection lost");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pool.getResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行 Redis 操作并返回相关值.
|
||||||
|
* <p>本方法会自动回收 Jedis.
|
||||||
|
* @param function 待运行的操作.
|
||||||
|
* @param <R> 返回值类型.
|
||||||
|
* @return 返回 function 返回的内容.
|
||||||
|
*/
|
||||||
|
public static <R> R executeRedis(Function<Jedis, R> function) {
|
||||||
|
try (Jedis jedis = getConnection()) {
|
||||||
|
return function.apply(jedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 Redis 连接池是否有可用的资源.
|
||||||
|
* @return 如果连接池依然活跃, 返回 true.
|
||||||
|
*/
|
||||||
|
public static boolean available() {
|
||||||
|
JedisPool jedisPool = POOL.get();
|
||||||
|
if (jedisPool == null || jedisPool.isClosed()) {
|
||||||
|
reconnectRedis();
|
||||||
|
if (jedisPool == null || jedisPool.isClosed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jedisPool.getNumIdle() == 0) {
|
||||||
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
return "pong".equalsIgnoreCase(jedis.ping());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Redis 连接测试时发生异常", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache.redis;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import net.lamgc.cgj.bot.cache.CacheKey;
|
||||||
|
import net.lamgc.cgj.bot.cache.MapCacheStore;
|
||||||
|
import net.lamgc.cgj.bot.cache.convert.StringConverter;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis Map缓存存储容器.
|
||||||
|
* @param <V> 值类型.
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public class RedisMapCacheStore<V> extends RedisCacheStore<Map<String, V>> implements MapCacheStore<V> {
|
||||||
|
|
||||||
|
private final String keyPrefix;
|
||||||
|
private final StringConverter<V> converter;
|
||||||
|
|
||||||
|
public RedisMapCacheStore(String keyPrefix, StringConverter<V> converter) {
|
||||||
|
keyPrefix = Strings.nullToEmpty(keyPrefix).trim();
|
||||||
|
if (!keyPrefix.isEmpty() && keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
|
||||||
|
this.keyPrefix = keyPrefix;
|
||||||
|
} else {
|
||||||
|
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.converter = Objects.requireNonNull(converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getKeyPrefix() {
|
||||||
|
return this.keyPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int mapSize(CacheKey key) {
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> {
|
||||||
|
String keyString = getKeyString(key);
|
||||||
|
if (jedis.exists(keyString)) {
|
||||||
|
return jedis.hlen(keyString).intValue();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> mapFieldSet(CacheKey key) {
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> {
|
||||||
|
String keyString = getKeyString(key);
|
||||||
|
if (jedis.exists(keyString)) {
|
||||||
|
return jedis.hkeys(keyString);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<V> mapValueSet(CacheKey key) {
|
||||||
|
List<String> rawValueSet = RedisConnectionPool.executeRedis(jedis -> {
|
||||||
|
String keyString = getKeyString(key);
|
||||||
|
if (jedis.exists(keyString)) {
|
||||||
|
return jedis.hvals(keyString);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rawValueSet == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<V> result = new HashSet<>();
|
||||||
|
for (String rawValue : rawValueSet) {
|
||||||
|
result.add(converter.from(rawValue));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(CacheKey key, String field, V value) {
|
||||||
|
Objects.requireNonNull(field);
|
||||||
|
Objects.requireNonNull(value);
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> {
|
||||||
|
String keyString = getKeyString(key);
|
||||||
|
return jedis.hset(keyString, field, converter.to(value)) == RedisUtils.RETURN_CODE_OK;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean putAll(CacheKey key, Map<String, V> map) {
|
||||||
|
Objects.requireNonNull(key);
|
||||||
|
Objects.requireNonNull(map);
|
||||||
|
if (map.size() == 0 && exists(key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, String> targetMap = new HashMap<>(map.size());
|
||||||
|
map.forEach((k, v) -> targetMap.put(k, converter.to(v)));
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> {
|
||||||
|
String keyString = getKeyString(key);
|
||||||
|
return RedisUtils.isOk(jedis.hmset(keyString, targetMap));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean putIfNotExist(CacheKey key, String field, V value) {
|
||||||
|
Objects.requireNonNull(field);
|
||||||
|
Objects.requireNonNull(value);
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> {
|
||||||
|
String keyString = getKeyString(key);
|
||||||
|
return jedis.hsetnx(keyString, field, converter.to(value)) == RedisUtils.RETURN_CODE_OK;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(CacheKey key, String field) {
|
||||||
|
Objects.requireNonNull(field);
|
||||||
|
String value = RedisConnectionPool.executeRedis(jedis -> jedis.hget(getKeyString(key), field));
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return converter.from(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeField(CacheKey key, String field) {
|
||||||
|
Objects.requireNonNull(field);
|
||||||
|
return RedisConnectionPool.executeRedis(jedis ->
|
||||||
|
jedis.hdel(getKeyString(key), field) == RedisUtils.RETURN_CODE_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsField(CacheKey key, String field) {
|
||||||
|
Objects.requireNonNull(field);
|
||||||
|
return RedisConnectionPool.executeRedis(jedis -> jedis.hexists(getKeyString(key), field));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mapIsEmpty(CacheKey key) {
|
||||||
|
return mapSize(key) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean clearMap(CacheKey key) {
|
||||||
|
Set<String> fields = mapFieldSet(key);
|
||||||
|
if (fields == null) {
|
||||||
|
return false;
|
||||||
|
} else if (fields.size() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] fieldsArray = new String[fields.size()];
|
||||||
|
fields.toArray(fieldsArray);
|
||||||
|
return RedisConnectionPool.executeRedis(jedis ->
|
||||||
|
jedis.hdel(getKeyString(key), fieldsArray) != RedisUtils.RETURN_CODE_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache.redis;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import net.lamgc.cgj.bot.cache.CacheKey;
|
||||||
|
import net.lamgc.cgj.bot.cache.SingleCacheStore;
|
||||||
|
import net.lamgc.cgj.bot.cache.convert.StringConverter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis 单项缓存存储容器.
|
||||||
|
* @param <V> 值类型.
|
||||||
|
* @see net.lamgc.cgj.bot.cache.CacheStore
|
||||||
|
* @see net.lamgc.cgj.bot.cache.SingleCacheStore
|
||||||
|
* @see net.lamgc.cgj.bot.cache.redis.RedisCacheStore
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public class RedisSingleCacheStore<V> extends RedisCacheStore<V> implements SingleCacheStore<V> {
|
||||||
|
|
||||||
|
private final String keyPrefix;
|
||||||
|
private final StringConverter<V> converter;
|
||||||
|
|
||||||
|
public RedisSingleCacheStore(String keyPrefix, StringConverter<V> converter) {
|
||||||
|
keyPrefix = Strings.nullToEmpty(keyPrefix).trim();
|
||||||
|
if (!keyPrefix.isEmpty() && keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
|
||||||
|
this.keyPrefix = keyPrefix;
|
||||||
|
} else {
|
||||||
|
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.converter = Objects.requireNonNull(converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean set(CacheKey key, V value) {
|
||||||
|
return RedisConnectionPool.executeRedis(jedis ->
|
||||||
|
RedisUtils.isOk(jedis.set(getKeyString(key), converter.to(Objects.requireNonNull(value)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setIfNotExist(CacheKey key, V value) {
|
||||||
|
return RedisConnectionPool.executeRedis(jedis ->
|
||||||
|
jedis.setnx(getKeyString(key), converter.to(Objects.requireNonNull(value)))
|
||||||
|
== RedisUtils.RETURN_CODE_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(CacheKey key) {
|
||||||
|
String value = RedisConnectionPool.executeRedis(jedis -> jedis.get(getKeyString(key)));
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return converter.from(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getKeyPrefix() {
|
||||||
|
return this.keyPrefix;
|
||||||
|
}
|
||||||
|
}
|
54
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/RedisUtils.java
vendored
Normal file
54
ContentGrabbingJi-CacheStore-redis/src/main/java/net/lamgc/cgj/bot/cache/redis/RedisUtils.java
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache.redis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public class RedisUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回码 - 成功
|
||||||
|
*/
|
||||||
|
public final static int RETURN_CODE_OK = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回码 - 失败
|
||||||
|
*/
|
||||||
|
public final static int RETURN_CODE_FAILED = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key匹配规则 - 所有Key
|
||||||
|
*/
|
||||||
|
public final static String KEY_PATTERN_ALL = "*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 分隔符
|
||||||
|
*/
|
||||||
|
public final static String KEY_SEPARATOR = ":";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串返回结果是否为操作成功.
|
||||||
|
* @param result 字符串返回结果.
|
||||||
|
* @return 如果为操作成功, 返回 true.
|
||||||
|
*/
|
||||||
|
public static boolean isOk(String result) {
|
||||||
|
return "OK".equalsIgnoreCase(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2020 LamGC
|
||||||
|
#
|
||||||
|
# ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
net.lamgc.cgj.bot.cache.redis.RedisCacheStoreFactory
|
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache.redis;
|
||||||
|
|
||||||
|
|
||||||
|
import net.lamgc.cgj.bot.cache.CacheKey;
|
||||||
|
import net.lamgc.cgj.bot.cache.MapCacheStore;
|
||||||
|
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see RedisMapCacheStore
|
||||||
|
*/
|
||||||
|
public class RedisMapCacheStoreTest {
|
||||||
|
|
||||||
|
private final static MapCacheStore<String> cacheStore =
|
||||||
|
new RedisMapCacheStore<>("test", new StringToStringConverter());
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
Assert.assertTrue(cacheStore.clear());
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
Assert.assertTrue(cacheStore.clear());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nullThrowTest() {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapSize(null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapFieldSet(null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapValueSet(null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(null, "field", "value"));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, null, "value"));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, "field", null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(null, new HashMap<>()));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(key, null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(null, "field", "value"));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, null, "value"));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, "field", null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(key, null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null, "field"));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(key, null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(null, "field"));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapIsEmpty(null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearMap(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void keyNotExistTest() {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
HashMap<String, String> testMap = new HashMap<>();
|
||||||
|
testMap.put("testField", "value");
|
||||||
|
|
||||||
|
// 例外情况: 因为 Redis 对空 Map 的处理机制, 导致返回只能为空.
|
||||||
|
Assert.assertEquals(0, cacheStore.mapSize(key));
|
||||||
|
// 例外情况: mapIsEmpty 在 Redis 没有相应指令, 所以是依靠 mapSize 实现的,
|
||||||
|
// 同样因 mapSize 的原因, 不存在等于空.
|
||||||
|
Assert.assertTrue(cacheStore.mapIsEmpty(key));
|
||||||
|
|
||||||
|
Assert.assertFalse(cacheStore.clearMap(key));
|
||||||
|
Assert.assertFalse(cacheStore.containsField(key, "Field"));
|
||||||
|
Assert.assertFalse(cacheStore.removeField(key, "Field"));
|
||||||
|
Assert.assertNull(cacheStore.get(key, "Field"));
|
||||||
|
Assert.assertTrue(cacheStore.put(key, "Field", "value"));
|
||||||
|
Assert.assertTrue("clearMap operation failed!", cacheStore.remove(key));
|
||||||
|
Assert.assertTrue(cacheStore.putAll(key, testMap));
|
||||||
|
Assert.assertTrue("clearMap operation failed!", cacheStore.remove(key));
|
||||||
|
Assert.assertTrue(cacheStore.putIfNotExist(key, "Field", "value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putAndGetTest() {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
final Map<String, String> expectedMap = new HashMap<>();
|
||||||
|
expectedMap.put("test01", "testValue01");
|
||||||
|
expectedMap.put("test02", "testValue02");
|
||||||
|
expectedMap.put("test03", "testValue03");
|
||||||
|
expectedMap.put("test04", "testValue04");
|
||||||
|
expectedMap.put("test05", "testValue05");
|
||||||
|
expectedMap.put("test06", "testValue06");
|
||||||
|
|
||||||
|
// put/get, mapIsEmpty, containsField
|
||||||
|
Assert.assertTrue("put operation failed!", cacheStore.put(key, "test00", "testValue00"));
|
||||||
|
Assert.assertTrue(cacheStore.containsField(key, "test00"));
|
||||||
|
Assert.assertEquals("testValue00", cacheStore.get(key, "test00"));
|
||||||
|
Assert.assertTrue("removeField operation failed!", cacheStore.removeField(key, "test00"));
|
||||||
|
|
||||||
|
// putIfNotExist
|
||||||
|
Assert.assertTrue(cacheStore.putIfNotExist(key, "test00", "testValue00"));
|
||||||
|
Assert.assertFalse(cacheStore.putIfNotExist(key, "test00", "testValue00"));
|
||||||
|
Assert.assertTrue("clearMap operation failed!", cacheStore.clearMap(key));
|
||||||
|
|
||||||
|
// putAll
|
||||||
|
Assert.assertTrue(cacheStore.putAll(key, expectedMap));
|
||||||
|
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.mapFieldSet(key)));
|
||||||
|
Assert.assertTrue(expectedMap.values().containsAll(cacheStore.mapValueSet(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fieldChangeTest() {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
final Map<String, String> expectedMap = new HashMap<>();
|
||||||
|
expectedMap.put("test01", "testValue01");
|
||||||
|
expectedMap.put("test02", "testValue02");
|
||||||
|
expectedMap.put("test03", "testValue03");
|
||||||
|
expectedMap.put("test04", "testValue04");
|
||||||
|
expectedMap.put("test05", "testValue05");
|
||||||
|
expectedMap.put("test06", "testValue06");
|
||||||
|
|
||||||
|
// mapSize, clearMap, mapIsEmpty 测试
|
||||||
|
Assert.assertTrue("putAll operation failed!", cacheStore.putAll(key, expectedMap));
|
||||||
|
Assert.assertEquals(expectedMap.size(), cacheStore.mapSize(key));
|
||||||
|
Assert.assertTrue(cacheStore.clearMap(key));
|
||||||
|
Assert.assertEquals(0, cacheStore.mapSize(key));
|
||||||
|
Assert.assertTrue(cacheStore.mapIsEmpty(key));
|
||||||
|
|
||||||
|
// removeField 多分支测试
|
||||||
|
Assert.assertTrue("put operation failed!", cacheStore.put(key, "test00", "testValue00"));
|
||||||
|
Assert.assertTrue(cacheStore.containsField(key, "test00"));
|
||||||
|
Assert.assertEquals("testValue00", cacheStore.get(key, "test00"));
|
||||||
|
Assert.assertTrue("removeField operation failed!", cacheStore.removeField(key, "test00"));
|
||||||
|
Assert.assertFalse(cacheStore.removeField(key, "test00"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 LamGC
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.lamgc.cgj.bot.cache.redis;
|
||||||
|
|
||||||
|
import net.lamgc.cgj.bot.cache.CacheKey;
|
||||||
|
import net.lamgc.cgj.bot.cache.SingleCacheStore;
|
||||||
|
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see RedisCacheStore
|
||||||
|
* @see RedisSingleCacheStore
|
||||||
|
*/
|
||||||
|
public class RedisSingleCacheStoreTest {
|
||||||
|
|
||||||
|
private final static SingleCacheStore<String> cacheStore = new RedisSingleCacheStore<>("test", new StringToStringConverter());
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
Assert.assertTrue(cacheStore.clear());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nullThrowTest() {
|
||||||
|
final SingleCacheStore<String> tempCacheStore =
|
||||||
|
new RedisSingleCacheStore<>("test" + RedisUtils.KEY_SEPARATOR, new StringToStringConverter());
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
|
||||||
|
// RedisSingleCacheStore
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.set(null, "testValue"));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.set(key, null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.get(null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.setIfNotExist(null, "testValue"));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.setIfNotExist(key, null));
|
||||||
|
|
||||||
|
// RedisCacheStore
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.exists(null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.getTimeToLive(null));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.setTimeToLive(null, 0));
|
||||||
|
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.remove(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setAndGetTest() {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
final String value = "testValue";
|
||||||
|
|
||||||
|
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||||
|
Assert.assertEquals(value, cacheStore.get(key));
|
||||||
|
Assert.assertTrue("Remove operation failed!", cacheStore.remove(key));
|
||||||
|
Assert.assertNull("Set operation failed!", cacheStore.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setIfNotExistTest() {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
final String value = "testValue";
|
||||||
|
final String value2 = "testValue02";
|
||||||
|
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||||
|
|
||||||
|
Assert.assertFalse(cacheStore.setIfNotExist(key, value2));
|
||||||
|
Assert.assertEquals(value, cacheStore.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void expireTest() throws InterruptedException {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
final String value = "testValue";
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
Assert.assertFalse(cacheStore.setTimeToLive(key, 300));
|
||||||
|
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
|
||||||
|
|
||||||
|
// TTL 到期被动检查测试: 使用 exists 经 expire 检查失败后返回 false.
|
||||||
|
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||||
|
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
|
||||||
|
Assert.assertNotEquals(-1, cacheStore.getTimeToLive(key));
|
||||||
|
Thread.sleep(300);
|
||||||
|
Assert.assertFalse(cacheStore.exists(key));
|
||||||
|
|
||||||
|
// 取消 TTL 测试
|
||||||
|
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||||
|
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
|
||||||
|
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, -1));
|
||||||
|
Thread.sleep(300);
|
||||||
|
Assert.assertTrue(cacheStore.exists(key));
|
||||||
|
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeTest() {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
final String value = "testValue";
|
||||||
|
|
||||||
|
// 删除不存在Cache测试
|
||||||
|
Assert.assertFalse(cacheStore.remove(key));
|
||||||
|
// 删除存在的Cache测试
|
||||||
|
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||||
|
Assert.assertTrue(cacheStore.remove(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearTest() {
|
||||||
|
final CacheKey key = new CacheKey("testKey");
|
||||||
|
final String value = "testValue";
|
||||||
|
|
||||||
|
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||||
|
|
||||||
|
Assert.assertTrue(cacheStore.exists(key));
|
||||||
|
Assert.assertTrue("Clear operation failed!", cacheStore.clear());
|
||||||
|
Assert.assertFalse(cacheStore.exists(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sizeAndKeySetTest() {
|
||||||
|
Map<String, String> expectedMap = new HashMap<>();
|
||||||
|
expectedMap.put("test01", "testValue01");
|
||||||
|
expectedMap.put("test02", "testValue02");
|
||||||
|
expectedMap.put("test03", "testValue03");
|
||||||
|
expectedMap.put("test04", "testValue04");
|
||||||
|
expectedMap.put("test05", "testValue05");
|
||||||
|
expectedMap.put("test06", "testValue06");
|
||||||
|
|
||||||
|
expectedMap.forEach((key, value) -> cacheStore.set(new CacheKey(key), value));
|
||||||
|
Assert.assertEquals(expectedMap.size(), cacheStore.size());
|
||||||
|
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user