RuoYi-Vue-Plus 阅读笔记 – 6 – MyBatis Plus 数据权限插件
本文最后更新于 229 天前,如有失效请评论区留言。

权限拦截器

DataPermissionInterceptor 是 MyBatis Plus 的一个数据权限插件,支持动态拦截执行 SQl 然后拼接权限部分 SQL片段

怎么使用呢? 拦截器 PlusDataPermissionInterceptor,这里在查询、修改、删除的操作时做了拦截处理,核心的方式实现了 dataPermissionHandler

public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
    @Override  
    protected void processSelect(Select select, int index, String sql, Object obj) {  
        SelectBody selectBody = select.getSelectBody();  
        if (selectBody instanceof PlainSelect plainSelect) {  
            this.setWhere(plainSelect, (String) obj);  
        } else if (selectBody instanceof SetOperationList setOperationList) {  
            List<SelectBody> selectBodyList = setOperationList.getSelects();  
            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));  
        }  
    }  

    @Override  
    protected void processUpdate(Update update, int index, String sql, Object obj) {  
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);  
        if (null != sqlSegment) {  
            update.setWhere(sqlSegment);  
        }  
    }  

    @Override  
    protected void processDelete(Delete delete, int index, String sql, Object obj) {  
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);  
        if (null != sqlSegment) {  
            delete.setWhere(sqlSegment);  
        }  
    }  

    /**  
     * 设置 where 条件  
     *  
     * @param plainSelect       查询对象  
     * @param mappedStatementId 执行方法id  
     */protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {  
        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);  
        if (null != sqlSegment) {  
            plainSelect.setWhere(sqlSegment);  
        }  
    }
}

PlusDataPermissionHandler 权限实现类

public class PlusDataPermissionHandler {

    /**  
     * 方法或类(名称) 与 注解的映射关系缓存  
     */  
    private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();  

    /**  
     * spel 解析器  
     */  
    private final ExpressionParser parser = new SpelExpressionParser();  
    private final ParserContext parserContext = new TemplateParserContext();  
    /**  
     * bean解析器 用于处理 spel 表达式中对 bean 的调用  
     */  
    private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());  

    public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {  
        DataColumn[] dataColumns = findAnnotation(mappedStatementId);  
        LoginUser currentUser = DataPermissionHelper.getVariable("user");  
        if (ObjectUtil.isNull(currentUser)) {  
            currentUser = LoginHelper.getLoginUser();  
            DataPermissionHelper.setVariable("user", currentUser);  
        }  
        // 如果是超级管理员或租户管理员,则不过滤数据  
        if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {  
            return where;  
        }  
        String dataFilterSql = buildDataFilter(dataColumns, isSelect);  
        if (StringUtils.isBlank(dataFilterSql)) {  
            return where;  
        }  
        try {  
            Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);  
            // 数据权限使用单独的括号 防止与其他条件冲突  
            Parenthesis parenthesis = new Parenthesis(expression);  
            if (ObjectUtil.isNotNull(where)) {  
                return new AndExpression(where, parenthesis);  
            } else {  
                return parenthesis;  
            }  
        } catch (JSQLParserException e) {  
            throw new ServiceException("数据权限解析异常 => " + e.getMessage());  
        }  
    }  

    /**  
     * 构造数据过滤sql  
     */private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) {  
        // 更新或删除需满足所有条件  
        String joinStr = isSelect ? " OR " : " AND ";  
        LoginUser user = DataPermissionHelper.getVariable("user");  
        StandardEvaluationContext context = new StandardEvaluationContext();  
        context.setBeanResolver(beanResolver);  
        DataPermissionHelper.getContext().forEach(context::setVariable);  
        Set<String> conditions = new HashSet<>();  
        for (RoleDTO role : user.getRoles()) {  
            user.setRoleId(role.getRoleId());  
            // 获取角色权限泛型  
            DataScopeType type = DataScopeType.findCode(role.getDataScope());  
            if (ObjectUtil.isNull(type)) {  
                throw new ServiceException("角色数据范围异常 => " + role.getDataScope());  
            }  
            // 全部数据权限直接返回  
            if (type == DataScopeType.ALL) {  
                return "";  
            }  
            boolean isSuccess = false;  
            for (DataColumn dataColumn : dataColumns) {  
                if (dataColumn.key().length != dataColumn.value().length) {  
                    throw new ServiceException("角色数据范围异常 => key与value长度不匹配");  
                }  
                // 不包含 key 变量 则不处理  
                if (!StringUtils.containsAny(type.getSqlTemplate(),  
                    Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new)  
                )) {  
                    continue;  
                }  
                // 设置注解变量 key 为表达式变量 value 为变量值  
                for (int i = 0; i < dataColumn.key().length; i++) {  
                    context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);  
                }  

                // 解析sql模板并填充  
                String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);  
                conditions.add(joinStr + sql);  
                isSuccess = true;  
            }  
            // 未处理成功则填充兜底方案  
            if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) {  
                conditions.add(joinStr + type.getElseSql());  
            }  
        }  

        if (CollUtil.isNotEmpty(conditions)) {  
            String sql = StreamUtils.join(conditions, Function.identity(), "");  
            return sql.substring(joinStr.length());  
        }  
        return "";  
    }  

    public DataColumn[] findAnnotation(String mappedStatementId) {  
        StringBuilder sb = new StringBuilder(mappedStatementId);  
        int index = sb.lastIndexOf(".");  
        String clazzName = sb.substring(0, index);  
        String methodName = sb.substring(index + 1, sb.length());  
        Class<?> clazz;  
        try {  
            clazz = ClassUtil.loadClass(clazzName);  
        } catch (Exception e) {  
            return null;  
        }  
        List<Method> methods = Arrays.stream(ClassUtil.getDeclaredMethods(clazz))  
            .filter(method -> method.getName().equals(methodName)).toList();  
        DataPermission dataPermission;  
        // 获取方法注解  
        for (Method method : methods) {  
            dataPermission = dataPermissionCacheMap.get(mappedStatementId);  
            if (ObjectUtil.isNotNull(dataPermission)) {  
                return dataPermission.value();  
            }  
            if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {  
                dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);  
                dataPermissionCacheMap.put(mappedStatementId, dataPermission);  
                return dataPermission.value();  
            }  
        }  
        dataPermission = dataPermissionCacheMap.get(clazz.getName());  
        if (ObjectUtil.isNotNull(dataPermission)) {  
            return dataPermission.value();  
        }  
        // 获取类注解  
        if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {  
            dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);  
            dataPermissionCacheMap.put(clazz.getName(), dataPermission);  
            return dataPermission.value();  
        }  
        return null;  
    }
}

