From 98e64ec683ef80e03886cb73e0734797794cf444 Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期一, 28 九月 2026 15:03:42 +0800
Subject: [PATCH] feat(auth): add 家长端登录认证及token缓存支持

---
 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java |   46 +++++++++++++++++++++++++++++++++++++---------
 1 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java
index 661ab20..7164923 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java
@@ -42,6 +42,8 @@
 
     private final static String ACCESS_TOKEN_STUDY = CacheConstants.LOGIN_TOKEN_KEY_STUDY;
 
+    private final static String ACCESS_TOKEN_PARENT = CacheConstants.LOGIN_TOKEN_KEY_PARENT;
+
     private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;
 
     /**
@@ -197,14 +199,18 @@
      */
     public LoginUserParent getLoginUser1(String token) {
         LoginUserParent user = null;
-        try {
-            if (StringUtils.isNotEmpty(token)) {
+        if (StringUtils.isNotEmpty(token)) {
+            try {
                 String userkey = JwtUtils.getUserKey1(token);
-                user = redisService.getCacheObject(getTokenKey(userkey));
-                return user;
+                user = redisService.getCacheObject(getTokenKeyParent(userkey));
+            } catch (Exception e) {
+                e.printStackTrace();
             }
-        } catch (Exception e) {
-            e.printStackTrace();
+            // 单设备登录校验:账号已在其他设备登录时,将先登录的设备强制下线
+            // 注意:此处不能放在 try 中,否则会被 catch(Exception) 吞掉异常
+            if (user != null && !Boolean.TRUE.equals(user.getIsCanLogin())) {
+                throw new StudyLoginException("当前登录账号在其他设备登录!", 505);
+            }
         }
         return user;
     }
@@ -261,7 +267,7 @@
     public void delLoginUser1(String token) {
         if (StringUtils.isNotEmpty(token)) {
             String userkey = JwtUtils.getUserKey1(token);
-            redisService.deleteObject(getTokenKey(userkey));
+            redisService.deleteObject(getTokenKeyParent(userkey));
         }
     }
 
@@ -326,13 +332,31 @@
     }
 
     /**
-     * 家长端用户登录
+     * 家长端用户登录(单设备登录:新设备登录后,将先登录的设备挤下线)
      */
     public void refreshToken1(LoginUserParent dto) {
+        // 获取所有 login_tokens_parent: 前缀的登录缓存
+        Set redisCache = redisService.getKeysPrefix(ACCESS_TOKEN_PARENT);
+        for (Object key : redisCache) {
+            String strKey = String.valueOf(key);
+            Object redisCacheUserInfo = redisService.getCacheObject(strKey);
+            LoginUserParent redisUserInfo = JSONObject.parseObject(JSONObject.toJSONString(redisCacheUserInfo), LoginUserParent.class);
+            // 单点逻辑:同一账号已在其他设备登录,则把先登录的 token 置为不可登录
+            if (dto.getUserid().equals(redisUserInfo.getUserid())) {
+                // 已被挤下线的 token 直接跳过,避免重复写缓存
+                if (!Boolean.TRUE.equals(redisUserInfo.getIsCanLogin())) {
+                    continue;
+                }
+                // isCanLogin 为 false 表示该 token 已被其他设备挤下线
+                redisUserInfo.setIsCanLogin(Boolean.FALSE);
+                // 使用原有过期时间续写,避免踢下线反而延长了旧 token 的生命周期
+                redisService.setCacheObject(strKey, redisUserInfo, redisService.getExpire(strKey), TimeUnit.SECONDS);
+            }
+        }
         dto.setLoginTime(System.currentTimeMillis());
         dto.setExpireTime(dto.getLoginTime() + expireTime * MILLIS_MINUTE);
         // 根据uuid将loginUser缓存
-        String userKey = getTokenKey(dto.getToken());
+        String userKey = getTokenKeyParent(dto.getToken());
         redisService.setCacheObject(userKey, dto, expireTime, TimeUnit.MINUTES);
     }
 
@@ -374,4 +398,8 @@
         return ACCESS_TOKEN_STUDY + token;
     }
 
+    private String getTokenKeyParent(String token) {
+        return ACCESS_TOKEN_PARENT + token;
+    }
+
 }
\ No newline at end of file

--
Gitblit v1.7.1