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