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 new file mode 100644 index 000000000..54d5ad41a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAdvice.java @@ -0,0 +1,54 @@ +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; +import org.dromara.common.mybatis.helper.DataPermissionHelper; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * 数据权限注解Advice + * + * @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)); + try { + // 执行代理方法 + return invocation.proceed(); + } finally { + // 清除权限注解 + DataPermissionHelper.removePermission(); + } + } + + /** + * 获取数据权限注解 + */ + private DataPermission getDataPermissionAnnotation(Object target, Method method,Object[] args){ + DataPermission dataPermission = method.getAnnotation(DataPermission.class); + // 优先获取方法上的注解 + if (dataPermission != null) { + return dataPermission; + } + // 方法上没有注解,则获取类上的注解 + Class targetClass = target.getClass(); + // 如果是 JDK 动态代理,则获取真实的Class实例 + if (Proxy.isProxyClass(targetClass)) { + targetClass = targetClass.getInterfaces()[0]; + } + dataPermission = targetClass.getAnnotation(DataPermission.class); + return dataPermission; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java deleted file mode 100644 index 1c83cc392..000000000 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.dromara.common.mybatis.aspect; - -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.AfterThrowing; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.dromara.common.mybatis.annotation.DataPermission; -import org.dromara.common.mybatis.helper.DataPermissionHelper; - -/** - * 数据权限处理 - * - * @author Lion Li - */ -@Slf4j -@Aspect -public class DataPermissionAspect { - - /** - * 处理请求前执行 - */ - @Before(value = "@annotation(dataPermission)") - public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) { - DataPermissionHelper.setPermission(dataPermission); - } - - /** - * 处理完请求后执行 - * - * @param joinPoint 切点 - */ - @AfterReturning(pointcut = "@annotation(dataPermission)") - public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) { - DataPermissionHelper.removePermission(); - } - - /** - * 拦截异常操作 - * - * @param joinPoint 切点 - * @param e 异常 - */ - @AfterThrowing(value = "@annotation(dataPermission)", throwing = "e") - public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) { - DataPermissionHelper.removePermission(); - } - -} 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 new file mode 100644 index 000000000..4b7d945a3 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcut.java @@ -0,0 +1,39 @@ +package org.dromara.common.mybatis.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.springframework.aop.support.StaticMethodMatcherPointcut; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * 数据权限匹配切点 + * + * @author 秋辞未寒 + */ +@Slf4j +@SuppressWarnings("all") +public class DataPermissionPointcut extends StaticMethodMatcherPointcut { + + @Override + public boolean matches(Method method, Class targetClass) { + // 优先匹配方法 + // 数据权限注解不对继承生效,所以检查当前方法是否有注解即可,不再往上匹配父类或接口 + if (method.isAnnotationPresent(DataPermission.class)) { + return true; + } + + // 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]; + + } + return targetClassRef.isAnnotationPresent(DataPermission.class); + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java new file mode 100644 index 000000000..351288ce4 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionPointcutAdvisor.java @@ -0,0 +1,33 @@ +package org.dromara.common.mybatis.aspect; + +import org.aopalliance.aop.Advice; +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; + +/** + * 数据权限注解切面定义 + * + * @author 秋辞未寒 + */ +@SuppressWarnings("all") +public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor { + + private final Advice advice; + private final Pointcut pointcut; + + public DataPermissionPointcutAdvisor() { + this.advice = new DataPermissionAdvice(); + this.pointcut = new DataPermissionPointcut(); + } + + @Override + public Pointcut getPointcut() { + return this.pointcut; + } + + @Override + public Advice getAdvice() { + return this.advice; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java index 18d8b75df..c11ca8ea5 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java @@ -11,7 +11,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import org.dromara.common.core.factory.YmlPropertySourceFactory; import org.dromara.common.core.utils.SpringUtils; -import org.dromara.common.mybatis.aspect.DataPermissionAspect; +import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor; import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; import org.dromara.common.mybatis.handler.MybatisExceptionHandler; import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler; @@ -19,9 +19,11 @@ import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor; import org.dromara.common.mybatis.service.SysDataScopeService; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; +import org.springframework.context.annotation.Role; import org.springframework.transaction.annotation.EnableTransactionManagement; /** @@ -30,6 +32,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * @author Lion Li */ @AutoConfiguration +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @EnableTransactionManagement(proxyTargetClass = true) @MapperScan("${mybatis-plus.mapperPackage}") @PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class) @@ -57,17 +60,17 @@ public class MybatisPlusConfiguration { * 数据权限拦截器 */ public PlusDataPermissionInterceptor dataPermissionInterceptor() { - return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage")); + return new PlusDataPermissionInterceptor(); } /** * 数据权限切面处理器 */ @Bean - public DataPermissionAspect dataPermissionAspect() { - return new DataPermissionAspect(); + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() { + return new DataPermissionPointcutAdvisor(); } - /** * 分页插件,自动识别数据库类型 */ diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java index 0acd10a5d..29a70d092 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java @@ -1,6 +1,5 @@ package org.dromara.common.mybatis.handler; -import cn.hutool.core.annotation.AnnotationUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import lombok.AllArgsConstructor; @@ -10,7 +9,6 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import net.sf.jsqlparser.parser.CCJSqlParserUtil; -import org.apache.ibatis.io.Resources; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StreamUtils; @@ -22,22 +20,13 @@ import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.system.api.model.LoginUser; import org.dromara.system.api.model.RoleDTO; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.core.type.ClassMetadata; -import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.expression.*; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.util.ClassUtils; -import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; /** @@ -49,11 +38,6 @@ import java.util.function.Function; @Slf4j public class PlusDataPermissionHandler { - /** - * 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行) - */ - private final Map dataPermissionCacheMap = new ConcurrentHashMap<>(); - /** * spel 解析器 */ @@ -64,27 +48,17 @@ public class PlusDataPermissionHandler { */ private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory()); - /** - * 构造方法,扫描指定包下的 Mapper 类并初始化缓存 - * - * @param mapperPackage Mapper 类所在的包路径 - */ - public PlusDataPermissionHandler(String mapperPackage) { - scanMapperClasses(mapperPackage); - } - /** * 获取数据过滤条件的 SQL 片段 * * @param where 原始的查询条件表达式 - * @param mappedStatementId Mapper 方法的 ID * @param isSelect 是否为查询语句 * @return 数据过滤条件的 SQL 片段 */ - public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { + public Expression getSqlSegment(Expression where, boolean isSelect) { try { // 获取数据权限配置 - DataPermission dataPermission = getDataPermission(mappedStatementId); + DataPermission dataPermission = getDataPermission(); // 获取当前登录用户信息 LoginUser currentUser = DataPermissionHelper.getVariable("user"); if (ObjectUtil.isNull(currentUser)) { @@ -206,92 +180,22 @@ public class PlusDataPermissionHandler { return StringUtils.EMPTY; } - /** - * 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类 - * - * @param mapperPackage Mapper 类所在的包路径 - */ - private void scanMapperClasses(String mapperPackage) { - // 创建资源解析器和元数据读取工厂 - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); - // 将 Mapper 包路径按分隔符拆分为数组 - String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); - String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; - try { - for (String packagePattern : packagePatternArray) { - // 将包路径转换为资源路径 - String path = ClassUtils.convertClassNameToResourcePath(packagePattern); - // 获取指定路径下的所有 .class 文件资源 - Resource[] resources = resolver.getResources(classpath + path + "/*.class"); - for (Resource resource : resources) { - // 获取资源的类元数据 - ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata(); - // 获取资源对应的类对象 - Class clazz = Resources.classForName(classMetadata.getClassName()); - // 查找类中的特定注解 - findAnnotation(clazz); - } - } - } catch (Exception e) { - log.error("初始化数据安全缓存时出错:{}", e.getMessage()); - } - } - - /** - * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中 - * - * @param clazz 要查找的类 - */ - private void findAnnotation(Class clazz) { - DataPermission dataPermission; - for (Method method : clazz.getMethods()) { - if (method.isDefault() || method.isVarArgs()) { - continue; - } - String mappedStatementId = clazz.getName() + "." + method.getName(); - if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) { - dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class); - dataPermissionCacheMap.put(mappedStatementId, dataPermission); - } - } - if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) { - dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class); - dataPermissionCacheMap.put(clazz.getName(), dataPermission); - } - } - /** * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象 * - * @param mapperId 映射语句 ID * @return DataPermission 注解对象,如果不存在则返回 null */ - public DataPermission getDataPermission(String mapperId) { - // 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象 - if (DataPermissionHelper.getPermission() != null) { - return DataPermissionHelper.getPermission(); - } - // 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象 - if (dataPermissionCacheMap.containsKey(mapperId)) { - return dataPermissionCacheMap.get(mapperId); - } - // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找 - String clazzName = mapperId.substring(0, mapperId.lastIndexOf(".")); - if (dataPermissionCacheMap.containsKey(clazzName)) { - return dataPermissionCacheMap.get(clazzName); - } - return null; + public DataPermission getDataPermission() { + return DataPermissionHelper.getPermission(); } /** * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象 * - * @param mapperId 映射语句 ID * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true */ - public boolean invalid(String mapperId) { - return getDataPermission(mapperId) == null; + public boolean invalid() { + return getDataPermission() == null; } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java index 85a4d0abc..b37d96ed1 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java @@ -35,16 +35,7 @@ import java.util.List; @Slf4j public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor { - private final PlusDataPermissionHandler dataPermissionHandler; - - /** - * 构造函数,初始化 PlusDataPermissionHandler 实例 - * - * @param mapperPackage 扫描的映射器包 - */ - public PlusDataPermissionInterceptor(String mapperPackage) { - this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage); - } + private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler(); /** * 在执行查询之前,检查并处理数据权限相关逻辑 @@ -64,7 +55,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto return; } // 检查是否缺少有效的数据权限注解 - if (dataPermissionHandler.invalid(ms.getId())) { + if (dataPermissionHandler.invalid()) { return; } // 解析 sql 分配对应方法 @@ -92,7 +83,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto return; } // 检查是否缺少有效的数据权限注解 - if (dataPermissionHandler.invalid(ms.getId())) { + if (dataPermissionHandler.invalid()) { return; } PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); @@ -128,7 +119,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto */ @Override protected void processUpdate(Update update, int index, String sql, Object obj) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false); + Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), false); if (null != sqlSegment) { update.setWhere(sqlSegment); } @@ -144,7 +135,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto */ @Override protected void processDelete(Delete delete, int index, String sql, Object obj) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false); + Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), false); if (null != sqlSegment) { delete.setWhere(sqlSegment); } @@ -157,7 +148,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto * @param mappedStatementId 映射语句的 ID */ protected void setWhere(PlainSelect plainSelect, String mappedStatementId) { - Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true); + Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), true); if (null != sqlSegment) { plainSelect.setWhere(sqlSegment); }