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 +++++++++++++++++++++++++++++++++++++---------
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java | 5 +++++
ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java | 9 ++++++++-
3 files changed, 50 insertions(+), 10 deletions(-)
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
index 1258c10..7c9bfcc 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
@@ -37,6 +37,11 @@
public final static String LOGIN_TOKEN_KEY_STUDY = "login_tokens_study:";
/**
+ * 家长端权限缓存前缀
+ */
+ public final static String LOGIN_TOKEN_KEY_PARENT = "login_tokens_parent:";
+
+ /**
* 验证码 redis key
*/
public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
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
diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java
index 6cbbec4..840b3a0 100644
--- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java
+++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java
@@ -61,7 +61,7 @@
String userkey1 = JwtUtils.getUserKey1(claims);
String userkey2 = JwtUtils.getUserKey2(claims);
boolean islogin = redisService.hasKey(getTokenKey(userkey));
- boolean islogin1 = redisService.hasKey(getTokenKey(userkey1));
+ boolean islogin1 = redisService.hasKey(getTokenKeyParent(userkey1));
boolean islogin2 = redisService.hasKey(getTokenKeyStudy(userkey2));
if (!islogin && !islogin1 && !islogin2) {
return unauthorizedResponse(exchange, "登录状态已过期");
@@ -115,6 +115,13 @@
}
/**
+ * 获取家长端缓存key
+ */
+ private String getTokenKeyParent(String token) {
+ return CacheConstants.LOGIN_TOKEN_KEY_PARENT + token;
+ }
+
+ /**
* 获取学习端缓存key
*/
private String getTokenKeyStudy(String token) {
--
Gitblit v1.7.1