This commit is contained in:
Vinjor 2025-11-27 16:20:48 +08:00
commit 6c1623254d
6 changed files with 72 additions and 7 deletions

View File

@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@ -38,6 +40,9 @@ public class SysUserOnlineController extends BaseController
@Autowired @Autowired
private RedisCache redisCache; private RedisCache redisCache;
@Autowired
private TokenService tokenService;
@PreAuthorize("@ss.hasPermi('monitor:online:list')") @PreAuthorize("@ss.hasPermi('monitor:online:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(String ipaddr, String userName) public TableDataInfo list(String ipaddr, String userName)
@ -77,7 +82,7 @@ public class SysUserOnlineController extends BaseController
@DeleteMapping("/{tokenId}") @DeleteMapping("/{tokenId}")
public AjaxResult forceLogout(@PathVariable String tokenId) public AjaxResult forceLogout(@PathVariable String tokenId)
{ {
redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); tokenService.forceLogout(tokenId);
return success(); return success();
} }
} }

View File

@ -156,6 +156,8 @@ aliyun:
# 登录相关配置 # 登录相关配置
login: login:
# 登录缓存
cache-enable: true
# 是否限制单用户登录 # 是否限制单用户登录
single: true single: true

View File

@ -12,6 +12,11 @@ public class CacheConstants
*/ */
public static final String LOGIN_TOKEN_KEY = "login_tokens:"; public static final String LOGIN_TOKEN_KEY = "login_tokens:";
/**
* 登录用户id redis key 用于实现灵活控制多设备登录
*/
public static final String LOGIN_USER_ID_KEY = "login_user_ids:";
/** /**
* 验证码 redis key * 验证码 redis key
*/ */

View File

@ -44,7 +44,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{ {
String userName = loginUser.getUsername(); String userName = loginUser.getUsername();
// 删除用户缓存记录 // 删除用户缓存记录
tokenService.delLoginUser(loginUser.getToken()); tokenService.delLoginUser(loginUser.getToken(), loginUser.getUserId());
// 记录用户退出日志 // 记录用户退出日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
} }

View File

@ -45,6 +45,9 @@ public class TokenService
@Value("${token.expireTime}") @Value("${token.expireTime}")
private int expireTime; private int expireTime;
@Value("${login.single}")
private boolean isSingle;
protected static final long MILLIS_SECOND = 1000; protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
@ -96,15 +99,36 @@ public class TokenService
/** /**
* 删除用户身份信息 * 删除用户身份信息
*/ */
public void delLoginUser(String token) public void delLoginUser(String token, Long userId)
{ {
if (StringUtils.isNotEmpty(token)) if (StringUtils.isNotEmpty(token)) {
{
String userKey = getTokenKey(token); String userKey = getTokenKey(token);
redisCache.deleteObject(userKey); redisCache.deleteObject(userKey);
} }
if (userId != null) {
String userIdKey = getUserKey(userId);
redisCache.deleteObject(userIdKey);
}
} }
/**
* 强制退出
*
* @param tokenId token(uuid)
*/
public void forceLogout(String tokenId){
String userKey = getTokenKey(tokenId);
if(isSingle){//多设备登录相关控制代码
LoginUser loginUser = redisCache.getCacheObject(userKey);
Long userId = loginUser.getUserId();
String userIdKey = getUserKey(userId);
redisCache.deleteObject(userIdKey);
}
redisCache.deleteObject(userKey);
}
/** /**
* 创建令牌 * 创建令牌
* *
@ -147,15 +171,44 @@ public class TokenService
*/ */
public void refreshToken(LoginUser loginUser) public void refreshToken(LoginUser loginUser)
{ {
//如果是单点登录则退出当前用户
if (isSingle) {
String userIdKey = getUserKey(loginUser.getUserId());
LoginUser user = redisCache.getCacheObject(userIdKey);
if (user != null){
String userKey = getTokenKey(user.getToken());
if (StringUtils.isNotEmpty(userKey)) {
redisCache.deleteObject(userIdKey);
redisCache.deleteObject(userKey);
}
}
}
loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setLoginTime(System.currentTimeMillis());
//todo 令牌有效期
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存 // 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken()); String userKey = getTokenKey(loginUser.getToken());
// redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); // redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
/* vinjor 永不过期,适配小程序*/ /* vinjor 永不过期,适配小程序*/
redisCache.setCacheObject(userKey, loginUser); redisCache.setCacheObject(userKey, loginUser);
//如果是单点登录则记录用户idkey对应的用户信息key
if (isSingle){
String userIdKey = getUserKey(loginUser.getUserId());
redisCache.setCacheObject(userIdKey, loginUser);
}
} }
/**
* 获取用户Key
*
* @param userId 当前登录用户的主键id
* @return
*/
private String getUserKey(Long userId) {
return CacheConstants.LOGIN_USER_ID_KEY + userId;
}
/** /**
* 设置用户代理信息 * 设置用户代理信息
* *

View File

@ -36,7 +36,7 @@ router.beforeEach((to, from, next) => {
watermark.remove() watermark.remove()
// 设置新水印 // 设置新水印
const currentUser = store.getters.name || '未知用户' const currentUser = store.getters.nickName || '未知用户'
const currentDate = new Date().toLocaleDateString() const currentDate = new Date().toLocaleDateString()
watermark.set({ watermark.set({
text: `${currentUser}\n${currentDate}`, text: `${currentUser}\n${currentDate}`,
@ -63,7 +63,7 @@ router.beforeEach((to, from, next) => {
watermark.remove() watermark.remove()
// 设置新水印 // 设置新水印
const currentUser = store.getters.name || '未知用户' const currentUser = store.getters.nickName || '未知用户'
const currentDate = new Date().toLocaleDateString() const currentDate = new Date().toLocaleDateString()
watermark.set({ watermark.set({
text: `${currentUser}\n${currentDate}`, text: `${currentUser}\n${currentDate}`,