From 3e9e81913bd8bde5b2ccde2c95acc7c5d94b32ba Mon Sep 17 00:00:00 2001
From: hjl <1657978663@qq.com>
Date: 星期五, 24 五月 2024 14:38:58 +0800
Subject: [PATCH] feat: 登录校验规则

---
 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/StudyLoginException.java     |   46 +++++++++++
 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java |    9 ++
 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthLogic.java                 |    7 +
 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java           |   53 +++++++++---
 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java                |   10 +
 ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java                        |   36 +++++++-
 ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java                 |    2 
 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/interceptor/HeaderInterceptor.java  |    7 -
 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthUtil.java                  |    9 ++
 ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserParent.java                       |   14 ++-
 10 files changed, 157 insertions(+), 36 deletions(-)

diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserParent.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserParent.java
index da08de9..a5c6616 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserParent.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserParent.java
@@ -1,10 +1,8 @@
 package com.ruoyi.system.api.model;
 
-import com.ruoyi.system.api.domain.SysUser;
 import lombok.Data;
 
 import java.io.Serializable;
-import java.util.Set;
 
 /**
  * 用户信息 家长端 学习端使用
@@ -12,8 +10,7 @@
  * @author ruoyi
  */
 @Data
-public class LoginUserParent implements Serializable
-{
+public class LoginUserParent implements Serializable {
     private static final long serialVersionUID = 1L;
 
     /**
@@ -49,4 +46,13 @@
      */
     private String ipaddr;
 
+    /**
+     * 是否可以登录
+     * 该字段用于学生端 - 互斥登录
+     */
+    private Boolean isCanLogin;
+
+    public LoginUserParent() {
+        this.isCanLogin = Boolean.TRUE;
+    }
 }
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 1d2510e..2592c62 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
@@ -2,11 +2,10 @@
 
 /**
  * 缓存常量信息
- * 
+ *
  * @author ruoyi
  */
-public class CacheConstants
-{
+public class CacheConstants {
     /**
      * 缓存有效期,默认720(分钟)
      */
@@ -33,6 +32,11 @@
     public final static String LOGIN_TOKEN_KEY = "login_tokens:";
 
     /**
+     * 学习端权限缓存前缀
+     */
+    public final static String LOGIN_TOKEN_KEY_STUDY = "login_tokens_study:";
+
+    /**
      * 验证码 redis key
      */
     public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/StudyLoginException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/StudyLoginException.java
new file mode 100644
index 0000000..a8770dd
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/StudyLoginException.java
@@ -0,0 +1,46 @@
+package com.ruoyi.common.core.exception.user;
+
+/**
+ * @author HJL
+ * @version 1.0
+ * @since 2024-05-24 11:35
+ */
+public class StudyLoginException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误码
+     */
+    private int code;
+
+    public StudyLoginException() {
+    }
+
+    public StudyLoginException(String message, Integer code) {
+        this.message = message;
+        this.code = code;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java
index 2b3e9a9..ba4459b 100644
--- a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java
@@ -242,6 +242,6 @@
     }
 
     public Set getKeysPrefix(String accessToken) {
-        return redisTemplate.keys(accessToken);
+        return redisTemplate.keys(accessToken + "*");
     }
 }
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthLogic.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthLogic.java
index 542b9ef..a2d7e31 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthLogic.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthLogic.java
@@ -64,6 +64,13 @@
     }
 
     /**
+     * 学习端会话注销,根据指定Token
+     */
+    public void logoutByTokenStudy(String token) {
+        tokenService.delLoginUserStudy(token);
+    }
+
+    /**
      * 检验用户是否已经登录,如未登录,则抛出异常
      */
     public void checkLogin() {
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthUtil.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthUtil.java
index 47f0533..ef7a949 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthUtil.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/auth/AuthUtil.java
@@ -42,6 +42,15 @@
     }
 
     /**
+     * 学习端会话注销,根据指定Token
+     *
+     * @param token 指定token
+     */
+    public static void logoutByTokenStudy(String token) {
+        authLogic.logoutByTokenStudy(token);
+    }
+
+    /**
      * 检验当前会话是否已经登录,如未登录,则抛出异常
      */
     public static void checkLogin() {
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
index 1e5aa4f..fa88c03 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
@@ -8,6 +8,7 @@
 import com.ruoyi.common.core.exception.ServiceException;
 import com.ruoyi.common.core.exception.auth.NotPermissionException;
 import com.ruoyi.common.core.exception.auth.NotRoleException;
+import com.ruoyi.common.core.exception.user.StudyLoginException;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import org.slf4j.Logger;
@@ -148,4 +149,12 @@
         return R.fail(e.getMessage());
     }
 
+    /**
+     * 学生端单点登录-异常信息
+     */
+    @ExceptionHandler(StudyLoginException.class)
+    public R<String> studyLoginExceptionHandler(StudyLoginException e) {
+        return R.fail(e.getCode(), e.getMessage());
+    }
+
 }
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/interceptor/HeaderInterceptor.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/interceptor/HeaderInterceptor.java
index b4d032b..e97a3b6 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/interceptor/HeaderInterceptor.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/interceptor/HeaderInterceptor.java
@@ -43,13 +43,6 @@
                 AuthUtil.verifyLoginUserExpire1(loginUser1);
                 SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
             }
