【若依项目-产品经理视角】用户、部门与角色——RBAC 权限体系的“铁三角“是怎么搭起来的?
大家好,我是你们的老朋友,今天继续我们的 RuoYi-Vue-Pro 源码拆解系列。
前两篇文章,我们分别拆了 yudao-framework 框架层和认证权限(auth/security/permission)模块。有读者在评论区问:"认证搞明白了,但用户管理、部门管理、角色管理这些'后台管理三件套'到底是怎么串起来的?"
废话不多说,今天我们就把 yudao-module-system 中最核心的 用户(User)、部门(Dept)、角色(Role) 三大实体拎出来,从 Controller 一路拆到数据库表,看看这套 RBAC 权限体系的"铁三角"是怎么搭起来的。
老规矩,先上结论:RuoYi-Vue-Pro 的用户-部门-角色模块,本质上是一套经典 RBAC 模型 + 树形组织架构 + 数据权限范围的三合一设计。它没有用什么花里胡哨的黑科技,但胜在工程化做得非常扎实——多租户隔离、缓存策略、数据权限、操作日志,该考虑的一个没落下。
一、今日模块概览
一句话总结:这个模块解决的是"谁在什么组织下、拥有什么权限、能看什么数据"的问题。
拆开来看就是三件事:
- 用户管理(User):管理后台操作员的增删改查、密码重置、状态启停、Excel 导入导出、个人中心资料维护。
- 部门管理(Dept):维护企业的组织架构树(树形结构,支持无限层级),每个用户归属一个部门,部门负责人可配置。
- 角色管理(Role):定义权限角色,关联菜单权限和数据权限范围,然后把角色分配给用户。
三者之间的关系用一张图来概括:

