mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2026-06-27 08:04:26 +00:00
refactor(common-satoken): 收敛登录助手、权限解析与 DAO 缓存逻辑
- LoginHelper 改成更稳的空值/未登录处理,isLogin() 直接走 StpUtil.isLogin() - SaPermissionImpl 把菜单/角色权限提取收敛成一套逻辑 - PlusSaTokenDao 抽了公共写入、读取、TTL 转换逻辑,并修了 searchData 的本地缓存键 - SaTokenExceptionHandler 合并了角色/权限异常处理
This commit is contained in:
+66
-40
@@ -37,8 +37,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
*/
|
||||
@Override
|
||||
public String get(String key) {
|
||||
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
||||
return (String) o;
|
||||
return getCacheValue(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,16 +45,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
*/
|
||||
@Override
|
||||
public void set(String key, String value, long timeout) {
|
||||
if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
// 判断是否为永不过期
|
||||
if (timeout == NEVER_EXPIRE) {
|
||||
RedisUtils.setCacheObject(key, value);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
||||
}
|
||||
CAFFEINE.invalidate(key);
|
||||
writeValue(key, value, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +55,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
public void update(String key, String value) {
|
||||
if (RedisUtils.hasKey(key)) {
|
||||
RedisUtils.setCacheObject(key, value, true);
|
||||
CAFFEINE.invalidate(key);
|
||||
invalidate(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +65,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
@Override
|
||||
public void delete(String key) {
|
||||
if (RedisUtils.deleteObject(key)) {
|
||||
CAFFEINE.invalidate(key);
|
||||
invalidate(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +74,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
*/
|
||||
@Override
|
||||
public long getTimeout(String key) {
|
||||
long timeout = RedisUtils.getTimeToLive(key);
|
||||
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
|
||||
return timeout < 0 ? timeout : timeout / 1000 + 1;
|
||||
return toTimeoutSeconds(RedisUtils.getTimeToLive(key));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,8 +91,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
*/
|
||||
@Override
|
||||
public Object getObject(String key) {
|
||||
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
||||
return o;
|
||||
return getCacheValue(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,11 +100,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
* @param key 键名称
|
||||
* @return object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getObject(String key, Class<T> classType) {
|
||||
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
||||
return (T) o;
|
||||
return classType.cast(getCacheValue(key));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,16 +110,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
*/
|
||||
@Override
|
||||
public void setObject(String key, Object object, long timeout) {
|
||||
if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
// 判断是否为永不过期
|
||||
if (timeout == NEVER_EXPIRE) {
|
||||
RedisUtils.setCacheObject(key, object);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
|
||||
}
|
||||
CAFFEINE.invalidate(key);
|
||||
writeValue(key, object, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,7 +120,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
public void updateObject(String key, Object object) {
|
||||
if (RedisUtils.hasKey(key)) {
|
||||
RedisUtils.setCacheObject(key, object, true);
|
||||
CAFFEINE.invalidate(key);
|
||||
invalidate(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +130,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
@Override
|
||||
public void deleteObject(String key) {
|
||||
if (RedisUtils.deleteObject(key)) {
|
||||
CAFFEINE.invalidate(key);
|
||||
invalidate(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,9 +139,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
*/
|
||||
@Override
|
||||
public long getObjectTimeout(String key) {
|
||||
long timeout = RedisUtils.getTimeToLive(key);
|
||||
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
|
||||
return timeout < 0 ? timeout : timeout / 1000 + 1;
|
||||
return toTimeoutSeconds(RedisUtils.getTimeToLive(key));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,11 +156,63 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
|
||||
String keyStr = prefix + "*" + keyword + "*";
|
||||
return (List<String>) CAFFEINE.get(keyStr, k -> {
|
||||
Collection<String> keys = RedisUtils.keys(keyStr);
|
||||
String pattern = prefix + "*" + keyword + "*";
|
||||
String cacheKey = pattern + start + ":" + size + ":" + sortType;
|
||||
return (List<String>) CAFFEINE.get(cacheKey, k -> {
|
||||
Collection<String> keys = RedisUtils.keys(pattern);
|
||||
List<String> list = new ArrayList<>(keys);
|
||||
return SaFoxUtil.searchList(list, start, size, sortType);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存读取对象。
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @return 缓存值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getCacheValue(String key) {
|
||||
return (T) CAFFEINE.get(key, RedisUtils::getCacheObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存值并刷新本地缓存。
|
||||
*
|
||||
* @param key 缓存键
|
||||
* @param value 缓存值
|
||||
* @param timeout 超时时间
|
||||
*/
|
||||
private void writeValue(String key, Object value, long timeout) {
|
||||
if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
if (timeout == NEVER_EXPIRE) {
|
||||
RedisUtils.setCacheObject(key, value);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
||||
}
|
||||
invalidate(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除本地缓存。
|
||||
*
|
||||
* @param key 缓存键
|
||||
*/
|
||||
private void invalidate(String key) {
|
||||
CAFFEINE.invalidate(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Redis TTL 转为秒。
|
||||
*
|
||||
* @param timeoutRedis Redis TTL 毫秒值
|
||||
* @return Sa-Token 需要的秒值
|
||||
*/
|
||||
private long toTimeoutSeconds(long timeoutRedis) {
|
||||
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
|
||||
return timeoutRedis < 0 ? timeoutRedis : timeoutRedis / 1000 + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+38
-35
@@ -3,16 +3,16 @@ package org.dromara.common.satoken.core.service;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.dromara.common.core.enums.UserType;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.service.PermissionService;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.system.api.model.LoginUser;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* sa-token 权限管理实现类
|
||||
@@ -30,26 +30,7 @@ public class SaPermissionImpl implements StpInterface {
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
|
||||
PermissionService permissionService = getPermissionService();
|
||||
if (ObjectUtil.isNotNull(permissionService)) {
|
||||
List<String> list = StringUtils.splitList(loginId.toString(), ":");
|
||||
return new ArrayList<>(permissionService.getMenuPermission(Long.parseLong(list.get(1))));
|
||||
} else {
|
||||
throw new ServiceException("PermissionService 实现类不存在");
|
||||
}
|
||||
}
|
||||
UserType userType = UserType.getUserType(loginUser.getUserType());
|
||||
if (userType == UserType.APP_USER) {
|
||||
// 其他端 自行根据业务编写
|
||||
}
|
||||
if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) {
|
||||
// SYS_USER 默认返回权限
|
||||
return new ArrayList<>(loginUser.getMenuPermission());
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return resolvePermissionList(loginId, LoginUser::getMenuPermission, PermissionService::getMenuPermission);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,26 +42,33 @@ public class SaPermissionImpl implements StpInterface {
|
||||
*/
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
return resolvePermissionList(loginId, LoginUser::getRolePermission, PermissionService::getRolePermission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前登录对象的权限列表。
|
||||
*
|
||||
* @param loginId 登录ID
|
||||
* @param localPermissionExtractor 当前登录用户权限提取器
|
||||
* @param remotePermissionExtractor 远程权限提取器
|
||||
* @return 权限列表
|
||||
*/
|
||||
private List<String> resolvePermissionList(Object loginId,
|
||||
java.util.function.Function<LoginUser, Collection<String>> localPermissionExtractor,
|
||||
BiFunction<PermissionService, Long, Collection<String>> remotePermissionExtractor) {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
|
||||
PermissionService permissionService = getPermissionService();
|
||||
if (ObjectUtil.isNotNull(permissionService)) {
|
||||
List<String> list = StringUtils.splitList(loginId.toString(), ":");
|
||||
return new ArrayList<>(permissionService.getRolePermission(Long.parseLong(list.get(1))));
|
||||
} else {
|
||||
throw new ServiceException("PermissionService 实现类不存在");
|
||||
return new ArrayList<>(remotePermissionExtractor.apply(permissionService, resolveUserId(loginId)));
|
||||
}
|
||||
throw new ServiceException("PermissionService 实现类不存在");
|
||||
}
|
||||
UserType userType = UserType.getUserType(loginUser.getUserType());
|
||||
if (userType == UserType.APP_USER) {
|
||||
// 其他端 自行根据业务编写
|
||||
}
|
||||
if (CollUtil.isNotEmpty(loginUser.getRolePermission())) {
|
||||
// SYS_USER 默认返回权限
|
||||
return new ArrayList<>(loginUser.getRolePermission());
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
Collection<String> permissionList = localPermissionExtractor.apply(loginUser);
|
||||
if (CollUtil.isNotEmpty(permissionList)) {
|
||||
return new ArrayList<>(permissionList);
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,4 +84,19 @@ public class SaPermissionImpl implements StpInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从登录ID中提取用户ID。
|
||||
*
|
||||
* @param loginId 登录ID
|
||||
* @return 用户ID
|
||||
*/
|
||||
private Long resolveUserId(Object loginId) {
|
||||
String loginIdStr = loginId.toString();
|
||||
int separatorIndex = loginIdStr.indexOf(':');
|
||||
if (separatorIndex < 0 || separatorIndex == loginIdStr.length() - 1) {
|
||||
throw new ServiceException("登录ID格式错误");
|
||||
}
|
||||
return Long.parseLong(loginIdStr.substring(separatorIndex + 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-17
@@ -26,24 +26,11 @@ public class SaTokenExceptionHandler {
|
||||
* @param request 当前请求
|
||||
* @return 统一失败响应
|
||||
*/
|
||||
@ExceptionHandler(NotPermissionException.class)
|
||||
public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
|
||||
@ExceptionHandler({NotPermissionException.class, NotRoleException.class})
|
||||
public R<Void> handleNotAccessException(RuntimeException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理角色权限校验失败异常。
|
||||
*
|
||||
* @param e 异常信息
|
||||
* @param request 当前请求
|
||||
* @return 统一失败响应
|
||||
*/
|
||||
@ExceptionHandler(NotRoleException.class)
|
||||
public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
|
||||
String reason = e instanceof NotRoleException ? "角色权限校验失败" : "权限码校验失败";
|
||||
log.error("请求地址'{}',{}'{}'", requestURI, reason, e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
|
||||
+25
-12
@@ -3,6 +3,7 @@ package org.dromara.common.satoken.utils;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
@@ -98,13 +99,12 @@ public class LoginHelper {
|
||||
*
|
||||
* @return 登录用户
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends LoginUser> T getLoginUser() {
|
||||
SaSession session = StpUtil.getTokenSession();
|
||||
if (ObjectUtil.isNull(session)) {
|
||||
try {
|
||||
return getLoginUser(StpUtil.getTokenSession());
|
||||
} catch (NotLoginException e) {
|
||||
return null;
|
||||
}
|
||||
return (T) session.get(LOGIN_USER_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,9 +113,27 @@ public class LoginHelper {
|
||||
* @param token Token
|
||||
* @return 登录用户
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends LoginUser> T getLoginUser(String token) {
|
||||
SaSession session = StpUtil.getTokenSessionByToken(token);
|
||||
if (StringUtils.isBlank(token)) {
|
||||
return null;
|
||||
}
|
||||
SaSession session;
|
||||
try {
|
||||
session = StpUtil.getTokenSessionByToken(token);
|
||||
} catch (NotLoginException e) {
|
||||
return null;
|
||||
}
|
||||
return getLoginUser(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从会话中读取登录用户。
|
||||
*
|
||||
* @param session 登录会话
|
||||
* @return 登录用户
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends LoginUser> T getLoginUser(SaSession session) {
|
||||
if (ObjectUtil.isNull(session)) {
|
||||
return null;
|
||||
}
|
||||
@@ -225,12 +243,7 @@ public class LoginHelper {
|
||||
* @return 是否已登录
|
||||
*/
|
||||
public static boolean isLogin() {
|
||||
try {
|
||||
StpUtil.checkLogin();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return StpUtil.isLogin();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user