[重大更新] update (实验性功能慎更)重构数据权限实现逻辑 支持任意mapper方法标注注解 无需再找真实mapper标注
已添加1个文件
已修改5个文件
253 ■■■■ 文件已修改
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java 146 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/aspect/DataPermissionAspect.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
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();
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java
@@ -10,6 +10,7 @@
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.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
@@ -51,7 +52,15 @@
     * æ•°æ®æƒé™æ‹¦æˆªå™¨
     */
    public PlusDataPermissionInterceptor dataPermissionInterceptor() {
        return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
        return new PlusDataPermissionInterceptor();
    }
    /**
     * æ•°æ®æƒé™åˆ‡é¢å¤„理器
     */
    @Bean
    public DataPermissionAspect dataPermissionAspect() {
        return new DataPermissionAspect();
    }
    /**
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.extern.slf4j.Slf4j;
@@ -9,7 +8,6 @@
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.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
@@ -21,27 +19,17 @@
import org.dromara.common.mybatis.enums.DataScopeType;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.satoken.utils.LoginHelper;
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.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
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.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
@@ -54,11 +42,6 @@
public class PlusDataPermissionHandler {
    /**
     * æ–¹æ³•或类(名称) ä¸Ž æ³¨è§£çš„æ˜ å°„关系缓存
     */
    private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
    /**
     * spel è§£æžå™¨
     */
    private final ExpressionParser parser = new SpelExpressionParser();
@@ -69,15 +52,6 @@
    private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
    /**
     * æž„造方法,扫描指定包下的 Mapper ç±»å¹¶åˆå§‹åŒ–缓存
     *
     * @param mapperPackage Mapper ç±»æ‰€åœ¨çš„包路径
     */
    public PlusDataPermissionHandler(String mapperPackage) {
        scanMapperClasses(mapperPackage);
    }
    /**
     * èŽ·å–æ•°æ®è¿‡æ»¤æ¡ä»¶çš„ SQL ç‰‡æ®µ
     *
     * @param where             åŽŸå§‹çš„æŸ¥è¯¢æ¡ä»¶è¡¨è¾¾å¼
@@ -86,24 +60,24 @@
     * @return æ•°æ®è¿‡æ»¤æ¡ä»¶çš„ SQL ç‰‡æ®µ
     */
    public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
        // èŽ·å–æ•°æ®æƒé™é…ç½®
        DataPermission dataPermission = getDataPermission(mappedStatementId);
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ä¿¡æ¯
        LoginUser currentUser = DataPermissionHelper.getVariable("user");
        if (ObjectUtil.isNull(currentUser)) {
            currentUser = LoginHelper.getLoginUser();
            DataPermissionHelper.setVariable("user", currentUser);
        }
        // å¦‚果是超级管理员或租户管理员,则不过滤数据
        if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
            return where;
        }
        // æž„造数据过滤条件的 SQL ç‰‡æ®µ
        String dataFilterSql = buildDataFilter(dataPermission, isSelect);
        if (StringUtils.isBlank(dataFilterSql)) {
            return where;
        }
        try {
            // èŽ·å–æ•°æ®æƒé™é…ç½®
            DataPermission dataPermission = DataPermissionHelper.getPermission();
            // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ä¿¡æ¯
            LoginUser currentUser = DataPermissionHelper.getVariable("user");
            if (ObjectUtil.isNull(currentUser)) {
                currentUser = LoginHelper.getLoginUser();
                DataPermissionHelper.setVariable("user", currentUser);
            }
            // å¦‚果是超级管理员或租户管理员,则不过滤数据
            if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
                return where;
            }
            // æž„造数据过滤条件的 SQL ç‰‡æ®µ
            String dataFilterSql = buildDataFilter(dataPermission, isSelect);
            if (StringUtils.isBlank(dataFilterSql)) {
                return where;
            }
            Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
            // æ•°æ®æƒé™ä½¿ç”¨å•独的括号 é˜²æ­¢ä¸Žå…¶ä»–条件冲突
            ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression);
@@ -114,6 +88,8 @@
            }
        } catch (JSQLParserException e) {
            throw new ServiceException("数据权限解析异常 => " + e.getMessage());
        } finally {
            DataPermissionHelper.removePermission();
        }
    }