-//            LoginUserParent loginUserStudy = AuthUtil.getLoginUserStudy(token);
-//            if (StringUtils.isNotNull(loginUserStudy)) {
-//                AuthUtil.verifyLoginUserStudyExpire(loginUserStudy);
-//                SecurityContextHolder.set(SecurityConstants.USER_STUDY_KEY, loginUserStudy);
-//            } else {
-//                return false;
-//            }
         }
         return true;
     }
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 38eed13..31fb6ac 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
@@ -3,7 +3,7 @@
 import com.alibaba.fastjson2.JSONObject;
 import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.constant.SecurityConstants;
-import com.ruoyi.common.core.exception.GlobalException;
+import com.ruoyi.common.core.exception.user.StudyLoginException;
 import com.ruoyi.common.core.utils.JwtUtils;
 import com.ruoyi.common.core.utils.ServletUtils;
 import com.ruoyi.common.core.utils.StringUtils;
@@ -39,6 +39,8 @@
     private final static long expireTime = CacheConstants.EXPIRATION;
 
     private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;
+
+    private final static String ACCESS_TOKEN_STUDY = CacheConstants.LOGIN_TOKEN_KEY_STUDY;
 
     private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;
 
@@ -143,7 +145,7 @@
     public LoginUserParent getLoginUserStudy() {
         LoginUserParent userStudy = getLoginUserStudy(ServletUtils.getRequest());
         if (null == userStudy) {
-            throw new GlobalException("登录失效,请重新登录!");
+            throw new StudyLoginException("登录失效,请重新登录!", 505);
         }
         return userStudy;
     }
@@ -214,14 +216,18 @@
      */
     public LoginUserParent getLoginUserStudy(String token) {
         LoginUserParent user = null;
-        try {
-            if (StringUtils.isNotEmpty(token)) {
-                String userkey = JwtUtils.getUserKeyStudy(token);
-                user = redisService.getCacheObject(getTokenKey(userkey));
-                return user;
+        if (StringUtils.isNotEmpty(token)) {
+            String userkey = JwtUtils.getUserKeyStudy(token);
+            user = redisService.getCacheObject(getTokenKeyStudy(userkey));
+            // 优先判断当前账号是否已在其他设备登录
+            if (!user.getIsCanLogin()) {
+                throw new StudyLoginException("当前登录账号在其他设备登录!", 505);
             }
-        } catch (Exception e) {
-            e.printStackTrace();
+            // 再次判断登录状态是否已过期
+            if (System.currentTimeMillis() > user.getExpireTime()) {
+                throw new StudyLoginException("登录信息已过期,请重新登录!", 504);
+            }
+            return user;
         }
         return user;
     }
@@ -252,6 +258,16 @@
         if (StringUtils.isNotEmpty(token)) {
             String userkey = JwtUtils.getUserKey1(token);
             redisService.deleteObject(getTokenKey(userkey));
+        }
+    }
+
+    /**
+     * 学习端删除用户缓存信息
+     */
+    public void delLoginUserStudy(String token) {
+        if (StringUtils.isNotEmpty(token)) {
+            String userkey = JwtUtils.getUserKeyStudy(token);
+            redisService.deleteObject(getTokenKeyStudy(userkey));
         }
     }
 
@@ -320,27 +336,34 @@
      * 学习端用户登录
      */
     public void refreshTokenStudy(LoginUserParent dto) {
-        // 获取所有 login_tokens: 前缀的登录缓存
-        Set redisCache = redisService.getKeysPrefix(ACCESS_TOKEN + "*");
+        // 获取所有 login_tokens_study: 前缀的登录缓存
+        Set redisCache = redisService.getKeysPrefix(ACCESS_TOKEN_STUDY);
         for (Object key : redisCache) {
             String strKey = String.valueOf(key);
             // 根据 login_tokens:加密token 获取用户登录信息
             Object redisCacheUserInfo = redisService.getCacheObject(strKey);
             LoginUserParent redisUserInfo = JSONObject.parseObject(JSONObject.toJSONString(redisCacheUserInfo), LoginUserParent.class);
-            // 单点逻辑
-            if (dto.getPhone().equals(redisUserInfo.getPhone())) {
-                redisService.deleteObject(strKey);
+            // 单点逻辑,如果当前用户已处于登录状态并再次登录,则清除该用户上一次登录token
+            if (dto.getUserid().equals(redisUserInfo.getUserid())) {
+                // 设置能否登录字段为 否,当该token登录时,isCanLogin为false表示账号被挤
+                redisUserInfo.setIsCanLogin(Boolean.FALSE);
+                redisService.setCacheObject(strKey, redisUserInfo);
             }
         }
         // 单点登录逻辑
         dto.setLoginTime(System.currentTimeMillis());
         dto.setExpireTime(dto.getLoginTime() + expireTime * MILLIS_MINUTE);
         // 根据uuid将loginUser缓存
-        String userKey = getTokenKey(dto.getToken());
+        String userKey = getTokenKeyStudy(dto.getToken());
         redisService.setCacheObject(userKey, dto, expireTime, TimeUnit.MINUTES);
     }
 
     private String getTokenKey(String token) {
         return ACCESS_TOKEN + token;
     }
+
+    private String getTokenKeyStudy(String token) {
+        return ACCESS_TOKEN_STUDY + token;
+    }
+
 }
\ No newline at end of file
diff --git a/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java b/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java
index 4d134b1..7548bb6 100644
--- a/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java
+++ b/ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java
@@ -385,7 +385,7 @@
             @ApiImplicitParam(value = "手机号", name = "phone", dataType = "string", required = true),
             @ApiImplicitParam(value = "验证码", name = "phoneCode", dataType = "string", required = true)
     })
