diff --git a/pom.xml b/pom.xml
index 30503f001..b0a6988d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,6 +28,13 @@
redis.clients
jedis
3.6.0
+ true
+
+
+ io.lettuce
+ lettuce-core
+ 6.2.3.RELEASE
+ true
@@ -38,7 +45,7 @@
org.apache.shiro
shiro-core
- 1.10.0
+ 1.11.0
diff --git a/src/main/java/org/crazycake/shiro/LettuceRedisClusterManager.java b/src/main/java/org/crazycake/shiro/LettuceRedisClusterManager.java
new file mode 100644
index 000000000..d95d56696
--- /dev/null
+++ b/src/main/java/org/crazycake/shiro/LettuceRedisClusterManager.java
@@ -0,0 +1,332 @@
+package org.crazycake.shiro;
+
+import io.lettuce.core.*;
+import io.lettuce.core.cluster.ClusterClientOptions;
+import io.lettuce.core.cluster.RedisClusterClient;
+import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
+import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
+import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
+import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
+import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
+import io.lettuce.core.cluster.models.partitions.ClusterPartitionParser;
+import io.lettuce.core.cluster.models.partitions.Partitions;
+import io.lettuce.core.codec.ByteArrayCodec;
+import io.lettuce.core.support.ConnectionPoolSupport;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.crazycake.shiro.exception.PoolException;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+/**
+ * @author Teamo
+ * @since 2022/05/19
+ */
+public class LettuceRedisClusterManager implements IRedisManager {
+
+ /**
+ * Comma-separated list of "host:port" pairs to bootstrap from. This represents an
+ * "initial" list of cluster nodes and is required to have at least one entry.
+ */
+ private List nodes;
+
+ /**
+ * Default value of count.
+ */
+ private static final int DEFAULT_COUNT = 100;
+
+ /**
+ * timeout for RedisClient try to connect to redis server, not expire time! unit seconds.
+ */
+ private Duration timeout = RedisURI.DEFAULT_TIMEOUT_DURATION;
+
+ /**
+ * Redis database.
+ */
+ private int database = 0;
+
+ /**
+ * Redis password.
+ */
+ private String password;
+
+ /**
+ * Whether to enable async.
+ */
+ private boolean isAsync = true;
+
+ /**
+ * The number of elements returned at every iteration.
+ */
+ private int count = DEFAULT_COUNT;
+
+ /**
+ * genericObjectPoolConfig used to initialize GenericObjectPoolConfig object.
+ */
+ private GenericObjectPoolConfig> genericObjectPoolConfig = new GenericObjectPoolConfig<>();
+
+ /**
+ * GenericObjectPool.
+ */
+ private volatile GenericObjectPool> genericObjectPool;
+
+ /**
+ * ClusterClientOptions used to initialize RedisClient.
+ */
+ private ClusterClientOptions clusterClientOptions = ClusterClientOptions.create();
+
+ private void initialize() {
+ if (genericObjectPool == null) {
+ synchronized (LettuceRedisClusterManager.class) {
+ if (genericObjectPool == null) {
+ RedisClusterClient redisClusterClient = RedisClusterClient.create(getClusterRedisURI());
+ redisClusterClient.setOptions(clusterClientOptions);
+ StatefulRedisClusterConnection connect = redisClusterClient.connect(new ByteArrayCodec());
+ genericObjectPool = ConnectionPoolSupport.createGenericObjectPool(() -> connect, genericObjectPoolConfig);
+ }
+ }
+ }
+ }
+
+ private StatefulRedisClusterConnection getStatefulConnection() {
+ if (genericObjectPool == null) {
+ initialize();
+ }
+ try {
+ return genericObjectPool.borrowObject();
+ } catch (Exception e) {
+ throw new PoolException("Could not get a resource from the pool", e);
+ }
+ }
+
+ private List getClusterRedisURI() {
+ Objects.requireNonNull(nodes, "nodes must not be null!");
+ return nodes.stream().map(node -> {
+ String[] hostAndPort = node.split(":");
+ RedisURI.Builder builder = RedisURI.builder()
+ .withHost(hostAndPort[0])
+ .withPort(Integer.parseInt(hostAndPort[1]))
+ .withDatabase(database)
+ .withTimeout(timeout);
+ if (password != null) {
+ builder.withPassword(password.toCharArray());
+ }
+ return builder.build();
+ }).collect(Collectors.toList());
+ }
+
+ @Override
+ public byte[] get(byte[] key) {
+ if (key == null) {
+ return null;
+ }
+ byte[] value = null;
+ try (StatefulRedisClusterConnection connection = getStatefulConnection()) {
+ if (isAsync) {
+ RedisAdvancedClusterAsyncCommands async = connection.async();
+ value = LettuceFutures.awaitOrCancel(async.get(key), timeout.getSeconds(), TimeUnit.SECONDS);
+ } else {
+ RedisAdvancedClusterCommands sync = connection.sync();
+ value = sync.get(key);
+ }
+ }
+ return value;
+ }
+
+ @Override
+ public byte[] set(byte[] key, byte[] value, int expire) {
+ if (key == null) {
+ return null;
+ }
+ try (StatefulRedisClusterConnection connection = getStatefulConnection()) {
+ if (isAsync) {
+ RedisAdvancedClusterAsyncCommands async = connection.async();
+ if (expire > 0) {
+ async.set(key, value, SetArgs.Builder.ex(expire));
+ } else {
+ async.set(key, value);
+ }
+ } else {
+ RedisAdvancedClusterCommands sync = connection.sync();
+ if (expire > 0) {
+ sync.set(key, value, SetArgs.Builder.ex(expire));
+ } else {
+ sync.set(key, value);
+ }
+ }
+ }
+ return value;
+ }
+
+ @Override
+ public void del(byte[] key) {
+ try (StatefulRedisClusterConnection connection = getStatefulConnection()) {
+ if (isAsync) {
+ RedisAdvancedClusterAsyncCommands async = connection.async();
+ async.del(key);
+ } else {
+ RedisAdvancedClusterCommands sync = connection.sync();
+ sync.del(key);
+ }
+ }
+ }
+
+ @Override
+ public Long dbSize(byte[] pattern) {
+ AtomicLong dbSize = new AtomicLong(0L);
+
+ try (StatefulRedisClusterConnection connection = getStatefulConnection()) {
+ if (isAsync) {
+ RedisAdvancedClusterAsyncCommands async = connection.async();
+ Partitions parse = ClusterPartitionParser.parse(LettuceFutures.awaitOrCancel(async.clusterNodes(), timeout.getSeconds(), TimeUnit.SECONDS));
+
+ parse.forEach(redisClusterNode -> {
+ RedisClusterAsyncCommands clusterAsyncCommands = async.getConnection(redisClusterNode.getNodeId());
+
+ KeyScanCursor scanCursor = new KeyScanCursor<>();
+ scanCursor.setCursor(ScanCursor.INITIAL.getCursor());
+ ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count);
+ while (!scanCursor.isFinished()) {
+ scanCursor = LettuceFutures.awaitOrCancel(clusterAsyncCommands.scan(scanCursor, scanArgs), timeout.getSeconds(), TimeUnit.SECONDS);
+ dbSize.addAndGet(scanCursor.getKeys().size());
+ }
+ });
+ } else {
+ RedisAdvancedClusterCommands sync = connection.sync();
+ Partitions parse = ClusterPartitionParser.parse(sync.clusterNodes());
+
+ parse.forEach(redisClusterNode -> {
+ RedisClusterCommands clusterCommands = sync.getConnection(redisClusterNode.getNodeId());
+
+ KeyScanCursor scanCursor = new KeyScanCursor<>();
+ scanCursor.setCursor(ScanCursor.INITIAL.getCursor());
+ ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count);
+ while (!scanCursor.isFinished()) {
+ scanCursor = clusterCommands.scan(scanCursor, scanArgs);
+ dbSize.addAndGet(scanCursor.getKeys().size());
+ }
+ });
+ }
+ }
+ return dbSize.get();
+ }
+
+ @Override
+ public Set keys(byte[] pattern) {
+ Set keys = new HashSet<>();
+
+ try (StatefulRedisClusterConnection connection = getStatefulConnection()) {
+ if (isAsync) {
+ RedisAdvancedClusterAsyncCommands async = connection.async();
+ Partitions parse = ClusterPartitionParser.parse(LettuceFutures.awaitOrCancel(async.clusterNodes(), timeout.getSeconds(), TimeUnit.SECONDS));
+
+ parse.forEach(redisClusterNode -> {
+ RedisClusterAsyncCommands clusterAsyncCommands = async.getConnection(redisClusterNode.getNodeId());
+
+ KeyScanCursor scanCursor = new KeyScanCursor<>();
+ scanCursor.setCursor(ScanCursor.INITIAL.getCursor());
+ ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count);
+ while (!scanCursor.isFinished()) {
+ scanCursor = LettuceFutures.awaitOrCancel(clusterAsyncCommands.scan(scanCursor, scanArgs), timeout.getSeconds(), TimeUnit.SECONDS);
+ keys.addAll(scanCursor.getKeys());
+ }
+ });
+ } else {
+ RedisAdvancedClusterCommands sync = connection.sync();
+ Partitions parse = ClusterPartitionParser.parse(sync.clusterNodes());
+
+ parse.forEach(redisClusterNode -> {
+ RedisClusterCommands clusterCommands = sync.getConnection(redisClusterNode.getNodeId());
+
+ KeyScanCursor scanCursor = new KeyScanCursor<>();
+ scanCursor.setCursor(ScanCursor.INITIAL.getCursor());
+ ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count);
+ while (!scanCursor.isFinished()) {
+ scanCursor = clusterCommands.scan(scanCursor, scanArgs);
+ keys.addAll(scanCursor.getKeys());
+ }
+ });
+ }
+ }
+ return keys;
+ }
+
+ public List getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(List nodes) {
+ this.nodes = nodes;
+ }
+
+ public ClusterClientOptions getClusterClientOptions() {
+ return clusterClientOptions;
+ }
+
+ public void setClusterClientOptions(ClusterClientOptions clusterClientOptions) {
+ this.clusterClientOptions = clusterClientOptions;
+ }
+
+ public Duration getTimeout() {
+ return timeout;
+ }
+
+ public void setTimeout(Duration timeout) {
+ this.timeout = timeout;
+ }
+
+ public int getDatabase() {
+ return database;
+ }
+
+ public void setDatabase(int database) {
+ this.database = database;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public boolean isAsync() {
+ return isAsync;
+ }
+
+ public void setIsAsync(boolean isAsync) {
+ this.isAsync = isAsync;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public GenericObjectPoolConfig> getGenericObjectPoolConfig() {
+ return genericObjectPoolConfig;
+ }
+
+ public void setGenericObjectPoolConfig(GenericObjectPoolConfig> genericObjectPoolConfig) {
+ this.genericObjectPoolConfig = genericObjectPoolConfig;
+ }
+
+ public GenericObjectPool> getGenericObjectPool() {
+ return genericObjectPool;
+ }
+
+ public void setGenericObjectPool(GenericObjectPool> genericObjectPool) {
+ this.genericObjectPool = genericObjectPool;
+ }
+}
diff --git a/src/main/java/org/crazycake/shiro/LettuceRedisManager.java b/src/main/java/org/crazycake/shiro/LettuceRedisManager.java
new file mode 100644
index 000000000..5913ad259
--- /dev/null
+++ b/src/main/java/org/crazycake/shiro/LettuceRedisManager.java
@@ -0,0 +1,90 @@
+package org.crazycake.shiro;
+
+import io.lettuce.core.RedisClient;
+import io.lettuce.core.RedisURI;
+import io.lettuce.core.api.StatefulRedisConnection;
+import io.lettuce.core.codec.ByteArrayCodec;
+import io.lettuce.core.support.ConnectionPoolSupport;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.crazycake.shiro.common.AbstractLettuceRedisManager;
+import org.crazycake.shiro.exception.PoolException;
+
+/**
+ * Singleton lettuce redis
+ *
+ * @author Teamo
+ * @since 2022/05/18
+ */
+public class LettuceRedisManager extends AbstractLettuceRedisManager {
+
+ /**
+ * Redis server host.
+ */
+ private String host = "localhost";
+
+ /**
+ * Redis server port.
+ */
+ private int port = RedisURI.DEFAULT_REDIS_PORT;
+
+ /**
+ * GenericObjectPool.
+ */
+ private volatile GenericObjectPool> genericObjectPool;
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private void initialize() {
+ if (genericObjectPool == null) {
+ synchronized (LettuceRedisManager.class) {
+ if (genericObjectPool == null) {
+ RedisClient redisClient = RedisClient.create(createRedisURI());
+ redisClient.setOptions(getClientOptions());
+ GenericObjectPoolConfig genericObjectPoolConfig = getGenericObjectPoolConfig();
+ genericObjectPool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(new ByteArrayCodec()), genericObjectPoolConfig);
+ }
+ }
+ }
+ }
+
+ private RedisURI createRedisURI() {
+ RedisURI.Builder builder = RedisURI.builder()
+ .withHost(getHost())
+ .withPort(getPort())
+ .withDatabase(getDatabase())
+ .withTimeout(getTimeout());
+ String password = getPassword();
+ if (password != null) {
+ builder.withPassword(password.toCharArray());
+ }
+ return builder.build();
+ }
+
+ @Override
+ protected StatefulRedisConnection getStatefulConnection() {
+ if (genericObjectPool == null) {
+ initialize();
+ }
+ try {
+ return genericObjectPool.borrowObject();
+ } catch (Exception e) {
+ throw new PoolException("Could not get a resource from the pool", e);
+ }
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+}
diff --git a/src/main/java/org/crazycake/shiro/LettuceRedisSentinelManager.java b/src/main/java/org/crazycake/shiro/LettuceRedisSentinelManager.java
new file mode 100644
index 000000000..c3308ab08
--- /dev/null
+++ b/src/main/java/org/crazycake/shiro/LettuceRedisSentinelManager.java
@@ -0,0 +1,121 @@
+package org.crazycake.shiro;
+
+import io.lettuce.core.ReadFrom;
+import io.lettuce.core.RedisClient;
+import io.lettuce.core.RedisURI;
+import io.lettuce.core.codec.ByteArrayCodec;
+import io.lettuce.core.masterreplica.MasterReplica;
+import io.lettuce.core.masterreplica.StatefulRedisMasterReplicaConnection;
+import io.lettuce.core.support.ConnectionPoolSupport;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.crazycake.shiro.common.AbstractLettuceRedisManager;
+import org.crazycake.shiro.exception.PoolException;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @author Teamo
+ * @since 2022/05/19
+ */
+public class LettuceRedisSentinelManager extends AbstractLettuceRedisManager {
+ private static final String DEFAULT_MASTER_NAME = "mymaster";
+
+ private String masterName = DEFAULT_MASTER_NAME;
+
+ private List nodes;
+
+ private String sentinelPassword;
+
+ private ReadFrom readFrom = ReadFrom.REPLICA_PREFERRED;
+
+ /**
+ * GenericObjectPool.
+ */
+ private volatile GenericObjectPool> genericObjectPool;
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private void initialize() {
+ if (genericObjectPool == null) {
+ synchronized (LettuceRedisSentinelManager.class) {
+ if (genericObjectPool == null) {
+ RedisURI redisURI = this.createSentinelRedisURI();
+ RedisClient redisClient = RedisClient.create(redisURI);
+ redisClient.setOptions(getClientOptions());
+ StatefulRedisMasterReplicaConnection connect = MasterReplica.connect(redisClient, new ByteArrayCodec(), redisURI);
+ connect.setReadFrom(readFrom);
+ GenericObjectPoolConfig genericObjectPoolConfig = getGenericObjectPoolConfig();
+ genericObjectPool = ConnectionPoolSupport.createGenericObjectPool(() -> connect, genericObjectPoolConfig);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected StatefulRedisMasterReplicaConnection getStatefulConnection() {
+ if (genericObjectPool == null) {
+ initialize();
+ }
+ try {
+ return genericObjectPool.borrowObject();
+ } catch (Exception e) {
+ throw new PoolException("Could not get a resource from the pool", e);
+ }
+ }
+
+ private RedisURI createSentinelRedisURI() {
+ Objects.requireNonNull(nodes, "nodes must not be null!");
+
+ RedisURI.Builder builder = RedisURI.builder();
+ for (String node : nodes) {
+ String[] hostAndPort = node.split(":");
+
+ RedisURI.Builder sentinelBuilder = RedisURI.Builder.redis(hostAndPort[0], Integer.parseInt(hostAndPort[1]));
+
+ if (sentinelPassword != null) {
+ sentinelBuilder.withPassword(sentinelPassword.toCharArray());
+ }
+
+ builder.withSentinel(sentinelBuilder.build());
+ }
+
+ String password = getPassword();
+ if (password != null) {
+ builder.withPassword(password.toCharArray());
+ }
+ return builder.withSentinelMasterId(masterName).withDatabase(getDatabase()).build();
+ }
+
+ public String getMasterName() {
+ return masterName;
+ }
+
+ public void setMasterName(String masterName) {
+ this.masterName = masterName;
+ }
+
+ public List getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(List nodes) {
+ this.nodes = nodes;
+ }
+
+ public String getSentinelPassword() {
+ return sentinelPassword;
+ }
+
+ public void setSentinelPassword(String sentinelPassword) {
+ this.sentinelPassword = sentinelPassword;
+ }
+
+ public ReadFrom getReadFrom() {
+ return readFrom;
+ }
+
+ public void setReadFrom(ReadFrom readFrom) {
+ this.readFrom = readFrom;
+ }
+}
diff --git a/src/main/java/org/crazycake/shiro/RedisClusterManager.java b/src/main/java/org/crazycake/shiro/RedisClusterManager.java
index a27326975..8b7ef6098 100644
--- a/src/main/java/org/crazycake/shiro/RedisClusterManager.java
+++ b/src/main/java/org/crazycake/shiro/RedisClusterManager.java
@@ -93,7 +93,7 @@ public void del(byte[] key) {
@Override
public Long dbSize(byte[] pattern) {
- Long dbSize = 0L;
+ long dbSize = 0L;
Map clusterNodes = getJedisCluster().getClusterNodes();
Iterator> nodeIt = clusterNodes.entrySet().iterator();
while (nodeIt.hasNext()) {
@@ -115,7 +115,7 @@ public Set keys(byte[] pattern) {
while (nodeIt.hasNext()) {
Map.Entry node = nodeIt.next();
Set nodeKeys = getKeysFromClusterNode(node.getValue(), pattern);
- if (nodeKeys == null || nodeKeys.size() == 0) {
+ if (nodeKeys.size() == 0) {
continue;
}
keys.addAll(nodeKeys);
diff --git a/src/main/java/org/crazycake/shiro/RedisManager.java b/src/main/java/org/crazycake/shiro/RedisManager.java
index 180cb9a05..eef3fb435 100644
--- a/src/main/java/org/crazycake/shiro/RedisManager.java
+++ b/src/main/java/org/crazycake/shiro/RedisManager.java
@@ -17,7 +17,7 @@ public class RedisManager extends WorkAloneRedisManager implements IRedisManager
private int database = Protocol.DEFAULT_DATABASE;
- private JedisPool jedisPool;
+ private volatile JedisPool jedisPool;
private void init() {
if (jedisPool == null) {
diff --git a/src/main/java/org/crazycake/shiro/common/AbstractLettuceRedisManager.java b/src/main/java/org/crazycake/shiro/common/AbstractLettuceRedisManager.java
new file mode 100644
index 000000000..44ac427bb
--- /dev/null
+++ b/src/main/java/org/crazycake/shiro/common/AbstractLettuceRedisManager.java
@@ -0,0 +1,238 @@
+package org.crazycake.shiro.common;
+
+import io.lettuce.core.*;
+import io.lettuce.core.api.StatefulRedisConnection;
+import io.lettuce.core.api.async.RedisAsyncCommands;
+import io.lettuce.core.api.sync.RedisCommands;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.crazycake.shiro.IRedisManager;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Teamo
+ * @since 2022/05/19
+ */
+public abstract class AbstractLettuceRedisManager implements IRedisManager {
+
+ /**
+ * Default value of count.
+ */
+ private static final int DEFAULT_COUNT = 100;
+
+ /**
+ * timeout for RedisClient try to connect to redis server, not expire time! unit seconds.
+ */
+ private Duration timeout = RedisURI.DEFAULT_TIMEOUT_DURATION;
+
+ /**
+ * Redis database.
+ */
+ private int database = 0;
+
+ /**
+ * Redis password.
+ */
+ private String password;
+
+ /**
+ * Whether to enable async.
+ */
+ private boolean isAsync = true;
+
+ /**
+ * The number of elements returned at every iteration.
+ */
+ private int count = DEFAULT_COUNT;
+
+ /**
+ * ClientOptions used to initialize RedisClient.
+ */
+ private ClientOptions clientOptions = ClientOptions.create();
+
+ /**
+ * genericObjectPoolConfig used to initialize GenericObjectPoolConfig object.
+ */
+ @SuppressWarnings("rawtypes")
+ private GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig<>();
+
+ /**
+ * Get a stateful connection.
+ *
+ * @return T
+ */
+ @SuppressWarnings("rawtypes")
+ protected abstract StatefulRedisConnection getStatefulConnection();
+
+ public Duration getTimeout() {
+ return timeout;
+ }
+
+ public void setTimeout(Duration timeout) {
+ this.timeout = timeout;
+ }
+
+ public int getDatabase() {
+ return database;
+ }
+
+ public void setDatabase(int database) {
+ this.database = database;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public boolean isAsync() {
+ return isAsync;
+ }
+
+ public void setIsAsync(boolean isAsync) {
+ this.isAsync = isAsync;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public ClientOptions getClientOptions() {
+ return clientOptions;
+ }
+
+ public void setClientOptions(ClientOptions clientOptions) {
+ this.clientOptions = clientOptions;
+ }
+
+ public GenericObjectPoolConfig> getGenericObjectPoolConfig() {
+ return genericObjectPoolConfig;
+ }
+
+ public void setGenericObjectPoolConfig(GenericObjectPoolConfig> genericObjectPoolConfig) {
+ this.genericObjectPoolConfig = genericObjectPoolConfig;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public byte[] get(byte[] key) {
+ if (key == null) {
+ return null;
+ }
+ byte[] value = null;
+ try (StatefulRedisConnection connect = getStatefulConnection()) {
+ if (isAsync) {
+ RedisAsyncCommands async = connect.async();
+ RedisFuture redisFuture = async.get(key);
+ value = LettuceFutures.awaitOrCancel(redisFuture, timeout.getSeconds(), TimeUnit.SECONDS);
+ } else {
+ RedisCommands sync = connect.sync();
+ value = sync.get(key);
+ }
+ }
+ return value;
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked"})
+ public byte[] set(byte[] key, byte[] value, int expire) {
+ if (key == null) {
+ return null;
+ }
+ try (StatefulRedisConnection connect = getStatefulConnection()) {
+ if (isAsync) {
+ RedisAsyncCommands async = connect.async();
+ if (expire > 0) {
+ async.set(key, value, SetArgs.Builder.ex(expire));
+ } else {
+ async.set(key, value);
+ }
+ } else {
+ RedisCommands sync = connect.sync();
+ if (expire > 0) {
+ sync.set(key, value, SetArgs.Builder.ex(expire));
+ } else {
+ sync.set(key, value);
+ }
+ }
+ }
+ return value;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void del(byte[] key) {
+ try (StatefulRedisConnection connect = getStatefulConnection()) {
+ if (isAsync) {
+ RedisAsyncCommands async = connect.async();
+ async.del(key);
+ } else {
+ RedisCommands sync = connect.sync();
+ sync.del(key);
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Long dbSize(byte[] pattern) {
+ long dbSize = 0L;
+ KeyScanCursor scanCursor = new KeyScanCursor<>();
+ scanCursor.setCursor(ScanCursor.INITIAL.getCursor());
+ ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count);
+ try (StatefulRedisConnection connect = getStatefulConnection()) {
+ while (!scanCursor.isFinished()) {
+ scanCursor = getKeyScanCursor(connect, scanCursor, scanArgs);
+ dbSize += scanCursor.getKeys().size();
+ }
+ }
+ return dbSize;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Set keys(byte[] pattern) {
+ Set keys = new HashSet<>();
+ KeyScanCursor scanCursor = new KeyScanCursor<>();
+ scanCursor.setCursor(ScanCursor.INITIAL.getCursor());
+ ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count);
+ try (StatefulRedisConnection connect = getStatefulConnection()) {
+ while (!scanCursor.isFinished()) {
+ scanCursor = getKeyScanCursor(connect, scanCursor, scanArgs);
+ keys.addAll(scanCursor.getKeys());
+ }
+ }
+ return keys;
+ }
+
+ /**
+ * get scan cursor result
+ *
+ * @param connect connection
+ * @param scanCursor scan cursor
+ * @param scanArgs scan param
+ * @return KeyScanCursor
+ */
+ private KeyScanCursor getKeyScanCursor(final StatefulRedisConnection connect,
+ KeyScanCursor scanCursor,
+ ScanArgs scanArgs) {
+ if (isAsync) {
+ RedisAsyncCommands async = connect.async();
+ scanCursor = LettuceFutures.awaitOrCancel(async.scan(scanCursor, scanArgs), timeout.getSeconds(), TimeUnit.SECONDS);
+ } else {
+ RedisCommands sync = connect.sync();
+ scanCursor = sync.scan(scanCursor, scanArgs);
+ }
+ return scanCursor;
+ }
+}
diff --git a/src/main/java/org/crazycake/shiro/common/WorkAloneRedisManager.java b/src/main/java/org/crazycake/shiro/common/WorkAloneRedisManager.java
index 7352f6432..5c3ee3a98 100644
--- a/src/main/java/org/crazycake/shiro/common/WorkAloneRedisManager.java
+++ b/src/main/java/org/crazycake/shiro/common/WorkAloneRedisManager.java
@@ -117,9 +117,7 @@ public Long dbSize(byte[] pattern) {
do {
scanResult = jedis.scan(cursor, params);
List results = scanResult.getResult();
- for (byte[] result : results) {
- dbSize++;
- }
+ dbSize += results.size();
cursor = scanResult.getCursorAsBytes();
} while (scanResult.getCursor().compareTo(ScanParams.SCAN_POINTER_START) > 0);
} finally {
@@ -133,6 +131,7 @@ public Long dbSize(byte[] pattern) {
* @param pattern key pattern
* @return key set
*/
+ @Override
public Set keys(byte[] pattern) {
Set keys = new HashSet();
Jedis jedis = getJedis();
diff --git a/src/main/java/org/crazycake/shiro/exception/PoolException.java b/src/main/java/org/crazycake/shiro/exception/PoolException.java
new file mode 100644
index 000000000..9e5de632b
--- /dev/null
+++ b/src/main/java/org/crazycake/shiro/exception/PoolException.java
@@ -0,0 +1,26 @@
+package org.crazycake.shiro.exception;
+
+/**
+ * @author Teamo
+ * @since 2022/05/18
+ */
+public class PoolException extends RuntimeException {
+ /**
+ * Constructs a new LettucePoolException
instance.
+ *
+ * @param msg the detail message.
+ */
+ public PoolException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs a new LettucePoolException
instance.
+ *
+ * @param msg the detail message.
+ * @param cause the nested exception.
+ */
+ public PoolException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}