@@ -170,8 +146,11 @@
                    context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
                }
                // å¿½ç•¥æ•°æ®æƒé™ é˜²æ­¢spel表达式内有其他sql查询导致死循环调用
                String sql = DataPermissionHelper.ignore(() ->
                    parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class)
                );
                // è§£æžsql模板并填充
                String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);
                conditions.add(joinStr + sql);
                isSuccess = true;
            }
@@ -189,86 +168,11 @@
    }
    /**
     * æ‰«ææŒ‡å®šåŒ…下的 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 (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;
    }
    /**
     * æ£€æŸ¥ç»™å®šçš„æ˜ å°„语句 ID æ˜¯å¦æœ‰æ•ˆï¼Œå³æ˜¯å¦èƒ½å¤Ÿæ‰¾åˆ°å¯¹åº”çš„ DataPermission æ³¨è§£å¯¹è±¡
     *
     * @param mapperId æ˜ å°„语句 ID
     * @return å¦‚果找到对应的 DataPermission æ³¨è§£å¯¹è±¡ï¼Œåˆ™è¿”回 false;否则返回 true
     */
    public boolean invalid(String mapperId) {
        return getDataPermission(mapperId) == null;
    public boolean invalid() {
        return DataPermissionHelper.getPermission() == null;
    }
}
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
@@ -9,6 +9,7 @@
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mybatis.annotation.DataPermission;
import java.util.HashMap;
import java.util.Map;
@@ -29,6 +30,33 @@
    private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
    private static final ThreadLocal<DataPermission> PERMISSION_CACHE = new ThreadLocal<>();
    /**
     * èŽ·å–å½“å‰æ‰§è¡Œmapper权限注解
     *
     * @return è¿”回当前执行mapper权限注解
     */
    public static DataPermission getPermission() {
        return PERMISSION_CACHE.get();
    }
    /**
     * è®¾ç½®å½“前执行mapper权限注解
     *
     * @param dataPermission   æ•°æ®æƒé™æ³¨è§£
     */
    public static void setPermission(DataPermission dataPermission) {
        PERMISSION_CACHE.set(dataPermission);
    }
    /**
     * åˆ é™¤å½“前执行mapper权限注解
     */
    public static void removePermission() {
        PERMISSION_CACHE.remove();
    }
    /**
     * ä»Žä¸Šä¸‹æ–‡ä¸­èŽ·å–æŒ‡å®šé”®çš„å˜é‡å€¼ï¼Œå¹¶å°†å…¶è½¬æ¢ä¸ºæŒ‡å®šçš„ç±»åž‹
     *
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
@@ -39,11 +39,9 @@
    /**
     * æž„造函数,初始化 PlusDataPermissionHandler å®žä¾‹
     *
     * @param mapperPackage æ‰«æçš„æ˜ å°„器包
     */
    public PlusDataPermissionInterceptor(String mapperPackage) {
        this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
    public PlusDataPermissionInterceptor() {
        this.dataPermissionHandler = new PlusDataPermissionHandler();
    }
    /**
@@ -64,7 +62,7 @@
            return;
        }
        // æ£€æŸ¥æ˜¯å¦ç¼ºå°‘有效的数据权限注解
        if (dataPermissionHandler.invalid(ms.getId())) {
        if (dataPermissionHandler.invalid()) {
            return;
        }
        // è§£æž sql åˆ†é…å¯¹åº”方法
@@ -92,7 +90,7 @@
                return;
            }
            // æ£€æŸ¥æ˜¯å¦ç¼ºå°‘有效的数据权限注解
            if (dataPermissionHandler.invalid(ms.getId())) {
            if (dataPermissionHandler.invalid()) {
                return;
            }
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/mapper/TestDemoMapper.java
@@ -34,14 +34,18 @@
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    List<TestDemo> selectList(IPage<TestDemo> page, @Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
    default <P extends IPage<TestDemoVo>> P selectVoPage(IPage<TestDemo> page, Wrapper<TestDemo> wrapper) {
        return selectVoPage(page, wrapper, this.currentVoClass());
    }
    @Override
    @DataPermission({
        @DataColumn(key = "deptName", value = "dept_id"),
        @DataColumn(key = "userName", value = "user_id")
    })
    List<TestDemo> selectList(@Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
    default List<TestDemoVo> selectVoList(Wrapper<TestDemo> wrapper) {
        return selectVoList(wrapper, this.currentVoClass());
    }
    @Override
    @DataPermission(value = {