-    public R<Map<String,Object>> login(String phone, String phoneCode) {
+    public R<Map<String, Object>> login(String phone, String phoneCode) {
         TUser tUser1 = userService.getOne(new QueryWrapper<TUser>()
                 .ne("state", 3)
                 .eq("phone", phone));
@@ -428,7 +428,7 @@
      */
     @PostMapping("/studyLogin")
     @ApiOperation(value = "学习端-登录", tags = {"学习端-登录"})
-    public R<Map<String,Object>> studyLogin(@RequestBody @Validated RegisterPhoneRequest phoneRequest) {
+    public R<Map<String, Object>> studyLogin(@RequestBody @Validated RegisterPhoneRequest phoneRequest) {
         String phone = phoneRequest.getPhone();
         String phoneCode = phoneRequest.getPhoneCode();
         TUser user = userService.getOne(new QueryWrapper<TUser>()
@@ -465,6 +465,28 @@
         map.put("token", tokenService.createTokenStudy(loginUserParent));
         // 获取登录token
         return R.ok(map);
+    }
+
+    /**
+     * 学生端退出登录
+     * @param request 请求信息
+     */
+    @PostMapping("/logoutStudy")
+    @ApiOperation(value = "退出登录", tags = {"学习端-个人中心"})
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "Authorization", value = "Bearer eyJhbGciOiJIUzUxMiJ....", required = true, paramType = "header"),
+    })
+    public R<String> logoutStudy(HttpServletRequest request) {
+        if (tokenService.getLoginUserStudy() == null) {
+            throw new GlobalException("登录失效!");
+        }
+        String token = SecurityUtils.getToken(request);
+        if (null != token) {
+            // 删除用户缓存记录
+            AuthUtil.logoutByTokenStudy(token);
+        }
+        // todo 清除token
+        return R.ok("退出登录成功!");
     }
 
     private TUser getUser(String phone) {
@@ -527,7 +549,7 @@
     @GetMapping("/userInfo")
     @ApiOperation(value = "用户详情", tags = {"用户详情"})
     public R<TUser> userInfo() {
-        return R.ok(userService.lambdaQuery().eq(TUser::getId,tokenService.getLoginUserStudy().getUserid()).one());
+        return R.ok(userService.lambdaQuery().eq(TUser::getId, tokenService.getLoginUserStudy().getUserid()).one());
     }
 
     @PostMapping("/deleteUser")
@@ -737,12 +759,14 @@
         res.setTotal(list.size());
         return R.ok(res);
     }
+
     @Resource
     private ITUserStudyService userStudyService;
     @Resource
     private ITGameRecordService gameRecordService;
     @Autowired
     private ITStudyService studyService;
+
     @PostMapping("/getUserInfo")
     @ApiOperation(value = "查看用户详情", tags = {"管理后台-用户管理"})
     public R<UserInfoVO> getUserInfo(@RequestBody UserInfoQuery dto) {
@@ -779,16 +803,16 @@
         // 游戏测试成绩
         List<TGameRecord> gameRecordList = gameRecordService.lambdaQuery().eq(TGameRecord::getUserId, dto.getId())
                 .eq(TGameRecord::getDisabled, 0).list();
-        if (studyRecord!=null){
+        if (studyRecord != null) {
             res.setCurrent(studyRecord.getWeek());
             // 查询当前听的总周目
             List<TStudy> list1 = studyService.list(new QueryWrapper<>());
-            res.setSurplus(list1.size()-studyRecord.getWeek());
+            res.setSurplus(list1.size() - studyRecord.getWeek());
             res.setTotalHours(studyRecord.getTotalStudy().doubleValue());
             res.setTodayHours(studyRecord.getTodayStudy().doubleValue());
             res.setWeekHours(studyRecord.getWeekStudy().doubleValue());
             res.setMonthHours(studyRecord.getMonthStudy().doubleValue());
-        }else{
+        } else {
             res.setCurrent(0);
             res.setSurplus(0);
             res.setTotalHours(0.0);

--
Gitblit v1.7.1