万字拆解 RuoYi-Vue-Plus 数据权限:从 AOP 切面到 MyBatis-Plus 拦截器的 SQL 改写源码全解
2026/7/29 15:10:54 网站建设 项目流程

文章目录

    • 一、为什么数据权限比接口权限更复杂?
      • 1.1 接口权限 vs 数据权限:本质区别
      • 1.2 数据权限为什么难?
      • 1.3 你将从本文学到什么
    • 二、数据权限核心概念扫盲
      • 2.1 六种数据范围(DataScope)
      • 2.2 几个关键数据库字段
      • 2.3 数据权限对开发者的体验
    • 三、整体架构:注解 + AOP + 拦截器 + SpEL 四件套
      • 3.1 数据流向图(一句话版)
      • 3.2 为什么要用 AOP + ThreadLocal 而不是直接在拦截器里找注解?
    • 四、@DataPermission 注解体系详解
      • 4.1 @DataPermission 注解定义
      • 4.2 @DataColumn 子注解
      • 4.3 五种典型使用示例
        • 示例 1:方法级注解(最常见)
        • 示例 2:带表别名(JOIN 查询时用)
        • 示例 3:类级注解(整个 Mapper 生效)
        • 示例 4:自定义 joinStr(强制 AND)
        • 示例 5:带 permission 豁免
    • 五、DataScopeType 枚举:6 种数据范围与 SpEL 模板
      • 5.1 完整源码
      • 5.2 SpEL 模板语法详解
      • 5.3 @sdss 是什么?——Bean 引用
      • 5.4 elseSql 兜底条件的作用
      • 5.5 六种模板的渲染示例
      • 5.6 findCode 方法
    • 六、DataPermissionHelper:ThreadLocal 上下文与 ignore 防死循环机制
      • 6.1 为什么需要 ThreadLocal?
      • 6.2 核心 ThreadLocal 变量
      • 6.3 注解的存取:setPermission / getPermission / removePermission
      • 6.4 上下文变量:setVariable / getVariable
      • 6.5 ignore 机制:防止 SpEL 内部查询死循环(核心难点)
        • 6.5.1 为什么会死循环?
        • 6.5.2 ignore 方法:安全地关闭数据权限
        • 6.5.3 enableIgnore / disableIgnore:可重入设计
      • 6.6 对外暴露的 invalid 方法
    • 七、AOP 切面三件套源码逐行解读
      • 7.1 DataPermissionPointcut:切点(确定拦截范围)
      • 7.2 DataPermissionAdvice:通知(拦截后的动作)
      • 7.3 DataPermissionPointcutAdvisor:顾问(组装切点和通知)
      • 7.4 注册 Advisor 到 Spring 容器
      • 7.5 AOP 三件套的协作流程(一图胜千言)
    • 八、PlusDataPermissionInterceptor:MyBatis Plus 拦截器入口
      • 8.1 类继承关系
      • 8.2 beforeQuery:SELECT 查询拦截入口
      • 8.3 beforePrepare:UPDATE/DELETE 拦截入口
      • 8.4 processSelect/processUpdate/processDelete:SQL 类型分发
      • 8.5 setWhere:SELECT 的 WHERE 设置
      • 8.6 buildTableExpression:多表 JOIN 支持(预留)
      • 8.7 拦截器在 MybatisPlusConfig 中的注册
    • 九、PlusDataPermissionHandler:核心 SQL 构建逻辑深度解析
      • 9.1 核心成员变量
      • 9.2 getSqlSegment:主入口方法(SQL 片段生成)
      • 9.3 buildDataFilter:核心中的核心(SQL 字符串构建)
        • 9.3.1 初始化:决定拼接符 OR/AND
        • 9.3.2 准备 SpEL 上下文
        • 9.3.3 第一步:遍历 @DataColumn,设置 SpEL 变量
        • 9.3.4 第二步:遍历用户的所有角色,逐一生成 SQL 条件
        • 9.3.5 第三步:拼接所有条件
      • 9.4 NullSafe 内部类:防御性编程
    • 十、SysDataScopeServiceImpl:SpEL 中的 @sdss Bean 实现
      • 10.1 类声明与注意事项
      • 10.2 getRoleCustom:获取角色自定义部门列表
      • 10.3 getDeptAndChild:获取部门及所有子部门 ID 列表
      • 10.4 返回值为什么是 String 而不是 List<Long>?
    • 十一、数据权限完整执行时序(20步全链路)
      • 11.1 完整时序图(20 步)
      • 11.2 关键节点总结
    • 十二、实战:从零为订单模块接入数据权限
      • 12.1 第一步:数据库表设计(必须的字段)
      • 12.2 第二步:实体类继承 BaseEntity
      • 12.3 第三步:Mapper 接口加 @DataPermission 注解
      • 12.4 第四步:Service 层调用 Mapper
      • 12.5 第五步:Controller 层加接口权限注解
      • 12.6 第六步:菜单配置(sys_menu 表)
      • 12.7 第七步:测试验证
      • 12.8 JOIN 查询时的写法
    • 十三、多角色数据权限合并策略:SELECT 用 OR,UPDATE/DELETE 用 AND
      • 13.1 为什么 SELECT 用 OR?
      • 13.2 为什么 UPDATE/DELETE 用 AND?
      • 13.3 强制指定 joinStr 的场景
      • 13.4 多角色 AND 策略的一个特殊情况:ALL 角色短路
    • 十四、数据权限与多租户协同工作机制
      • 14.1 多租户拦截器原理回顾
      • 14.2 租户管理员的数据权限豁免
      • 14.3 ignore 机制与多租户的兼容
      • 14.4 SpEL 里查询是否会加租户条件?
    • 十五、字段自动填充:InjectionMetaObjectHandler
    • 十六、常见问题排查指南(FAQ)
      • Q1:数据权限完全不生效,SQL 没有追加任何条件
      • Q2:数据权限生效了,但查不到任何数据
      • Q3:StackOverflowError / 死循环
      • Q4:多表 JOIN 查询时列名歧义(Column 'create_dept' in where clause is ambiguous)
      • Q5:新增/修改时 create_dept/create_by 没有自动填充
      • Q6:新增的数据自己看不到/别人能看到
      • Q7:@DataPermission 类级注解不生效
      • Q8:修改/删除时数据权限不生效,能修改别人的数据
      • Q9:如何临时关闭某个方法的数据权限?
      • Q10:如何验证数据权限最终生成的 SQL?
    • 十七、性能优化与最佳实践
      • 17.1 缓存优化
      • 17.2 数据库索引优化
      • 17.3 最佳实践清单
      • 17.4 性能参考数据
    • 十八、关键源码清单与速查表
      • 18.1 核心文件速查表
      • 18.2 6种数据范围速查表
      • 18.3 注解使用速查
    • 十九、总结
      • 19.1 核心知识点回顾
      • 19.2 架构设计思想升华

一、为什么数据权限比接口权限更复杂?

1.1 接口权限 vs 数据权限:本质区别

在上一篇博客《RuoYi-Vue-Plus5 接口权限深度剖析》中,我们详细讲解了接口权限的实现——它解决的是"能不能调这个接口"的问题,通过 Sa-Token 的@SaCheckPermission注解 + AOP 校验用户是否拥有某个权限码,不满足就返回 403。

但数据权限解决的是一个更难的问题:即使两个用户都能调用同一个查询接口,他们看到的数据行也应该不一样

举个真实场景:

公司「深圳总公司」下有「研发部」「市场部」「财务部」三个部门。

  • 员工张三属于研发部,角色是「研发主管」:他能看到研发部所有员工的数据,但看不到市场部和财务部。
  • 员工李四角色是「普通员工」:只能看到自己创建的数据。
  • 员工王五是超级管理员:能看到所有数据。

三个人调用的是同一个接口/system/user/list,接口权限都验证通过了(都有system:user:list权限码),但返回的数据完全不同。这就是数据权限要做的事。

1.2 数据权限为什么难?

接口权限的本质是"布尔判断

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询