ソースを参照

登录态改用字符串读写,Redis 中存纯 JSON

- RedisUtils 新增 getString / setString / deleteString(底层 StringRedisTemplate)
- RedisTokenStore 读取、写入、删除改用上述方法,不再走 Jackson 序列化的 redisTemplate
- 修复落库变成双层编码的问题:此前存的是 "{\"user\":...}",需二次 parse,体积约大 30%
- 现在 redis-cli 直接 GET 即为标准 JSON,中文不被转义
- test_redis_auth.py 保留双层编码兼容解析,注释同步更新

验证:13/13 端到端通过;改资料刷新 TTL 300→299 保留剩余有效期;落库 json.loads 直接得到 dict
tanlie 6 時間 前
親
コミット
c1c12c63ec

+ 3 - 3
tools/test_redis_auth.py

@@ -113,9 +113,9 @@ def main():
         check("TTL 为 7 天(604800,允许 5 秒误差)", isinstance(ttl, int) and 604795 <= ttl <= 604800, "ttl=%s" % ttl)
         try:
             val = json.loads(raw) if raw else {}
-            # RedisConfig 的 value 序列化器是 Jackson2JsonRedisSerializer,
-            # 写入 String 时会再包一层 JSON 编码(存储为 "{\"user\":...}"),
-            # 这里解两层,兼容纯 JSON 与双层编码两种存法。
+            # 现版本用 StringRedisTemplate 写入,落库即纯 JSON(json.loads 直接得到 dict)。
+            # 兼容早期用 RedisTemplate(Jackson) 写入的双层编码(存成 "{\"user\":...}"),
+            # 若解出来还是字符串就再解一层。
             if isinstance(val, str):
                 val = json.loads(val)
         except Exception:

+ 9 - 7
yoshop-java/src/main/java/cn/yoshop/common/auth/RedisTokenStore.java

@@ -21,6 +21,9 @@ import java.util.Map;
  *   <li>有效期默认 7 天(对齐原 PHP 端 {@code Cache::set($token, $value, 86400 * 7)})</li>
  * </ul>
  *
+ * <p>读写走 {@link RedisUtils} 的 string 专用方法(底层 {@code StringRedisTemplate}),
+ * 落库就是纯 JSON 文本,不会被 Jackson 再编码成带引号的字符串,便于 redis-cli 直接查看。
+ *
  * <p>字段名沿用 PHP 的 snake_case({@code store_id} / {@code is_login}),
  * 这样 {@link LoginInfo#fromCacheValue(Map)} 可直接复用,无需再写一套解析。
  */
@@ -54,12 +57,11 @@ public class RedisTokenStore {
             return null;
         }
         try {
-            // RedisUtils.get 返回 Object,键不存在时为 null,必须先判空再 toString
-            Object cached = redis.get(key(token));
-            if (cached == null) {
+            // 用 string 专用读取,避免 Jackson 序列化把 JSON 文本再包一层引号
+            String json = redis.getString(key(token));
+            if (json == null) {
                 return null;
             }
-            String json = cached.toString();
             Map<String, Object> value = objectMapper.readValue(
                     json, new TypeReference<LinkedHashMap<String, Object>>() {
                     });
@@ -92,9 +94,9 @@ public class RedisTokenStore {
             throw new IllegalStateException("登录态序列化失败: " + e.getMessage(), e);
         }
         if (ttlSeconds > 0) {
-            redis.set(key(token), json, ttlSeconds);
+            redis.setString(key(token), json, ttlSeconds);
         } else {
-            redis.set(key(token), json);
+            redis.setString(key(token), json);
         }
     }
 
@@ -104,7 +106,7 @@ public class RedisTokenStore {
             return;
         }
         try {
-            redis.delete(key(token));
+            redis.deleteString(key(token));
         } catch (Exception e) {
             log.warn("清除登录态失败 token={} : {}", token, e.getMessage());
         }

+ 40 - 0
yoshop-java/src/main/java/cn/yoshop/utils/RedisUtils.java

@@ -2,6 +2,7 @@ package cn.yoshop.utils;
 
 import jakarta.annotation.Resource;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
@@ -23,11 +24,50 @@ public class RedisUtils {
     @Resource
     private RedisTemplate<String, Object> redisTemplate;
 
+    /**
+     * 纯字符串读写用这个:value 不做 JSON 二次编码,存进去是什么就是什么。
+     * 与 redisTemplate(Jackson 序列化)区分开,避免 JSON 文本被包成带引号的字符串。
+     */
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
     /**
      * 分布式锁过期时间
      */
     private final long EXPIRE_TIME = 5 * 10000;
 
+    /**
+     * 读取字符串值。key 不存在返回 null。
+     */
+    public String getString(String key) {
+        return stringRedisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 写入字符串值,不设过期时间。
+     */
+    public void setString(String key, String value) {
+        stringRedisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 写入字符串值并设置过期时间(秒)。expire <= 0 时不设过期。
+     */
+    public void setString(String key, String value, long expire) {
+        if (expire > 0) {
+            stringRedisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
+        } else {
+            stringRedisTemplate.opsForValue().set(key, value);
+        }
+    }
+
+    /**
+     * 删除 key。与 value 的序列化方式无关,两种 template 都能删。
+     */
+    public Boolean deleteString(String key) {
+        return stringRedisTemplate.delete(key);
+    }
+
     public Object get(String key) {
         return redisTemplate.opsForValue().get(key);
     }