此外还有一位"配角"——岗位(Post),它和部门配合,描述用户在组织中的"职位"(如:总经理、产品经理、开发工程师)。岗位和用户也是多对多关系,通过 system_user_post 关联表维护。
二、技术选型分析
2.1 ORM 层:MyBatis-Plus 而非 JPA/Hibernate
RuoYi-Vue-Pro 全栈使用 MyBatis-Plus 作为 ORM 框架。这个选择在国内 Java 生态中非常主流,原因有几个:
为什么选 MyBatis-Plus?
首先是SQL 可控性。国内项目经常遇到复杂查询(多表联查、动态条件、分页优化),MyBatis-Plus 的 LambdaQueryWrapperX 提供了流式 API 构建查询条件,同时保留了 XML SQL 的退路。而 JPA 的 Criteria API 写起来太啰嗦,JPQL 在复杂场景下又不够灵活。
其次是代码生成能力。MyBatis-Plus 自带的 BaseMapper 提供了 17 个通用 CRUD 方法,配合 RuoYi 自己封装的 BaseMapperX(增加了 selectPage、selectListByStatus 等便捷方法),开发效率非常高。
最后是国内生态。MyBatis-Plus 的文档、社区、踩坑经验在国内远比 JPA 丰富,遇到问题搜一搜就能找到答案。
| 维度 | MyBatis-Plus | JPA/Hibernate |
|---|---|---|
| SQL 可控性 | 高,支持 XML + Wrapper | 中,JPQL + Criteria |
| 学习曲线 | 低 | 高(懒加载、缓存坑多) |
| 代码生成 | 内置 CRUD + 代码生成器 | 需额外配置 |
| 复杂查询 | 灵活 | 容易出性能问题 |
| 国内生态 | 极其丰富 | 一般 |
| 适合场景 | 国内企业级项目 | 标准化 CRUD 应用 |
2.2 权限框架:Spring Security + 自定义 @ss 表达式
RuoYi-Vue-Pro 没有用 Shiro(老版 RuoYi 用的),而是升级到了 Spring Security。但它的用法和标准 Spring Security 教程不太一样——它注册了一个名为 ss 的 SpEL Bean,通过 @PreAuthorize("@ss.hasPermission('system:user:create')") 这种方式做接口级权限控制。
为什么不直接用 Spring Security 的 @PreAuthorize("hasAuthority('xxx')")?
因为 RuoYi-Vue-Pro 的权限判断不是简单的"有没有某个权限",而是要经过 "用户 → 角色 → 菜单权限" 的链路查询,还要考虑超级管理员绕过、数据权限过滤、多租户菜单隔离等逻辑。自定义 @ss 表达式可以把这些复杂逻辑封装在 PermissionServiceImpl 里,Controller 层只需要写一个权限字符串就够了。
2.3 缓存策略:Spring Cache + Redis
部门子树 ID 列表、角色信息、用户角色映射、菜单角色映射、权限字符串到菜单的映射——这五类数据全部走了 Spring Cache 抽象 + Redis 后端。
为什么不用 Caffeine 本地缓存?
因为 RuoYi-Vue-Pro 支持多实例部署。如果用本地缓存,A 实例修改了角色权限,B 实例的缓存不会自动失效,就会出现"明明改了权限但就是不生效"的诡异 Bug。Redis 作为集中式缓存,天然支持多实例共享。
2.4 密码加密:BCrypt
密码存储使用 Spring Security 的 BCryptPasswordEncoder。BCrypt 自带盐值,抗彩虹表攻击,是目前业界公认的密码存储最佳实践之一。相比 MD5 + Salt 的老方案,BCrypt 的计算成本可调(通过 strength 参数),能有效抵抗暴力破解。
2.5 社交登录:JustAuth
第三方登录集成使用了 JustAuth,一个号称"史上最全的第三方登录整合工具"的开源库,支持微信、QQ、钉钉、飞书、GitHub 等 80+ 平台。相比自己对接每个平台的 OAuth2 流程,JustAuth 统一了 API 抽象,大幅降低了接入成本。
三、需求溯源推演
看到这里,你可能会有一个疑问:这套模块最初的需求是什么样的?让我尝试从代码反推,还原一份"产品需求文档"的样子。
3.1 最初的需求可能长这样
背景:公司要给客户交付一套管理后台,客户说"我们需要管理员工账号、按部门分配权限"。
核心需求:
- 管理员能创建、编辑、删除员工账号,能重置密码
- 公司的组织架构要能录进系统,支持多级部门
- 不同岗位的人看到的菜单和功能不一样——财务看财务模块,HR 看人事模块
- 同一个模块里,不同层级的人看到的数据范围不一样——部门经理只能看本部门的数据,总经理能看全公司的
- 要支持 Excel 批量导入用户,不然几百号人一个个录太慢了
3.2 需求是怎么一步步长出来的
从代码中的细节,可以推断出一些需求的演进过程:
第一阶段:基础 CRUD。用户、部门、角色的增删改查,这是最基本的。代码里每个 Controller 都有一套标准的 /create、/update、/delete、/get、/page 接口,命名规范高度统一。
第二阶段:权限控制。光有用户和角色不够,还得把角色和菜单关联起来,让用户-角色-菜单形成完整链路。这就是 PermissionController 和 PermissionService 存在的原因。
第三阶段:数据权限。有了功能权限(能看到哪些菜单),客户又会说"我要限制他们只能看自己部门的数据"。于是 DataScopeEnum 出现了——全部数据、自定义部门、本部门、本部门及下级、仅自己,五种粒度。
第四阶段:多租户。SaaS 场景下,一套系统要服务多个客户。每个租户有自己的用户、部门、角色,互相隔离。于是 TenantBaseDO 出现了,几乎所有表都加了 tenant_id 字段。
第五阶段:工程化打磨。Excel 导入导出、操作日志记录、个人中心、社交账号绑定、OAuth2 用户信息接口……这些都是在实际交付过程中逐步长出来的需求。
四、竞品对标分析
说到 RBAC 权限管理,国内开源后台管理系统里做得比较出色的有这几位选手。我们来做个横向对比:
| 维度 | RuoYi-Vue-Pro | JeecgBoot | Pig | SpringBlade | Guns |
|---|---|---|---|---|---|
| 权限模型 | 经典 RBAC(用户-角色-菜单) | RBAC + 数据权限规则引擎 | OAuth2 + RBAC | RBAC + 数据权限 | RBAC |
| 数据权限实现 | 5 种 DataScope + AOP 拦截 | 基于注解 + 自定义 SQL 片段 | 基于 MyBatis 拦截器 | 基于 BladeMetaObjectHandler | 基于 AOP |
| 多租户 | 原生支持,字段级隔离 | 支持,Schema 级隔离 | 支持,字段级隔离 | 支持,字段级隔离 | 不支持(商业版支持) |
| 部门层级 | 树形结构,无限层级 | 树形结构 | 树形结构 | 树形结构 | 树形结构 |
| 缓存策略 | Spring Cache + Redis,5 类缓存 | Redis 缓存 | Redis 缓存 | Ehcache + Redis | Redis 缓存 |
| 岗位管理 | 有,用户-岗位多对多 | 有 | 无 | 有 | 有 |
| 社交登录 | JustAuth 80+ 平台 | 自行对接 | 仅 OAuth2 | JustAuth | 无 |
| API 风格 | RESTful + Swagger 注解 | RESTful + Knife4j | RESTful | RESTful + Blade API | RESTful |
RuoYi-Vue-Pro 的优势
数据权限的灵活性。五种 DataScope 覆盖了绝大多数企业场景,而且通过 DataPermissionConfiguration 统一注册规则,新增实体的数据权限只需要几行配置。相比之下,JeecgBoot 的数据权限需要写自定义 SQL 片段,学习成本更高。
多租户的原生集成。RuoYi-Vue-Pro 的多租户是字段级隔离,所有表共享同一个库,通过 tenant_id 字段区分。这种方案部署简单、成本低,适合中小规模 SaaS。而 JeecgBoot 的 Schema 级隔离虽然更安全,但运维成本也更高。
缓存设计精细。5 类缓存分别覆盖了部门子树、角色信息、菜单角色映射、用户角色映射、权限字符串映射,且缓存失效策略精确到单个 key 或全量清除,视场景而定。
RuoYi-Vue-Pro 的不足
缺少权限模拟功能。企业级权限系统通常需要一个"权限模拟"功能——管理员可以"以某个用户的视角"查看系统,验证权限配置是否正确。目前 RuoYi-Vue-Pro 没有这个功能。
角色没有"继承"机制。如果一个"部门经理"角色需要拥有"普通员工"角色的所有权限 + 额外权限,目前只能手动把所有菜单都勾一遍。如果角色支持继承(类似 RBAC with hierarchy),配置效率会更高。
部门树没有"虚拟部门"概念。有些企业的组织架构中,一个人可能同时属于多个部门(矩阵式管理),但 RuoYi-Vue-Pro 的用户只能归属一个 dept_id。
五、核心业务流程
5.1 用户创建全流程
这是整个模块中最复杂的流程之一,涉及租户校验、唯一性校验、部门校验、密码加密、岗位关联等多个环节:

