From ce764f08a7150d42bff821d73a843bc09d5ee119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90Li?= <15040126243@163.com> Date: Sat, 16 May 2026 14:41:13 +0800 Subject: [PATCH] =?UTF-8?q?refactor(common-mybatis):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90=E4=B8=8E=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DataPermissionIgnoreContext,收口数据权限忽略状态处理 - 优化 DataPermissionHelper,恢复进入 ignore 前的权限忽略状态 - 优化数据权限切点和通知的代理接口匹配逻辑 - 缓存 BaseMapperPlus 泛型解析结果,减少重复反射 - 优化 PageQuery 排序解析,避免修改请求对象状态 - 增强 PageQuery 默认构造和起始行号计算的空值兼容 --- .../mybatis/aspect/DataPermissionAdvice.java | 22 ++++-- .../aspect/DataPermissionPointcut.java | 27 ++++--- .../mybatis/core/mapper/BaseMapperPlus.java | 24 +++++- .../common/mybatis/core/page/PageQuery.java | 15 +++- .../mybatis/helper/DataPermissionHelper.java | 69 ++---------------- .../helper/DataPermissionIgnoreContext.java | 73 +++++++++++++++++++ 6 files changed, 141 insertions(+), 89 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionIgnoreContext.java diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java index 54d5ad41a..50ce4a91b 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java @@ -1,6 +1,5 @@ package org.dromara.common.mybatis.aspect; -import lombok.extern.slf4j.Slf4j; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.dromara.common.mybatis.annotation.DataPermission; @@ -14,16 +13,14 @@ import java.lang.reflect.Proxy; * * @author 秋辞未寒 */ -@Slf4j public class DataPermissionAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object target = invocation.getThis(); Method method = invocation.getMethod(); - Object[] args = invocation.getArguments(); // 设置权限注解 - DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method, args)); + DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method)); try { // 执行代理方法 return invocation.proceed(); @@ -36,7 +33,7 @@ public class DataPermissionAdvice implements MethodInterceptor { /** * 获取数据权限注解 */ - private DataPermission getDataPermissionAnnotation(Object target, Method method,Object[] args){ + private DataPermission getDataPermissionAnnotation(Object target, Method method) { DataPermission dataPermission = method.getAnnotation(DataPermission.class); // 优先获取方法上的注解 if (dataPermission != null) { @@ -46,9 +43,18 @@ public class DataPermissionAdvice implements MethodInterceptor { Class targetClass = target.getClass(); // 如果是 JDK 动态代理,则获取真实的Class实例 if (Proxy.isProxyClass(targetClass)) { - targetClass = targetClass.getInterfaces()[0]; + return getProxyClassDataPermission(targetClass); } - dataPermission = targetClass.getAnnotation(DataPermission.class); - return dataPermission; + return targetClass.getAnnotation(DataPermission.class); + } + + private DataPermission getProxyClassDataPermission(Class targetClass) { + for (Class interfaceClass : targetClass.getInterfaces()) { + DataPermission dataPermission = interfaceClass.getAnnotation(DataPermission.class); + if (dataPermission != null) { + return dataPermission; + } + } + return null; } } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java index 4b7d945a3..43ca05371 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java @@ -1,6 +1,5 @@ package org.dromara.common.mybatis.aspect; -import lombok.extern.slf4j.Slf4j; import org.dromara.common.mybatis.annotation.DataPermission; import org.springframework.aop.support.StaticMethodMatcherPointcut; @@ -12,8 +11,6 @@ import java.lang.reflect.Proxy; * * @author 秋辞未寒 */ -@Slf4j -@SuppressWarnings("all") public class DataPermissionPointcut extends StaticMethodMatcherPointcut { @Override @@ -25,15 +22,23 @@ public class DataPermissionPointcut extends StaticMethodMatcherPointcut { } // MyBatis 的 Mapper 就是通过 JDK 动态代理实现的,所以这里需要检查是否匹配 JDK 的动态代理 - Class targetClassRef = targetClass; - if (Proxy.isProxyClass(targetClassRef)) { - // 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper,而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。 - // 所以这里不能用 targetClass.isAnnotationPresent,只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解 - // 原理:JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass - targetClassRef = targetClass.getInterfaces()[0]; - - } + Class targetClassRef = resolveTargetClass(targetClass); return targetClassRef.isAnnotationPresent(DataPermission.class); } + private Class resolveTargetClass(Class targetClass) { + if (!Proxy.isProxyClass(targetClass)) { + return targetClass; + } + for (Class interfaceClass : targetClass.getInterfaces()) { + // 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper,而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。 + // 所以这里不能用 targetClass.isAnnotationPresent,只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解 + // 原理:JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass + if (interfaceClass.isAnnotationPresent(DataPermission.class)) { + return interfaceClass; + } + } + return targetClass; + } + } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java index 566c63eeb..02b716e49 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java @@ -35,13 +35,20 @@ public interface BaseMapperPlus extends BaseMapper { Log log = LogFactory.getLog(BaseMapperPlus.class); + ClassValue[]> TYPE_ARGUMENT_CACHE = new ClassValue<>() { + @Override + protected Class[] computeValue(Class type) { + return GenericTypeUtils.resolveTypeArguments(type, BaseMapperPlus.class); + } + }; + /** * 获取当前实例对象关联的泛型类型 V 的 Class 对象 * * @return 返回当前实例对象关联的泛型类型 V 的 Class 对象 */ default Class currentVoClass() { - return (Class) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1]; + return (Class) currentMapperTypes()[1]; } /** @@ -50,7 +57,20 @@ public interface BaseMapperPlus extends BaseMapper { * @return 返回当前实例对象关联的泛型类型 T 的 Class 对象 */ default Class currentModelClass() { - return (Class) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[0]; + return (Class) currentMapperTypes()[0]; + } + + /** + * 获取当前 Mapper 的实体与 VO 泛型类型。 + * + * @return 泛型类型数组 + */ + private Class[] currentMapperTypes() { + Class[] types = TYPE_ARGUMENT_CACHE.get(this.getClass()); + if (types == null || types.length < 2) { + throw new IllegalStateException("无法解析 Mapper 泛型类型: " + this.getClass().getName()); + } + return types; } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java index d66021582..62e140668 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import lombok.NoArgsConstructor; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.sql.SqlUtil; @@ -21,6 +22,7 @@ import java.util.List; * @author Lion Li */ @Data +@NoArgsConstructor public class PageQuery implements Serializable { @Serial @@ -84,16 +86,16 @@ public class PageQuery implements Serializable { */ private List buildOrderItem() { if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) { - return null; + return List.of(); } String orderBy = SqlUtil.escapeOrderBySql(orderByColumn); orderBy = StringUtils.toUnderScoreCase(orderBy); // 兼容前端排序类型 - isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"}); + String orderDirection = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"}); String[] orderByArr = orderBy.split(StringUtils.SEPARATOR); - String[] isAscArr = isAsc.split(StringUtils.SEPARATOR); + String[] isAscArr = orderDirection.split(StringUtils.SEPARATOR); if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) { throw new ServiceException("排序参数有误"); } @@ -121,7 +123,12 @@ public class PageQuery implements Serializable { */ @JsonIgnore public Integer getFirstNum() { - return (pageNum - 1) * pageSize; + Integer currentPageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM); + Integer currentPageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE); + if (currentPageNum <= 0) { + currentPageNum = DEFAULT_PAGE_NUM; + } + return (currentPageNum - 1) * currentPageSize; } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java index d31b45b1a..cae799427 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java @@ -2,18 +2,12 @@ package org.dromara.common.mybatis.helper; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.model.SaStorage; -import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; -import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; -import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import org.dromara.common.mybatis.core.domain.DataPermissionAccess; -import org.dromara.common.core.utils.reflect.ReflectUtils; import org.dromara.common.mybatis.annotation.DataPermission; +import org.dromara.common.mybatis.core.domain.DataPermissionAccess; -import java.util.ArrayDeque; -import java.util.Deque; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -31,8 +25,6 @@ public class DataPermissionHelper { private static final String DATA_PERMISSION_KEY = "data:permission"; private static final String ACCESS_KEY = "data:permission:access"; - private static final ThreadLocal> REENTRANT_IGNORE = ThreadLocal.withInitial(ArrayDeque::new); - private static final ThreadLocal PERMISSION_CACHE = new ThreadLocal<>(); /** @@ -120,68 +112,17 @@ public class DataPermissionHelper { throw new IllegalStateException("data permission context type exception"); } - /** - * 获取当前忽略策略。 - * - * @return 忽略策略 - */ - private static IgnoreStrategy getIgnoreStrategy() { - Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL")); - if (ignoreStrategyLocal instanceof ThreadLocal IGNORE_STRATEGY_LOCAL) { - if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) { - return ignoreStrategy; - } - } - return null; - } - - /** - * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭) - */ - private static void enableIgnore() { - IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); - if (ObjectUtil.isNull(ignoreStrategy)) { - InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build()); - } else { - ignoreStrategy.setDataPermission(true); - } - Deque reentrantStack = REENTRANT_IGNORE.get(); - reentrantStack.push(reentrantStack.size() + 1); - } - - /** - * 关闭忽略数据权限 - */ - private static void disableIgnore() { - IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); - if (ObjectUtil.isNotNull(ignoreStrategy)) { - boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName()) - && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack()) - && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql()) - && !Boolean.TRUE.equals(ignoreStrategy.getTenantLine()) - && CollectionUtil.isEmpty(ignoreStrategy.getOthers()); - Deque reentrantStack = REENTRANT_IGNORE.get(); - boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1; - if (noOtherIgnoreStrategy && empty) { - InterceptorIgnoreHelper.clearIgnoreStrategy(); - } else if (empty) { - ignoreStrategy.setDataPermission(false); - } - - } - } - /** * 在忽略数据权限中执行 * * @param handle 处理执行方法 */ public static void ignore(Runnable handle) { - enableIgnore(); + DataPermissionIgnoreContext.enable(); try { handle.run(); } finally { - disableIgnore(); + DataPermissionIgnoreContext.disable(); } } @@ -192,11 +133,11 @@ public class DataPermissionHelper { * @return 执行结果 */ public static T ignore(Supplier handle) { - enableIgnore(); + DataPermissionIgnoreContext.enable(); try { return handle.get(); } finally { - disableIgnore(); + DataPermissionIgnoreContext.disable(); } } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionIgnoreContext.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionIgnoreContext.java new file mode 100644 index 000000000..848d3387a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionIgnoreContext.java @@ -0,0 +1,73 @@ +package org.dromara.common.mybatis.helper; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.common.core.utils.reflect.ReflectUtils; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * 数据权限忽略状态适配器。 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +final class DataPermissionIgnoreContext { + + private static final ThreadLocal> DATA_PERMISSION_STACK = ThreadLocal.withInitial(ArrayDeque::new); + + /** + * 开启忽略数据权限。 + */ + static void enable() { + IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); + DATA_PERMISSION_STACK.get().push(ignoreStrategy != null && Boolean.TRUE.equals(ignoreStrategy.getDataPermission())); + if (ObjectUtil.isNull(ignoreStrategy)) { + InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build()); + } else { + ignoreStrategy.setDataPermission(true); + } + } + + /** + * 关闭忽略数据权限,并恢复进入前的数据权限忽略状态。 + */ + static void disable() { + Deque stack = DATA_PERMISSION_STACK.get(); + boolean previousDataPermission = !stack.isEmpty() && stack.pop(); + IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); + if (ObjectUtil.isNotNull(ignoreStrategy)) { + if (previousDataPermission) { + ignoreStrategy.setDataPermission(true); + } else if (isOnlyDataPermissionIgnored(ignoreStrategy) && stack.isEmpty()) { + InterceptorIgnoreHelper.clearIgnoreStrategy(); + } else { + ignoreStrategy.setDataPermission(false); + } + } + if (stack.isEmpty()) { + DATA_PERMISSION_STACK.remove(); + } + } + + private static IgnoreStrategy getIgnoreStrategy() { + Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL")); + if (ignoreStrategyLocal instanceof ThreadLocal ignoreStrategyThreadLocal + && ignoreStrategyThreadLocal.get() instanceof IgnoreStrategy ignoreStrategy) { + return ignoreStrategy; + } + return null; + } + + private static boolean isOnlyDataPermissionIgnored(IgnoreStrategy ignoreStrategy) { + return !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName()) + && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack()) + && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql()) + && !Boolean.TRUE.equals(ignoreStrategy.getTenantLine()) + && CollectionUtil.isEmpty(ignoreStrategy.getOthers()); + } + +}