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; } } 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:"; ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/StudyLoginException.java
New file @@ -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; } } 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 + "*"); } } 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() { 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() { 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()); } } 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; } 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; } } 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);