划重点!!!这里有个细节值得注意:唯一性校验是在 DataPermissionUtils.executeIgnore() 中执行的。为什么要绕过数据权限?因为如果不绕过,当前用户如果只能看到自己部门的数据,那创建其他部门同名用户时,唯一性检查会因为"看不到那个用户"而误判为不重复,导致数据库插入重复数据。
5.2 权限校验链路
当一个用户访问某个接口时,权限校验的完整链路如下:

整个链路中,步骤 ①②③ 全部走 Redis 缓存,只有缓存未命中时才会查数据库。这意味着权限校验的延迟基本在毫秒级别,即使在高并发场景下也能扛住。
5.3 数据权限过滤流程
数据权限是 RuoYi-Vue-Pro 的一大亮点。当用户查询列表时,系统会根据其角色的 dataScope 自动拼接 SQL 条件:
- ALL(全部数据):不追加任何条件
- DEPT_CUSTOM(自定义部门):追加 WHERE dept_id IN (指定部门 + 本人部门)
- DEPT_ONLY(本部门):追加 WHERE dept_id = 本人部门
- DEPT_AND_CHILD(本部门及下级):追加 WHERE dept_id IN (本人部门 + 所有子部门)
- SELF(仅本人):追加 WHERE creator = 本人用户ID
多个角色的数据权限是取并集的——比如一个用户同时拥有"本部门"和"自定义部门 A、B"两个角色,那他能看到的数据范围就是"本部门 + A + B"。
六、数据模型解读
6.1 核心表结构一览
| 表名 | 用途 | 关键字段 | 租户隔离 |
|---|---|---|---|
| system_users | 用户信息 | username, password(BCrypt), dept_id, post_ids(JSON), mobile, email, status | ✅ tenant_id |
| system_dept | 部门信息 | name, parent_id, sort, leader_user_id, status | ✅ tenant_id |
| system_role | 角色信息 | name, code, sort, data_scope, data_scope_dept_ids(JSON), type, status | ✅ tenant_id |
| system_post | 岗位信息 | name, code, sort, status | ❌ BaseDO |
| system_user_role | 用户-角色关联 | user_id, role_id | ✅ tenant_id |
| system_user_post | 用户-岗位关联 | user_id, post_id | ❌ BaseDO |
| system_role_menu | 角色-菜单关联 | role_id, menu_id | ✅ tenant_id |
6.2 设计亮点
JSON 字段的双刃剑。system_users.post_ids 和 system_role.data_scope_dept_ids 都使用了 JSON 数组存储。这样做的好处是查询时不需要 JOIN 关联表,直接一个字段就能拿到所有 ID。但代价是失去了数据库层面的外键约束,需要在应用层保证数据一致性。
💡 踩坑提醒:JSON 字段在 MySQL 5.7+ 才支持,如果你用的是更老的数据库版本,需要改为逗号分隔的字符串 + 应用层解析。
自引用树形结构。system_dept 通过 parent_id 字段实现树形结构,根节点的 parent_id = 0。这种设计简单直观,但查询子树需要递归。RuoYi-Vue-Pro 的做法是用 BFS 迭代 + Redis 缓存来优化——getChildDeptIdListFromCache 方法加了 @Cacheable,避免了每次数据权限过滤都要递归查询数据库。
逻辑删除。所有表都使用 deleted 字段做逻辑删除(bit(1) 类型),而不是物理删除。这是一个非常务实的选择——企业系统中,数据删除后往往还需要审计追溯。
6.3 表关系 ER 图

