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:"; 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; } } 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) { ruoyi-service/ruoyi-management/pom.xml
@@ -27,7 +27,7 @@ <dependency> <groupId>com.huawei.apigateway</groupId> <artifactId>java-sdk-core</artifactId> <version>3.2.4</version> <version>3.2.5</version> </dependency> <dependency> <groupId>com.huaweicloud</groupId> ruoyi-service/ruoyi-parent/src/main/resources/bootstrap.yml
@@ -3,7 +3,7 @@ spring: application: # 应用名称 name: ruoyi-management name: ruoyi-parent main: allow-bean-definition-overriding: true profiles: ruoyi-service/ruoyi-study/pom.xml
@@ -66,7 +66,7 @@ <dependency> <groupId>com.huawei.apigateway</groupId> <artifactId>java-sdk-core</artifactId> <version>3.2.4</version> <version>3.2.5</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> @@ -82,6 +82,12 @@ <artifactId>alipay-sdk-java</artifactId> <version>4.8.10.ALL</version> </dependency> <!-- ALiSendSms 直接使用阿里云 SDK,需要显式声明依赖。 --> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.6.0</version> </dependency> <dependency> <groupId>com.ruoyi</groupId> <artifactId>ruoyi-api-system</artifactId> ruoyi-service/ruoyi-study/src/main/java/com/ruoyi/study/controller/TUserController.java
@@ -79,6 +79,10 @@ @Autowired private ITIntegralRecordService integralRecordService; /** 家长端新用户注册赠送的会员天数 */ private static final int GIFT_VIP_DAYS = 7; @PostMapping("/getUserById/{id}") @ApiOperation(value = "根据用户id 获取用户信息", tags = {"管理后台-用户管理"}) public R<TUser> getUserById(@PathVariable("id") Integer id) { @@ -859,21 +863,13 @@ if (!rCode.equalsIgnoreCase(phoneCode)) { return R.errorCode("登录失败,手机验证码无效!"); } else { tUser1 = getUser(phone); tUser1.setHeadImg("https://haitunyingyu.obs.cn-southwest-2.myhuaweicloud.com/admin/1c6ee218aa18434db2e86df903990de8.jpg"); String upperCase = UUIDUtil.getRandomCode(6).toUpperCase(); tUser1.setAccount("用户"+upperCase); tUser1.setName("用户"+upperCase); userService.save(tUser1); // 新用户:创建账号并赠送 7 天会员 tUser1 = registerParentUserWithGift(phone); } } } else { String upperCase = UUIDUtil.getRandomCode(6).toUpperCase(); tUser1 = getUser(phone); tUser1.setHeadImg("https://haitunyingyu.obs.cn-southwest-2.myhuaweicloud.com/admin/1c6ee218aa18434db2e86df903990de8.jpg"); tUser1.setAccount("用户"+upperCase); tUser1.setName("用户"+upperCase); userService.save(tUser1); // 新用户:创建账号并赠送 7 天会员 tUser1 = registerParentUserWithGift(phone); } } LoginUserParent loginUserParent = new LoginUserParent(); @@ -998,6 +994,33 @@ } /** * 家长端新用户注册:创建账号并赠送 7 天会员 * * @param phone 手机号 * @return 新建的用户 */ private TUser registerParentUserWithGift(String phone) throws Exception { TUser user = getUser(phone); user.setHeadImg("https://haitunyingyu.obs.cn-southwest-2.myhuaweicloud.com/admin/1c6ee218aa18434db2e86df903990de8.jpg"); String upperCase = UUIDUtil.getRandomCode(6).toUpperCase(); user.setAccount("用户" + upperCase); user.setName("用户" + upperCase); // 新用户福利:赠送 7 天会员,到期时间为 7 天后的 23:59:59 Date now = new Date(); user.setVipPayTime(now); Calendar calendar = Calendar.getInstance(); calendar.setTime(now); calendar.add(Calendar.DATE, GIFT_VIP_DAYS); Date vipEndTime = calendar.getTime(); vipEndTime.setHours(23); vipEndTime.setMinutes(59); vipEndTime.setSeconds(59); user.setVipEndTime(vipEndTime); userService.save(user); return user; } /** * 家长端 学习端都可用 * * @param phone 手机号