Pārlūkot izejas kodu

RedisUserTokenManager 增加本地缓存提升效率

zhou-hao 4 gadi atpakaļ
vecāks
revīzija
0e4d4016d9

+ 17 - 1
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserToken.java

@@ -49,8 +49,25 @@ public interface UserToken extends Serializable, Comparable<UserToken> {
      */
     String getType();
 
+    /**
+     * @return 会话过期时间, 单位毫秒
+     */
     long getMaxInactiveInterval();
 
+    /**
+     * 检查会话是否过期
+     *
+     * @return 是否过期
+     * @since 4.0.10
+     */
+    default boolean checkExpired() {
+        long maxInactiveInterval = getMaxInactiveInterval();
+        if (maxInactiveInterval > 0) {
+            return System.currentTimeMillis() - getLastRequestTime() > maxInactiveInterval;
+        }
+        return false;
+    }
+
     default boolean isNormal() {
         return getState() == TokenState.normal;
     }
@@ -77,7 +94,6 @@ public interface UserToken extends Serializable, Comparable<UserToken> {
         return getState() == TokenState.deny;
     }
 
-
     default boolean validate() {
         if (!isNormal()) {
             throw new UnAuthorizedException(getState());

+ 64 - 17
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java

@@ -17,11 +17,16 @@ import org.springframework.data.redis.core.*;
 import org.springframework.data.redis.serializer.RedisSerializationContext;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import reactor.core.publisher.Flux;
+import reactor.core.publisher.FluxSink;
 import reactor.core.publisher.Mono;
 
 import java.time.Duration;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 public class RedisUserTokenManager implements UserTokenManager {
@@ -32,21 +37,41 @@ public class RedisUserTokenManager implements UserTokenManager {
 
     private final ReactiveSetOperations<Object, Object> userTokenMapping;
 
+    @Setter
+    private Map<String, SimpleUserToken> localCache = new ConcurrentHashMap<>();
+
+    private FluxSink<UserToken> touchSink;
+
     public RedisUserTokenManager(ReactiveRedisOperations<Object, Object> operations) {
         this.operations = operations;
         this.userTokenStore = operations.opsForHash();
         this.userTokenMapping = operations.opsForSet();
+        this.operations
+                .listenToChannel("_user_token_removed")
+                .subscribe(msg -> localCache.remove(String.valueOf(msg.getMessage())));
+
+        Flux.<UserToken>create(sink -> this.touchSink = sink)
+                .buffer(Flux.interval(Duration.ofSeconds(10)), HashSet::new)
+                .flatMap(list -> Flux
+                        .fromIterable(list)
+                        .flatMap(token -> operations
+                                .expire(getTokenRedisKey(token.getToken()), Duration.ofMillis(token.getMaxInactiveInterval()))
+                                .then())
+                        .onErrorResume(err -> Mono.empty()))
+                .subscribe();
+
     }
 
     @SuppressWarnings("all")
     public RedisUserTokenManager(ReactiveRedisConnectionFactory connectionFactory) {
         this(new ReactiveRedisTemplate<>(connectionFactory,
-                                         RedisSerializationContext.newSerializationContext()
-                                                                  .key((RedisSerializer) RedisSerializer.string())
-                                                                  .value(RedisSerializer.java())
-                                                                  .hashKey(RedisSerializer.string())
-                                                                  .hashValue(RedisSerializer.java())
-                                                                  .build()
+                                         RedisSerializationContext
+                                                 .newSerializationContext()
+                                                 .key((RedisSerializer) RedisSerializer.string())
+                                                 .value(RedisSerializer.java())
+                                                 .hashKey(RedisSerializer.string())
+                                                 .hashValue(RedisSerializer.java())
+                                                 .build()
         ));
     }
 
@@ -72,11 +97,17 @@ public class RedisUserTokenManager implements UserTokenManager {
 
     @Override
     public Mono<UserToken> getByToken(String token) {
+        SimpleUserToken inCache = localCache.get(token);
+        if (inCache != null && inCache.isNormal()) {
+            return Mono.just(inCache);
+        }
         return userTokenStore
                 .entries(getTokenRedisKey(token))
                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
                 .filter(map -> !map.isEmpty())
-                .map(SimpleUserToken::of);
+                .map(SimpleUserToken::of)
+                .doOnNext(userToken -> localCache.put(userToken.getToken(), userToken))
+                .cast(UserToken.class);
     }
 
     @Override
@@ -185,7 +216,7 @@ public class RedisUserTokenManager implements UserTokenManager {
     public Mono<UserToken> signIn(String token, String type, String userId, long maxInactiveInterval) {
         return Mono
                 .defer(() -> {
-                    Mono<UserToken> doSign = Mono.defer(() -> {
+                    Mono<SimpleUserToken> doSign = Mono.defer(() -> {
                         Map<String, Object> map = new HashMap<>();
                         map.put("token", token);
                         map.put("type", type);
@@ -237,12 +268,17 @@ public class RedisUserTokenManager implements UserTokenManager {
 
     @Override
     public Mono<Void> touch(String token) {
+        SimpleUserToken inCache = localCache.get(token);
+        if (inCache != null && inCache.isNormal()) {
+            inCache.setLastRequestTime(System.currentTimeMillis());
+            //异步touch
+            touchSink.next(inCache);
+            return Mono.empty();
+        }
         return getByToken(token)
                 .flatMap(userToken -> {
                     if (userToken.getMaxInactiveInterval() > 0) {
-                        return operations
-                                .expire(getTokenRedisKey(token), Duration.ofMillis(userToken.getMaxInactiveInterval()))
-                                .then();
+                        touchSink.next(userToken);
                     }
                     return Mono.empty();
                 });
@@ -268,26 +304,37 @@ public class RedisUserTokenManager implements UserTokenManager {
                 .then();
     }
 
+    private Mono<Void> notifyTokenRemoved(String token) {
+        return operations.convertAndSend("_user_token_removed", token).then();
+    }
+
     private Mono<Void> onTokenRemoved(UserToken token) {
+        localCache.remove(token.getToken());
+
         if (eventPublisher == null) {
-            return Mono.empty();
+            return notifyTokenRemoved(token.getToken());
         }
-        return Mono.fromRunnable(() -> eventPublisher.publishEvent(new UserTokenRemovedEvent(token)));
+        return Mono.fromRunnable(() -> eventPublisher.publishEvent(new UserTokenRemovedEvent(token)))
+                   .then(notifyTokenRemoved(token.getToken()));
     }
 
-    private Mono<Void> onTokenChanged(UserToken old, UserToken newToken) {
+    private Mono<Void> onTokenChanged(UserToken old, SimpleUserToken newToken) {
+        localCache.put(newToken.getToken(), newToken);
         if (eventPublisher == null) {
-            return Mono.empty();
+            return notifyTokenRemoved(newToken.getToken());
         }
         return Mono.fromRunnable(() -> eventPublisher.publishEvent(new UserTokenChangedEvent(old, newToken)));
     }
 
-    private Mono<UserToken> onUserTokenCreated(UserToken token) {
+    private Mono<UserToken> onUserTokenCreated(SimpleUserToken token) {
+        localCache.put(token.getToken(), token);
         if (eventPublisher == null) {
-            return Mono.just(token);
+            return notifyTokenRemoved(token.getToken())
+                    .thenReturn(token);
         }
         return Mono
                 .fromRunnable(() -> eventPublisher.publishEvent(new UserTokenCreatedEvent(token)))
+                .then(notifyTokenRemoved(token.getToken()))
                 .thenReturn(token);
     }
 

+ 11 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/SimpleUserToken.java

@@ -1,5 +1,6 @@
 package org.hswebframework.web.authorization.token.redis;
 
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.ToString;
@@ -12,6 +13,7 @@ import java.util.Map;
 @Getter
 @Setter
 @ToString(exclude = "token")
+@EqualsAndHashCode(of = "token")
 public class SimpleUserToken implements UserToken {
 
     private String userId;
@@ -34,4 +36,13 @@ public class SimpleUserToken implements UserToken {
 
         return FastBeanCopier.copy(map, new SimpleUserToken());
     }
+
+    @Override
+    public boolean isNormal() {
+        if (checkExpired()) {
+            setState(TokenState.expired);
+            return false;
+        }
+        return UserToken.super.isNormal();
+    }
 }