Procházet zdrojové kódy

修复Redis集群环境更新令牌异常问题

woody před 1 rokem
rodič
revize
88e344a612

+ 29 - 9
framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenStore.java

@@ -1,16 +1,21 @@
 package com.chelvc.framework.oauth.token;
 
-import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.common.model.Terminal;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.oauth.context.OAuthContextHolder;
 import com.chelvc.framework.redis.context.RedisContextHolder;
-import com.google.common.collect.ImmutableMap;
 import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.oauth2.common.OAuth2AccessToken;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
@@ -25,8 +30,26 @@ import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
  */
 @Slf4j
 public class RedisTokenStore extends JwtTokenStore {
+    /**
+     * 终端/令牌更新脚本映射表
+     */
+    private final Map<Terminal, RedisScript<Boolean>> scripts;
+
     public RedisTokenStore(@NonNull JwtAccessTokenConverter jwtAccessTokenConverter) {
         super(jwtAccessTokenConverter);
+
+        // 初始化各终端令牌更新脚本映射表
+        this.scripts = Stream.of(Terminal.values()).collect(Collectors.toMap(terminal -> terminal,
+                terminal -> new DefaultRedisScript<>(
+                        String.format("return redis.call('HSET', KEYS[1], 'scope', ARGV[1], '%s', ARGV[2]) " +
+                                "and redis.call('EXPIRE', KEYS[1], ARGV[3])", terminal), Boolean.class
+                )));
+
+        // 初始化无终端令牌更新脚本
+        this.scripts.put(null, new DefaultRedisScript<>(
+                "return redis.call('HSET', KEYS[1], 'scope', ARGV[1], 'null', ARGV[2]) " +
+                        "and redis.call('EXPIRE', KEYS[1], ARGV[3])", Boolean.class
+        ));
     }
 
     @Override
@@ -35,15 +58,12 @@ public class RedisTokenStore extends JwtTokenStore {
         if (principal instanceof UserDetails) {
             principal = ((UserDetails) principal).getUsername();
         }
-        String key = OAuthContextHolder.key(principal);
+        List<String> keys = Collections.singletonList(OAuthContextHolder.key(principal));
         String scope = ObjectUtils.ifNull(OAuthContextHolder.getScope(token), StringUtils.EMPTY);
-        Map<String, ?> values = ImmutableMap.of(
-                SessionContextHolder.HEADER_SCOPE, scope,
-                String.valueOf(SessionContextHolder.getTerminal()), token.getValue()
-        );
-        Duration duration = RedisContextHolder.duration(token.getExpiration());
+        long duration = RedisContextHolder.duration(token.getExpiration()).getSeconds();
+        RedisScript<Boolean> script = this.scripts.get(SessionContextHolder.getTerminal());
         try {
-            RedisContextHolder.put(RedisContextHolder.getDefaultTemplate(), key, values, duration);
+            RedisContextHolder.getDefaultTemplate().execute(script, keys, scope, token.getValue(), duration);
         } catch (Exception e) {
             log.warn("Redis token save failed: {}", e.getMessage());
         }

+ 0 - 49
framework-redis/src/main/java/com/chelvc/framework/redis/context/RedisContextHolder.java

@@ -224,15 +224,6 @@ public final class RedisContextHolder {
                     "else redis.call('SET', KEYS[1], 0) return 0 end", Long.class
     );
 
-    /**
-     * 批量设置带过期时间的hash值
-     */
-    private static final RedisScript<Boolean> HSET_WITH_DURATION_SCRIPT = new DefaultRedisScript<>(
-            "local values = {} for i = 2, #KEYS do table.insert(values, KEYS[i]) " +
-                    "table.insert(values, ARGV[i]) end return redis.call('HSET', KEYS[1], unpack(values)) " +
-                    "and redis.call('EXPIRE', KEYS[1], ARGV[1])", Boolean.class
-    );
-
     /**
      * 新增Stream消息流脚本
      */
@@ -1073,46 +1064,6 @@ public final class RedisContextHolder {
                 initialValue, duration.getSeconds());
     }
 
-    /**
-     * 批量设置带过期时间的hash值
-     *
-     * @param key      键名称
-     * @param values   hash键/值对
-     * @param duration 有效时间
-     * @return true/false
-     */
-    public static boolean put(@NonNull String key, @NonNull Map<String, ?> values, @NonNull Duration duration) {
-        return put(getRedisTemplate(), key, values, duration);
-    }
-
-    /**
-     * 批量设置带过期时间的hash值
-     *
-     * @param template Redis操作模版实例
-     * @param key      键名称
-     * @param values   hash键/值对
-     * @param duration 有效时间
-     * @param <K>      键类型
-     * @param <V>      值类型
-     * @return true/false
-     */
-    public static <K, V> boolean put(@NonNull RedisTemplate<K, ?> template, @NonNull K key, @NonNull Map<K, V> values,
-                                     @NonNull Duration duration) {
-        if (ObjectUtils.isEmpty(values)) {
-            return false;
-        }
-        List<K> keys = Lists.newArrayListWithCapacity(values.size() + 1);
-        keys.add(key);
-        Object[] args = new Object[values.size() + 1];
-        int i = 0;
-        args[i++] = duration.getSeconds();
-        for (Map.Entry<K, ?> entry : values.entrySet()) {
-            keys.add(entry.getKey());
-            args[i++] = entry.getValue();
-        }
-        return Boolean.TRUE.equals(template.execute(HSET_WITH_DURATION_SCRIPT, keys, args));
-    }
-
     /**
      * 数字自增1,如果键不存在则对值初始化
      *

+ 2 - 1
framework-sms/src/main/java/com/chelvc/framework/sms/support/AliyunSmsHandler.java

@@ -74,10 +74,11 @@ public class AliyunSmsHandler implements TemplateSmsHandler {
             throw new RuntimeException(e);
         }
         if (debug) {
-            log.debug("Aliyun sms response: {}, {}", mobile, response);
+            log.debug("Aliyun sms response: {}, {}", mobile, response.toMap());
         }
         SendSmsResponseBody body = response.getBody();
         if (!Objects.equals(body.getCode(), "OK")) {
+            log.warn("Aliyun sms send failed: {}, {}, {}", mobile, body.getCode(), body.getMessage());
             if (Objects.equals(body.getCode(), "isv.DAY_LIMIT_CONTROL")
                     || Objects.equals(body.getCode(), "isv.MONTH_LIMIT_CONTROL")
                     || Objects.equals(body.getCode(), "isv.BUSINESS_LIMIT_CONTROL")) {

+ 1 - 0
framework-sms/src/main/java/com/chelvc/framework/sms/support/TencentSmsHandler.java

@@ -62,6 +62,7 @@ public class TencentSmsHandler implements TemplateSmsHandler {
             log.debug("Tencent sms response: {}, {}", mobile, result);
         }
         if (result.result != 0) {
+            log.warn("Tencent sms send failed: {}, {}, {}", mobile, result.result, result.errMsg);
             if (result.result == 1023 || result.result == 1025) {
                 throw new ResourceUnavailableException(ApplicationContextHolder.getMessage("SMS.Count.Limit"));
             }