diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java index 09f82324e..ea19371be 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java @@ -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 getObject(String key, Class 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 searchData(String prefix, String keyword, int start, int size, boolean sortType) { - String keyStr = prefix + "*" + keyword + "*"; - return (List) CAFFEINE.get(keyStr, k -> { - Collection keys = RedisUtils.keys(keyStr); + String pattern = prefix + "*" + keyword + "*"; + String cacheKey = pattern + start + ":" + size + ":" + sortType; + return (List) CAFFEINE.get(cacheKey, k -> { + Collection keys = RedisUtils.keys(pattern); List list = new ArrayList<>(keys); return SaFoxUtil.searchList(list, start, size, sortType); }); } + + /** + * 从缓存读取对象。 + * + * @param key 缓存键 + * @return 缓存值 + */ + @SuppressWarnings("unchecked") + private 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; + } + } diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java index 7b94b9c33..8423792ec 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java @@ -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 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 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 getRoleList(Object loginId, String loginType) { + return resolvePermissionList(loginId, LoginUser::getRolePermission, PermissionService::getRolePermission); + } + + /** + * 解析当前登录对象的权限列表。 + * + * @param loginId 登录ID + * @param localPermissionExtractor 当前登录用户权限提取器 + * @param remotePermissionExtractor 远程权限提取器 + * @return 权限列表 + */ + private List resolvePermissionList(Object loginId, + java.util.function.Function> localPermissionExtractor, + BiFunction> remotePermissionExtractor) { LoginUser loginUser = LoginHelper.getLoginUser(); if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) { PermissionService permissionService = getPermissionService(); if (ObjectUtil.isNotNull(permissionService)) { - List 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 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)); + } + } diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java index 7710f9457..0a5ce1f51 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/handler/SaTokenExceptionHandler.java @@ -26,24 +26,11 @@ public class SaTokenExceptionHandler { * @param request 当前请求 * @return 统一失败响应 */ - @ExceptionHandler(NotPermissionException.class) - public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) { + @ExceptionHandler({NotPermissionException.class, NotRoleException.class}) + public R 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 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, "没有访问权限,请联系管理员授权"); } diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java index 94df3b319..47b8be926 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java @@ -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 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 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 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(); } }