@DataPermission 权限注解

SysUserMapper.java

/**
 * 根据条件分页查询用户列表
 *
 * @param queryWrapper 查询条件
 * @return 用户信息集合信息
 */
@DataPermission({
    @DataColumn(key = "deptName", value = "d.dept_id"),
    @DataColumn(key = "userName", value = "u.user_id")
})
List<SysUserVo> selectUserList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);

/**
 * 通过用户ID查询用户
 *
 * @param userId 用户ID
 * @return 用户对象信息
 */
@DataPermission({
    @DataColumn(key = "deptName", value = "d.dept_id"),
    @DataColumn(key = "userName", value = "u.user_id")
})
SysUserVo selectUserById(Long userId);

1、当进行 SQL 操作时,将@DataPermission 注解的方法的class 对象用 dataPermissionCacheMap 缓存起来,例如上面 SysUserMapper 中的查询方法、更新方法都有权限注解

2、buildDataFilter 数据过滤,默认管理员和租户管理员不过滤数据,普通用户按照角色进行过滤,使用自定数据权限

public enum DataScopeType {

    /**
     * 全部数据权限
     */
    ALL("1", "", ""),

    /**
     * 自定数据权限
     */
    CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),

    /**
     * 部门数据权限
     */
    DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),

    /**
     * 部门及以下数据权限
     */
    DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),

    /**
     * 仅本人数据权限
     */
    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");

    private final String code;

    /**
     * 语法 采用 spel 模板表达式
     */
    private final String sqlTemplate;

    /**
     * 不满足 sqlTemplate 则填充
     */
    private final String elseSql;

    public static DataScopeType findCode(String code) {
        if (StringUtils.isBlank(code)) {
            return null;
        }
        for (DataScopeType type : values()) {
            if (type.getCode().equals(code)) {
                return type;
            }
        }
        return null;
    }
}

自定义权限注解中 #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) 对应会被解析成 d.dept_id IN ( 100,101,105 )

这样经过SQL拦截处理后,就可以根据角色定义的规则进行数据过滤了

版权声明:除特殊说明,博客文章均为Gavin原创,依据CC BY-SA 4.0许可证进行授权,转载请附上出处链接及本声明。

评论

  1. Felix
    Windows Chrome 130.0.0.0
    1 月前
    2024-11-14 16:19:44

    关于ruoyi-vue升级MybatisPlus,数据权限、分页这些有没有完整的代码可以供参考的呢?

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