七、产品设计亮点与槽点
7.1 让我眼前一亮的设计
1. 用户禁用时自动踢出登录
在 AdminUserServiceImpl.updateUserStatus() 中,当用户状态被设为禁用时,会同步调用 oauth2TokenService.removeAccessToken() 清除该用户的所有 Token。这意味着管理员禁用一个用户后,该用户会立即被强制退出,不需要等 Token 自然过期。这个体验细节做得很到位。
2. 免鉴权的 IM 专用接口
UserController 中有两个接口——getSimpleUser(获取简易用户信息用于 IM 头像卡片)和 getSimpleUserListByNickname(按昵称模糊搜索用户用于添加好友)——被标注为"免鉴权"。这是为了 IM 模块的性能考虑:如果每次显示头像都要走完整的权限校验链路,延迟会很高。这种"特定场景开放绿色通道"的设计思路值得借鉴。
3. Diff-based 关联更新
在 PermissionServiceImpl.assignRoleMenu() 和 assignUserRole() 中,更新关联关系时不是"先删后增",而是计算差异——已有的保留、新增的插入、多余的删除。这样做的好处是不会破坏已有的审计记录(如果后续有关联表的操作日志需求),而且减少了数据库操作量。
4. 操作日志自动记录
用户、角色的创建/更新/删除操作都标注了 @LogRecord,配合框架层的操作日志切面,自动记录操作人、操作时间、操作内容、变更前后值。这对于企业级系统的合规审计非常重要。
7.2 可以改进的地方
1. 部门删除没有"迁移用户"机制
当前删除部门时,如果部门下有子部门,会直接拒绝删除。但如果部门下有用户呢?代码中没有显式检查。虽然业务上应该先迁移用户再删除部门,但如果能在删除时提供"将用户迁移到指定部门"的选项,体验会更好。
2. 角色编码的不可修改性
RoleServiceImpl 中禁止修改系统内置角色(type = SYSTEM),但没有禁止修改自定义角色的 code。如果某个自定义角色的 code 被修改了,而代码中有硬编码引用这个 code 的地方(比如 @PreAuthorize 注解),就会导致权限失效。建议在角色 code 修改时增加警告或禁止修改。
3. 缓存失效策略偏粗暴
部门子树缓存使用 allEntries = true 全量清除,意味着任何一个部门的增删改都会导致所有部门的子树缓存失效。在部门数量多、修改频繁的场景下,缓存命中率会很低。更好的做法是只失效被修改部门的所有祖先节点的缓存,但这需要维护"子→父"的反向索引,实现复杂度更高。
4. post_ids 冗余存储
system_users 表中有 post_ids JSON 字段,同时又有 system_user_post 关联表。两套数据需要同步维护,存在不一致的风险。建议二选一:要么只用 JSON 字段(简单但弱约束),要么只用关联表(规范但需 JOIN)。
八、发散性思考
8.1 这个模块还能做什么?
ABAC(基于属性的访问控制)。当前是纯 RBAC 模型,权限只跟角色挂钩。但在一些复杂场景下(如:只有项目状态为"进行中"时,项目经理才能审批),需要 ABAC 模型——根据资源属性动态判断权限。可以在现有框架上扩展一个"权限条件"的概念,每个菜单权限可以配置一组条件表达式。
权限版本管理。企业经常遇到这种情况:改了一波权限配置,结果出了问题,想回滚但又不知道改了什么。如果给角色-菜单的关联关系加上版本号,就能实现"权限快照"和"一键回滚"。
组织架构可视化编辑器。当前的部门管理是纯 CRUD,如果能提供一个拖拽式的组织架构编辑器(类似 ProcessOn 那种),让 HR 可以直观地调整部门层级和人员归属,体验会好很多。
8.2 如果让我重新设计
如果从零开始设计这套模块,我会做几个不同的选择:
引入事件驱动的用户变更通知。当前用户信息变更(昵称、头像)通过 MQ 通知下游模块。但部门变更、角色变更同样会影响下游(比如数据权限变更需要刷新缓存)。我会设计一个统一的 OrgChangeEvent,涵盖用户/部门/角色的所有变更,下游模块按需订阅。
数据权限规则引擎化。当前的五种 DataScope 是硬编码的枚举。如果未来客户提出"按项目组看数据"、"按区域看数据"的需求,就需要改代码。更好的做法是把数据权限规则抽象为可配置的规则引擎——管理员可以自定义"基于什么字段、按什么逻辑、过滤什么数据"。
角色支持"组"的概念。当一个企业有几十个角色时,管理成本会很高。如果引入"角色组"(类似 AWS 的 IAM Group),可以把多个角色打包管理,批量分配给用户。
8.3 技术思路可迁移的场景
这套 RBAC + 数据权限的设计思路,不仅适用于后台管理系统,还可以迁移到:
- SaaS 平台:多租户 + 按套餐分配功能权限,就是 RuoYi-Vue-Pro 已经在做的
- 低代码平台:表单、流程、报表的访问控制,可以复用菜单权限 + 数据权限的框架
- API 网关:把"菜单权限"替换为"API 权限",就能实现接口级的访问控制
- 数据中台:数据权限的"部门/本部门及下级/自定义"模型,可以直接用于数据资产的访问控制
九、关键代码导读
最后,列出 5 个最值得深入阅读的代码文件,按推荐阅读顺序排列:
1. PermissionServiceImpl.java — 权限引擎的核心
路径:yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
为什么值得读:这是整个 RBAC 模型的"大脑"。hasAnyPermissions() 方法展示了权限校验的完整链路(用户→角色→菜单→权限字符串),getDeptDataPermission() 方法展示了五种数据范围的计算逻辑,assignRoleMenu() 方法展示了 Diff-based 的关联更新策略。读完这个文件,RBAC 模型就不再是教科书上的抽象概念了。
2. AdminUserServiceImpl.java — 用户管理的"百科全书"
路径:yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
为什么值得读:这是整个模块中业务逻辑最复杂的 Service 文件。createUser() 方法串联了租户校验、唯一性校验(绕过数据权限!)、密码加密、岗位关联、操作日志等多个环节。getUserPage() 方法展示了部门递归查询 + 角色过滤的分页查询策略。importUserList() 方法是 Excel 批量导入的教科书实现。
3. DeptServiceImpl.java — 树形结构的优雅实现
路径:yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
为什么值得读:虽然代码量不大,但包含了树形结构处理的几个经典算法——BFS 迭代获取子树、循环引用检测(沿父链向上追溯)、缓存策略(@Cacheable + allEntries 清除)。特别是 getChildDeptIdListFromCache() 方法上加了 @DataPermission(enable = false),这个细节说明作者深刻理解数据权限对缓存数据完整性的影响。
4. TenantServiceImpl.java — 多租户的"编排大师"
路径:yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
为什么值得读:createTenant() 方法可能是整个模块中最复杂的方法——它在一个事务中完成了"创建租户 → 切换到新租户上下文 → 创建管理员角色 → 分配菜单权限 → 创建管理员用户 → 分配角色"这一系列编排操作。updateTenantRoleMenu() 方法展示了套餐变更时如何级联更新所有租户的角色菜单。读这个文件,你会理解"多租户不是一个字段,而是一套编排逻辑"。
5. DataPermissionConfiguration.java — 数据权限的"注册中心"
路径:yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/datapermission/config/DataPermissionConfiguration.java
为什么值得读:这个配置文件虽然只有几十行代码,但它揭示了数据权限的核心机制——通过注册 DeptDataPermissionRuleCustomizer,告诉框架"哪些表需要按部门过滤、用哪个字段关联部门"。理解了这几十行代码,你就理解了 RuoYi-Vue-Pro 数据权限的"开关"在哪里。
写在最后
用户、部门、角色——这"铁三角"是几乎所有后台管理系统的基石。RuoYi-Vue-Pro 的实现没有用什么花哨的技术,但在工程化细节上做得非常扎实:多租户隔离、五级数据权限、精细的缓存策略、Diff-based 关联更新、操作日志自动记录……这些都是"踩过坑才会想到要做"的设计。
下一篇文章,我们继续拆解 yudao-module-system 的另一个重要模块——字典、短信、邮件与通知。这四个子系统看起来不起眼,但它们是系统"对外发声"的通道,设计好了能省很多事。
觉得有用的话,点个赞支持一下呗~ 有问题欢迎评论区交流!
系列文章导航
- 第一篇:框架层 yudao-framework 深度拆解
- 第二篇:认证与权限 auth-security-permission 深度拆解
- 第三篇(本文):用户与组织 user-dept-role 深度拆解
- 第四篇预告:字典 / 短信 / 邮件 / 通知
更多推荐




所有评论(0)