diff --git a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/store/CustomRedisTokenStore.java b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/store/CustomRedisTokenStore.java index ad35a6fff4f34966398a2389d3a5ef45dea935aa..f07ddb0d1e3d26a259dd7048dbe7071887e8dcd1 100644 --- a/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/store/CustomRedisTokenStore.java +++ b/zlt-commons/zlt-auth-client-spring-boot-starter/src/main/java/com/central/oauth2/common/store/CustomRedisTokenStore.java @@ -39,6 +39,7 @@ import java.util.List; */ public class CustomRedisTokenStore implements TokenStore { private static final String ACCESS = "access:"; + private static final String ACCESS_BAK = "access_bak:"; private static final String AUTH_TO_ACCESS = "auth_to_access:"; private static final String REFRESH_AUTH = "refresh_auth:"; private static final String ACCESS_TO_REFRESH = "access_to_refresh:"; @@ -118,7 +119,7 @@ public class CustomRedisTokenStore implements TokenStore { } private ClientDetails deserializeClientDetails(byte[] bytes) { - return (ClientDetails)redisValueSerializer.deserialize(bytes); + return (ClientDetails) redisValueSerializer.deserialize(bytes); } private byte[] serialize(String string) { @@ -166,7 +167,7 @@ public class CustomRedisTokenStore implements TokenStore { //获取过期时长 int validitySeconds = getAccessTokenValiditySeconds(clientAuth.getClientId()); if (validitySeconds > 0) { - double expiresRatio = token.getExpiresIn() / (double)validitySeconds; + double expiresRatio = token.getExpiresIn() / (double) validitySeconds; //判断是否需要续签,当前剩余时间小于过期时长的50%则续签 if (expiresRatio <= securityProperties.getAuth().getRenew().getTimeRatio()) { //更新AccessToken过期时间 @@ -182,6 +183,7 @@ public class CustomRedisTokenStore implements TokenStore { /** * 判断应用自动续签是否满足白名单和黑名单的过滤逻辑 + * * @param clientId 应用id * @return 是否满足 */ @@ -193,7 +195,7 @@ public class CustomRedisTokenStore implements TokenStore { List exclusiveClientIds = securityProperties.getAuth().getRenew().getExclusiveClientIds(); if (includeClientIds.size() > 0) { result = includeClientIds.contains(clientId); - } else if(exclusiveClientIds.size() > 0) { + } else if (exclusiveClientIds.size() > 0) { result = !exclusiveClientIds.contains(clientId); } return result; @@ -201,6 +203,7 @@ public class CustomRedisTokenStore implements TokenStore { /** * 获取token的总有效时长 + * * @param clientId 应用id */ private int getAccessTokenValiditySeconds(String clientId) { @@ -256,12 +259,14 @@ public class CustomRedisTokenStore implements TokenStore { /** * 存储token + * * @param isRenew 是否续签 */ private void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication, boolean isRenew) { byte[] serializedAccessToken = serialize(token); byte[] serializedAuth = serialize(authentication); byte[] accessKey = serializeKey(ACCESS + token.getValue()); + byte[] accessBakKey = serializeKey(ACCESS_BAK + token.getValue()); byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + token.getValue()); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); byte[] approvalKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication)); @@ -279,6 +284,7 @@ public class CustomRedisTokenStore implements TokenStore { if (springDataRedis_2_0) { try { this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken); + this.redisConnectionSet_2_0.invoke(conn, accessBakKey, serializedAccessToken); this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth); this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken); } catch (Exception ex) { @@ -286,6 +292,7 @@ public class CustomRedisTokenStore implements TokenStore { } } else { conn.set(accessKey, serializedAccessToken); + conn.set(accessBakKey, serializedAccessToken); conn.set(authKey, serializedAuth); conn.set(authToAccessKey, serializedAccessToken); } @@ -303,6 +310,7 @@ public class CustomRedisTokenStore implements TokenStore { if (token.getExpiration() != null) { int seconds = token.getExpiresIn(); conn.expire(accessKey, seconds); + conn.expire(accessBakKey, seconds + 60); conn.expire(authKey, seconds); conn.expire(authToAccessKey, seconds); conn.expire(clientId, seconds); @@ -363,6 +371,7 @@ public class CustomRedisTokenStore implements TokenStore { public void removeAccessToken(String tokenValue) { byte[] accessKey = serializeKey(ACCESS + tokenValue); + byte[] accessBakKey = serializeKey(ACCESS_BAK + tokenValue); byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + tokenValue); byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); RedisConnection conn = getConnection(); @@ -371,6 +380,7 @@ public class CustomRedisTokenStore implements TokenStore { byte[] auth = conn.get(authKey); conn.openPipeline(); conn.del(accessKey); + conn.del(accessBakKey); conn.del(accessToRefreshKey); // Don't remove the refresh token - it's up to the caller to do that conn.del(authKey); diff --git a/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java b/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java index 45f0c12983b4b5cf00b31dfd3d0b9c569079a577..4cca96407a258cecfe018e7568a0eac52010378d 100644 --- a/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java +++ b/zlt-commons/zlt-common-core/src/main/java/com/central/common/constant/SecurityConstants.java @@ -154,6 +154,10 @@ public interface SecurityConstants { * redis中授权token对应的key */ String REDIS_TOKEN_AUTH = "auth:"; + /** + * 值同access 过期时间+60 + */ + String ACCESS_BAK = "access_bak:"; /** * redis中应用对应的token集合的key */ diff --git a/zlt-commons/zlt-redis-spring-boot-starter/src/main/java/com/central/common/redis/template/RedisRepository.java b/zlt-commons/zlt-redis-spring-boot-starter/src/main/java/com/central/common/redis/template/RedisRepository.java index d6c1162d844c5c0892556ccb81e53edf244a1590..7c80302fcff600a0a3e933338f862e2835bf230d 100644 --- a/zlt-commons/zlt-redis-spring-boot-starter/src/main/java/com/central/common/redis/template/RedisRepository.java +++ b/zlt-commons/zlt-redis-spring-boot-starter/src/main/java/com/central/common/redis/template/RedisRepository.java @@ -1,473 +1,498 @@ -package com.central.common.redis.template; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.dao.DataAccessException; -import org.springframework.data.redis.connection.RedisClusterNode; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisServerCommands; -import org.springframework.data.redis.core.*; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.data.redis.serializer.SerializationUtils; -import org.springframework.util.Assert; - -import java.util.*; -import java.util.concurrent.TimeUnit; - -/** - * Redis Repository - * redis 基本操作 可扩展,基本够用了 - * - * @author zlt - *

- * Blog: https://zlt2000.gitee.io - * Github: https://github.com/zlt2000 - */ -@Slf4j -public class RedisRepository { - /** - * Spring Redis Template - */ - private RedisTemplate redisTemplate; - - public RedisRepository(RedisTemplate redisTemplate) { - this.redisTemplate = redisTemplate; - } - - /** - * 获取链接工厂 - */ - public RedisConnectionFactory getConnectionFactory() { - return this.redisTemplate.getConnectionFactory(); - } - - /** - * 获取 RedisTemplate对象 - */ - public RedisTemplate getRedisTemplate() { - return redisTemplate; - } - - /** - * 清空DB - * - * @param node redis 节点 - */ - public void flushDB(RedisClusterNode node) { - this.redisTemplate.opsForCluster().flushDb(node); - } - - /** - * 添加到带有 过期时间的 缓存 - * - * @param key redis主键 - * @param value 值 - * @param time 过期时间(单位秒) - */ - public void setExpire(final byte[] key, final byte[] value, final long time) { - redisTemplate.execute((RedisCallback) connection -> { - connection.setEx(key, time, value); - return 1L; - }); - } - - /** - * 添加到带有 过期时间的 缓存 - * - * @param key redis主键 - * @param value 值 - * @param time 过期时间 - * @param timeUnit 过期时间单位 - */ - public void setExpire(final String key, final Object value, final long time, final TimeUnit timeUnit) { - redisTemplate.opsForValue().set(key, value, time, timeUnit); - } - public void setExpire(final String key, final Object value, final long time) { - this.setExpire(key, value, time, TimeUnit.SECONDS); - } - public void setExpire(final String key, final Object value, final long time, final TimeUnit timeUnit, RedisSerializer valueSerializer) { - byte[] rawKey = rawKey(key); - byte[] rawValue = rawValue(value, valueSerializer); - - redisTemplate.execute(new RedisCallback() { - @Override - public Object doInRedis(RedisConnection connection) throws DataAccessException { - potentiallyUsePsetEx(connection); - return null; - } - public void potentiallyUsePsetEx(RedisConnection connection) { - if (!TimeUnit.MILLISECONDS.equals(timeUnit) || !failsafeInvokePsetEx(connection)) { - connection.setEx(rawKey, TimeoutUtils.toSeconds(time, timeUnit), rawValue); - } - } - private boolean failsafeInvokePsetEx(RedisConnection connection) { - boolean failed = false; - try { - connection.pSetEx(rawKey, time, rawValue); - } catch (UnsupportedOperationException e) { - failed = true; - } - return !failed; - } - }, true); - } - - /** - * 一次性添加数组到 过期时间的 缓存,不用多次连接,节省开销 - * - * @param keys redis主键数组 - * @param values 值数组 - * @param time 过期时间(单位秒) - */ - public void setExpire(final String[] keys, final Object[] values, final long time) { - for (int i = 0; i < keys.length; i++) { - redisTemplate.opsForValue().set(keys[i], values[i], time, TimeUnit.SECONDS); - } - } - - - /** - * 一次性添加数组到 过期时间的 缓存,不用多次连接,节省开销 - * - * @param keys the keys - * @param values the values - */ - public void set(final String[] keys, final Object[] values) { - for (int i = 0; i < keys.length; i++) { - redisTemplate.opsForValue().set(keys[i], values[i]); - } - } - - - /** - * 添加到缓存 - * - * @param key the key - * @param value the value - */ - public void set(final String key, final Object value) { - redisTemplate.opsForValue().set(key, value); - } - - /** - * 查询在以keyPatten的所有 key - * - * @param keyPatten the key patten - * @return the set - */ - public Set keys(final String keyPatten) { - return redisTemplate.keys(keyPatten + "*"); - } - - /** - * 根据key获取对象 - * - * @param key the key - * @return the byte [ ] - */ - public byte[] get(final byte[] key) { - return redisTemplate.execute((RedisCallback) connection -> connection.get(key)); - } - - /** - * 根据key获取对象 - * - * @param key the key - * @return the string - */ - public Object get(final String key) { - return redisTemplate.opsForValue().get(key); - } - /** - * 根据key获取对象 - * - * @param key the key - * @param valueSerializer 序列化 - * @return the string - */ - public Object get(final String key, RedisSerializer valueSerializer) { - byte[] rawKey = rawKey(key); - return redisTemplate.execute(connection -> deserializeValue(connection.get(rawKey), valueSerializer), true); - } - - /** - * Ops for hash hash operations. - * - * @return the hash operations - */ - public HashOperations opsForHash() { - return redisTemplate.opsForHash(); - } - - /** - * 对HashMap操作 - * - * @param key the key - * @param hashKey the hash key - * @param hashValue the hash value - */ - public void putHashValue(String key, String hashKey, Object hashValue) { - opsForHash().put(key, hashKey, hashValue); - } - - /** - * 获取单个field对应的值 - * - * @param key the key - * @param hashKey the hash key - * @return the hash values - */ - public Object getHashValues(String key, String hashKey) { - return opsForHash().get(key, hashKey); - } - - /** - * 根据key值删除 - * - * @param key the key - * @param hashKeys the hash keys - */ - public void delHashValues(String key, Object... hashKeys) { - opsForHash().delete(key, hashKeys); - } - - /** - * key只匹配map - * - * @param key the key - * @return the hash value - */ - public Map getHashValue(String key) { - return opsForHash().entries(key); - } - - /** - * 批量添加 - * - * @param key the key - * @param map the map - */ - public void putHashValues(String key, Map map) { - opsForHash().putAll(key, map); - } - - /** - * 集合数量 - * - * @return the long - */ - public long dbSize() { - return redisTemplate.execute(RedisServerCommands::dbSize); - } - - /** - * 清空redis存储的数据 - * - * @return the string - */ - public String flushDB() { - return redisTemplate.execute((RedisCallback) connection -> { - connection.flushDb(); - return "ok"; - }); - } - - /** - * 判断某个主键是否存在 - * - * @param key the key - * @return the boolean - */ - public boolean exists(final String key) { - return redisTemplate.hasKey(key); - } - - - /** - * 删除key - * - * @param keys the keys - * @return the long - */ - public boolean del(final String... keys) { - boolean result = false; - for (String key : keys) { - result = redisTemplate.delete(key); - } - return result; - } - - /** - * 对某个主键对应的值加一,value值必须是全数字的字符串 - * - * @param key the key - * @return the long - */ - public long incr(final String key) { - return redisTemplate.opsForValue().increment(key); - } - - /** - * redis List 引擎 - * - * @return the list operations - */ - public ListOperations opsForList() { - return redisTemplate.opsForList(); - } - - /** - * redis List数据结构 : 将一个或多个值 value 插入到列表 key 的表头 - * - * @param key the key - * @param value the value - * @return the long - */ - public Long leftPush(String key, Object value) { - return opsForList().leftPush(key, value); - } - - /** - * redis List数据结构 : 移除并返回列表 key 的头元素 - * - * @param key the key - * @return the string - */ - public Object leftPop(String key) { - return opsForList().leftPop(key); - } - - /** - * redis List数据结构 :将一个或多个值 value 插入到列表 key 的表尾(最右边)。 - * - * @param key the key - * @param value the value - * @return the long - */ - public Long in(String key, Object value) { - return opsForList().rightPush(key, value); - } - - /** - * redis List数据结构 : 移除并返回列表 key 的末尾元素 - * - * @param key the key - * @return the string - */ - public Object rightPop(String key) { - return opsForList().rightPop(key); - } - - - /** - * redis List数据结构 : 返回列表 key 的长度 ; 如果 key 不存在,则 key 被解释为一个空列表,返回 0 ; 如果 key 不是列表类型,返回一个错误。 - * - * @param key the key - * @return the long - */ - public Long length(String key) { - return opsForList().size(key); - } - - - /** - * redis List数据结构 : 根据参数 i 的值,移除列表中与参数 value 相等的元素 - * - * @param key the key - * @param i the - * @param value the value - */ - public void remove(String key, long i, Object value) { - opsForList().remove(key, i, value); - } - - /** - * redis List数据结构 : 将列表 key 下标为 index 的元素的值设置为 value - * - * @param key the key - * @param index the index - * @param value the value - */ - public void set(String key, long index, Object value) { - opsForList().set(key, index, value); - } - - /** - * redis List数据结构 : 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 end 指定。 - * - * @param key the key - * @param start the start - * @param end the end - * @return the list - */ - public List getList(String key, int start, int end) { - return opsForList().range(key, start, end); - } - - /** - * redis List数据结构 : 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 end 指定。 - * - * @param key the key - * @param start the start - * @param end the end - * @param valueSerializer 序列化 - * @return the list - */ - public List getList(String key, int start, int end, RedisSerializer valueSerializer) { - byte[] rawKey = rawKey(key); - return redisTemplate.execute(connection -> deserializeValues(connection.lRange(rawKey, start, end), valueSerializer), true); - } - - /** - * redis List数据结构 : 批量存储 - * - * @param key the key - * @param list the list - * @return the long - */ - public Long leftPushAll(String key, List list) { - return opsForList().leftPushAll(key, list); - } - - /** - * redis List数据结构 : 将值 value 插入到列表 key 当中,位于值 index 之前或之后,默认之后。 - * - * @param key the key - * @param index the index - * @param value the value - */ - public void insert(String key, long index, Object value) { - opsForList().set(key, index, value); - } - - private byte[] rawKey(Object key) { - Assert.notNull(key, "non null key required"); - - if (key instanceof byte[]) { - return (byte[]) key; - } - RedisSerializer redisSerializer = (RedisSerializer)redisTemplate.getKeySerializer(); - return redisSerializer.serialize(key); - } - private byte[] rawValue(Object value, RedisSerializer valueSerializer) { - if (value instanceof byte[]) { - return (byte[]) value; - } - - return valueSerializer.serialize(value); - } - - private List deserializeValues(List rawValues, RedisSerializer valueSerializer) { - if (valueSerializer == null) { - return rawValues; - } - return SerializationUtils.deserialize(rawValues, valueSerializer); - } - - private Object deserializeValue(byte[] value, RedisSerializer valueSerializer) { - if (valueSerializer == null) { - return value; - } - return valueSerializer.deserialize(value); - } -} +package com.central.common.redis.template; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.RedisClusterNode; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisServerCommands; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationUtils; +import org.springframework.util.Assert; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * Redis Repository + * redis 基本操作 可扩展,基本够用了 + * + * @author zlt + *

+ * Blog: https://zlt2000.gitee.io + * Github: https://github.com/zlt2000 + */ +@Slf4j +public class RedisRepository { + /** + * Spring Redis Template + */ + private RedisTemplate redisTemplate; + + public RedisRepository(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + /** + * 获取链接工厂 + */ + public RedisConnectionFactory getConnectionFactory() { + return this.redisTemplate.getConnectionFactory(); + } + + /** + * 获取 RedisTemplate对象 + */ + public RedisTemplate getRedisTemplate() { + return redisTemplate; + } + + /** + * 清空DB + * + * @param node redis 节点 + */ + public void flushDB(RedisClusterNode node) { + this.redisTemplate.opsForCluster().flushDb(node); + } + + /** + * 添加到带有 过期时间的 缓存 + * + * @param key redis主键 + * @param value 值 + * @param time 过期时间(单位秒) + */ + public void setExpire(final byte[] key, final byte[] value, final long time) { + redisTemplate.execute((RedisCallback) connection -> { + connection.setEx(key, time, value); + return 1L; + }); + } + + /** + * 添加到带有 过期时间的 缓存 + * + * @param key redis主键 + * @param value 值 + * @param time 过期时间 + * @param timeUnit 过期时间单位 + */ + public void setExpire(final String key, final Object value, final long time, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, time, timeUnit); + } + public void setExpire(final String key, final Object value, final long time) { + this.setExpire(key, value, time, TimeUnit.SECONDS); + } + public void setExpire(final String key, final Object value, final long time, final TimeUnit timeUnit, RedisSerializer valueSerializer) { + byte[] rawKey = rawKey(key); + byte[] rawValue = rawValue(value, valueSerializer); + + redisTemplate.execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) throws DataAccessException { + potentiallyUsePsetEx(connection); + return null; + } + public void potentiallyUsePsetEx(RedisConnection connection) { + if (!TimeUnit.MILLISECONDS.equals(timeUnit) || !failsafeInvokePsetEx(connection)) { + connection.setEx(rawKey, TimeoutUtils.toSeconds(time, timeUnit), rawValue); + } + } + private boolean failsafeInvokePsetEx(RedisConnection connection) { + boolean failed = false; + try { + connection.pSetEx(rawKey, time, rawValue); + } catch (UnsupportedOperationException e) { + failed = true; + } + return !failed; + } + }, true); + } + + /** + * 一次性添加数组到 过期时间的 缓存,不用多次连接,节省开销 + * + * @param keys redis主键数组 + * @param values 值数组 + * @param time 过期时间(单位秒) + */ + public void setExpire(final String[] keys, final Object[] values, final long time) { + for (int i = 0; i < keys.length; i++) { + redisTemplate.opsForValue().set(keys[i], values[i], time, TimeUnit.SECONDS); + } + } + + + /** + * 一次性添加数组到 过期时间的 缓存,不用多次连接,节省开销 + * + * @param keys the keys + * @param values the values + */ + public void set(final String[] keys, final Object[] values) { + for (int i = 0; i < keys.length; i++) { + redisTemplate.opsForValue().set(keys[i], values[i]); + } + } + + + /** + * 添加到缓存 + * + * @param key the key + * @param value the value + */ + public void set(final String key, final Object value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 查询在以keyPatten的所有 key + * + * @param keyPatten the key patten + * @return the set + */ + public Set keys(final String keyPatten) { + return redisTemplate.keys(keyPatten + "*"); + } + + /** + * 根据key获取对象 + * + * @param key the key + * @return the byte [ ] + */ + public byte[] get(final byte[] key) { + return redisTemplate.execute((RedisCallback) connection -> connection.get(key)); + } + + /** + * 根据key获取对象 + * + * @param key the key + * @return the string + */ + public Object get(final String key) { + return redisTemplate.opsForValue().get(key); + } + + /** + *获取原来key键对应的值并重新赋新值。 + * @param key + * @param value + * @return + */ + public String getAndSet(final String key,String value) { + String result = null; + if (StringUtils.isEmpty(key)){ + log.error("非法入参"); + return null; + } + try { + Object object =redisTemplate.opsForValue().getAndSet(key, value); + if (object !=null){ + result = object.toString(); + } + }catch (Exception e){ + log.error("redisTemplate操作异常",e); + } + return result; + } + /** + * 根据key获取对象 + * + * @param key the key + * @param valueSerializer 序列化 + * @return the string + */ + public Object get(final String key, RedisSerializer valueSerializer) { + byte[] rawKey = rawKey(key); + return redisTemplate.execute(connection -> deserializeValue(connection.get(rawKey), valueSerializer), true); + } + + + /** + * Ops for hash hash operations. + * + * @return the hash operations + */ + public HashOperations opsForHash() { + return redisTemplate.opsForHash(); + } + + /** + * 对HashMap操作 + * + * @param key the key + * @param hashKey the hash key + * @param hashValue the hash value + */ + public void putHashValue(String key, String hashKey, Object hashValue) { + opsForHash().put(key, hashKey, hashValue); + } + + /** + * 获取单个field对应的值 + * + * @param key the key + * @param hashKey the hash key + * @return the hash values + */ + public Object getHashValues(String key, String hashKey) { + return opsForHash().get(key, hashKey); + } + + /** + * 根据key值删除 + * + * @param key the key + * @param hashKeys the hash keys + */ + public void delHashValues(String key, Object... hashKeys) { + opsForHash().delete(key, hashKeys); + } + + /** + * key只匹配map + * + * @param key the key + * @return the hash value + */ + public Map getHashValue(String key) { + return opsForHash().entries(key); + } + + /** + * 批量添加 + * + * @param key the key + * @param map the map + */ + public void putHashValues(String key, Map map) { + opsForHash().putAll(key, map); + } + + /** + * 集合数量 + * + * @return the long + */ + public long dbSize() { + return redisTemplate.execute(RedisServerCommands::dbSize); + } + + /** + * 清空redis存储的数据 + * + * @return the string + */ + public String flushDB() { + return redisTemplate.execute((RedisCallback) connection -> { + connection.flushDb(); + return "ok"; + }); + } + + /** + * 判断某个主键是否存在 + * + * @param key the key + * @return the boolean + */ + public boolean exists(final String key) { + return redisTemplate.hasKey(key); + } + + + /** + * 删除key + * + * @param keys the keys + * @return the long + */ + public boolean del(final String... keys) { + boolean result = false; + for (String key : keys) { + result = redisTemplate.delete(key); + } + return result; + } + + /** + * 对某个主键对应的值加一,value值必须是全数字的字符串 + * + * @param key the key + * @return the long + */ + public long incr(final String key) { + return redisTemplate.opsForValue().increment(key); + } + + /** + * redis List 引擎 + * + * @return the list operations + */ + public ListOperations opsForList() { + return redisTemplate.opsForList(); + } + + /** + * redis List数据结构 : 将一个或多个值 value 插入到列表 key 的表头 + * + * @param key the key + * @param value the value + * @return the long + */ + public Long leftPush(String key, Object value) { + return opsForList().leftPush(key, value); + } + + /** + * redis List数据结构 : 移除并返回列表 key 的头元素 + * + * @param key the key + * @return the string + */ + public Object leftPop(String key) { + return opsForList().leftPop(key); + } + + /** + * redis List数据结构 :将一个或多个值 value 插入到列表 key 的表尾(最右边)。 + * + * @param key the key + * @param value the value + * @return the long + */ + public Long in(String key, Object value) { + return opsForList().rightPush(key, value); + } + + /** + * redis List数据结构 : 移除并返回列表 key 的末尾元素 + * + * @param key the key + * @return the string + */ + public Object rightPop(String key) { + return opsForList().rightPop(key); + } + + + /** + * redis List数据结构 : 返回列表 key 的长度 ; 如果 key 不存在,则 key 被解释为一个空列表,返回 0 ; 如果 key 不是列表类型,返回一个错误。 + * + * @param key the key + * @return the long + */ + public Long length(String key) { + return opsForList().size(key); + } + + + /** + * redis List数据结构 : 根据参数 i 的值,移除列表中与参数 value 相等的元素 + * + * @param key the key + * @param i the + * @param value the value + */ + public void remove(String key, long i, Object value) { + opsForList().remove(key, i, value); + } + + /** + * redis List数据结构 : 将列表 key 下标为 index 的元素的值设置为 value + * + * @param key the key + * @param index the index + * @param value the value + */ + public void set(String key, long index, Object value) { + opsForList().set(key, index, value); + } + + /** + * redis List数据结构 : 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 end 指定。 + * + * @param key the key + * @param start the start + * @param end the end + * @return the list + */ + public List getList(String key, int start, int end) { + return opsForList().range(key, start, end); + } + + /** + * redis List数据结构 : 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 end 指定。 + * + * @param key the key + * @param start the start + * @param end the end + * @param valueSerializer 序列化 + * @return the list + */ + public List getList(String key, int start, int end, RedisSerializer valueSerializer) { + byte[] rawKey = rawKey(key); + return redisTemplate.execute(connection -> deserializeValues(connection.lRange(rawKey, start, end), valueSerializer), true); + } + + /** + * redis List数据结构 : 批量存储 + * + * @param key the key + * @param list the list + * @return the long + */ + public Long leftPushAll(String key, List list) { + return opsForList().leftPushAll(key, list); + } + + /** + * redis List数据结构 : 将值 value 插入到列表 key 当中,位于值 index 之前或之后,默认之后。 + * + * @param key the key + * @param index the index + * @param value the value + */ + public void insert(String key, long index, Object value) { + opsForList().set(key, index, value); + } + + private byte[] rawKey(Object key) { + Assert.notNull(key, "non null key required"); + + if (key instanceof byte[]) { + return (byte[]) key; + } + RedisSerializer redisSerializer = (RedisSerializer)redisTemplate.getKeySerializer(); + return redisSerializer.serialize(key); + } + private byte[] rawValue(Object value, RedisSerializer valueSerializer) { + if (value instanceof byte[]) { + return (byte[]) value; + } + + return valueSerializer.serialize(value); + } + + private List deserializeValues(List rawValues, RedisSerializer valueSerializer) { + if (valueSerializer == null) { + return rawValues; + } + return SerializationUtils.deserialize(rawValues, valueSerializer); + } + + private Object deserializeValue(byte[] value, RedisSerializer valueSerializer) { + if (valueSerializer == null) { + return value; + } + return valueSerializer.deserialize(value); + } +} diff --git a/zlt-uaa/src/main/java/com/central/oauth/config/RedisListenerConfig.java b/zlt-uaa/src/main/java/com/central/oauth/config/RedisListenerConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..68f98f8f89b0daa7c8989136215bc84853c13325 --- /dev/null +++ b/zlt-uaa/src/main/java/com/central/oauth/config/RedisListenerConfig.java @@ -0,0 +1,24 @@ +package com.central.oauth.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +/** + * + * redis过期key监听器配置类 + * @author zlt + * + */ +@Configuration +public class RedisListenerConfig { + @Bean + @Primary + public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(factory); + return container; + } +} diff --git a/zlt-uaa/src/main/java/com/central/oauth/listener/RedisKeyExpirationListener.java b/zlt-uaa/src/main/java/com/central/oauth/listener/RedisKeyExpirationListener.java new file mode 100644 index 0000000000000000000000000000000000000000..eb4e1db7903e410f93309bc13b6512ee469aa837 --- /dev/null +++ b/zlt-uaa/src/main/java/com/central/oauth/listener/RedisKeyExpirationListener.java @@ -0,0 +1,112 @@ +package com.central.oauth.listener; + +import com.central.common.constant.SecurityConstants; +import com.central.common.redis.template.RedisRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy; +import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy; +import org.springframework.stereotype.Component; + +/** + * + * redis过期key监听器 + * @author zlt + * + */ +@Component +@Slf4j +public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { + @Autowired + private RedisRepository redisRepository; + private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); + private final RedisConnectionFactory connectionFactory; + + public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer, RedisConnectionFactory connectionFactory) { + super(listenerContainer); + this.connectionFactory = connectionFactory; + } + + @Override + public void onMessage(Message message, byte[] pattern) { + if (message == null) { + log.debug("message不能为空"); + return; + } + //获取失效的的key + String expiredKey = message.toString(); + if (StringUtils.isEmpty(expiredKey)) { + log.debug("expiredKey不能为空"); + return; + } + String accesskey = expiredKey.substring(0, expiredKey.indexOf(":") + 1); + if (!"access:".equals(accesskey)) { + log.debug("非需要监听key,跳过"); + return; + } + String accessValue = expiredKey.substring(expiredKey.indexOf(":") + 1); + // 分布式集群部署下防止一个过期被多个服务重复消费 + String qc = "qc:" + accessValue; + String oldLock = redisRepository.getAndSet(qc, "1"); + if (StringUtils.isNotEmpty(oldLock) && "1".equals(oldLock)) { + log.debug("其他节点已经处理了该数据,跳过"); + return; + } + byte[] accessBakKey = serializeKey(SecurityConstants.ACCESS_BAK + accessValue); + byte[] authKey = serializeKey(SecurityConstants.REDIS_TOKEN_AUTH + accessValue); + RedisConnection conn = getConnection(); + try { + byte[] access = conn.get(accessBakKey); + byte[] auth = conn.get(authKey); + OAuth2Authentication authentication = deserializeAuthentication(auth); + if (authentication != null) { + byte[] unameKey = serializeKey(SecurityConstants.REDIS_UNAME_TO_ACCESS + getApprovalKey(authentication)); + byte[] clientId = serializeKey(SecurityConstants.REDIS_CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); + conn.openPipeline(); + conn.lRem(unameKey, 1, access); + conn.lRem(clientId, 1, access); + conn.closePipeline(); + } + } catch (Exception e) { + log.error(e.getMessage()); + } finally { + conn.del(serializeKey(qc)); + conn.close(); + } + + } + + private byte[] serializeKey(String object) { + return serialize("" + object); + } + + private byte[] serialize(String string) { + return serializationStrategy.serialize(string); + } + + private RedisConnection getConnection() { + return connectionFactory.getConnection(); + } + + private OAuth2Authentication deserializeAuthentication(byte[] bytes) { + return serializationStrategy.deserialize(bytes, OAuth2Authentication.class); + } + + private static String getApprovalKey(OAuth2Authentication authentication) { + String userName = authentication.getUserAuthentication() == null ? "" + : authentication.getUserAuthentication().getName(); + return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName); + } + + private static String getApprovalKey(String clientId, String userName) { + return clientId + (userName == null ? "" : ":" + userName); + } +}