RuoYi框架二次开发系列(二)数据权限深度定制、源码解析与实战落地
在上一篇《RuoYi二次开发常用技巧与功能拓展》中,我们讲解了基础开发规范、CRUD提效技巧与通用功能拓展。而在企业级后台系统中,功能权限只解决“能不能点”,数据权限才解决“能不能看”。
绝大多数OA、ERP、政务、国资、供应链系统的核心安全需求,都是行级数据隔离:普通员工只能看自己的数据、部门主管看本部门数据、管理员看全量数据。
本文为系列第二篇,深度拆解 RuoYi 数据权限底层原理、五种默认权限规则、自定义数据权限开发、特殊业务场景适配、常见Bug与终极避坑方案,所有代码均可直接用于生产环境。
一、先搞懂:RuoYi 功能权限 vs 数据权限
很多新手开发者容易混淆两种权限,导致权限配置混乱、数据越权等问题,二者核心区别如下:
1.1 功能权限(按钮/菜单权限)
控制用户能否访问某个接口、页面、按钮,属于「操作权限」。
实现方式:基于 @PreAuthorize 注解、菜单权限标识控制,例如@PreAuthorize(“hasPermission(‘biz:goods:list’)”)。
特点:全局固定,与数据本身无关,有权限就能操作全部数据。
1.2 数据权限(行级权限)
控制用户能查询、操作哪些数据,属于「数据范围权限」。
实现方式:MyBatis 拦截器动态拼接 SQL 条件,自动过滤数据,无需手动写判断。
特点:同接口、同权限的不同用户,查询结果不同,实现数据隔离。
二、RuoYi 原生五种数据权限规则(核心基础)
RuoYi 内置五种开箱即用的数据权限范围,也是企业项目最常用的规则,绑定在角色维度,一个角色对应一种数据范围。
| 权限类型 | 常量标识 | 权限说明 | 适用场景 |
|---|---|---|---|
| 全部数据权限 | data_scope=1 | 可查询所有数据,无过滤 | 超级管理员、平台总管理员 |
| 自定义数据权限 | data_scope=2 | 手动指定可查看的部门列表 | 跨部门审批、专项管理员 |
| 本部门数据权限 | data_scope=3 | 仅查询当前所属部门数据 | 部门主管、部门运营 |
| 本部门及以下 | data_scope=4 | 查询当前部门+下级所有部门数据 | 多级部门管理层、区域负责人 |
| 仅本人数据权限 | data_scope=5 | 只查询自己创建的数据 | 普通员工、基础业务人员 |
核心知识点:数据权限是角色级配置,一个用户多个角色时,权限会取并集,以最大数据范围为准。
三、底层原理:数据权限如何自动生效?
RuoYi 数据权限核心基于 MyBatis 插件拦截器 实现,无需开发者手动拼接 SQL,全程自动处理。
3.1 执行流程
- 用户登录,框架缓存当前用户角色、部门、数据范围配置;
- 业务 Mapper 执行查询 SQL 时,触发数据权限拦截器 DataScopeInterceptor;
- 拦截器判断当前接口是否需要数据权限过滤;
- 根据角色对应的 data_scope 类型,动态拼接 WHERE 过滤条件;
- MyBatis 执行拼接后的 SQL,返回过滤后的数据集。
3.2 默认过滤规则源码逻辑
框架默认依赖数据表的 create_by(本人数据)、dept_id(部门数据)两个字段实现过滤,这也是上一篇文章强调业务表必须携带通用字段的核心原因。
简单对应逻辑:
- 仅本人:create_by = 当前登录用户名
- 本部门:dept_id = 当前用户部门ID
- 本部门及下级:dept_id IN (当前部门及所有子部门ID)
四、标准数据权限快速落地(零代码开启)
很多开发者不知道:RuoYi 新增业务模块,无需写一行代码,即可开启数据权限,只需遵循规范配置即可。
4.1 前置条件
业务数据表必须包含两个核心字段:
- create_by varchar(64):创建人账号
- dept_id bigint:数据所属部门ID
通过代码生成器生成的代码,会自动适配数据权限拦截规则。
4.2 开启方式
在业务 Mapper 的 List 查询方法上,添加数据权限注解:
/** * 查询商品列表 */ @DataScope List<BizGoods>selectBizGoodsList(BizGoods bizGoods);仅此一行注解,自动拥有全部五种数据权限过滤能力。
4.3 后台角色配置
系统管理 > 角色管理 > 数据权限,选择对应权限范围,保存后立即生效,无需重启服务。
五、高阶实战:自定义数据权限场景开发
原生五种规则无法覆盖所有业务,实战中高频出现:按片区、按门店、按项目、按自定义维度过滤数据。下面讲解企业最常用的自定义字段数据权限通用方案,不改动源码、完全拓展实现。
5.1 业务场景
例如:门店管理系统,不同区域经理只能查看自己负责的门店数据,不再以部门为维度,而是以 shop_id 门店ID作为数据隔离维度。
5.2 实现思路(AOP+自定义注解)
- 自定义数据权限注解,标记需要门店权限过滤的接口;
- 用户维度存储可操作的门店ID集合;
- 通过 MyBatis 拦截器或 AOP 动态拼接 SQL 过滤条件;
- 实现基于门店的行级数据隔离。
5.3 完整可落地代码
第一步:自定义注解
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface ShopDataScope{}第二步:自定义数据权限拦截器,继承原生拦截器拓展逻辑,重写过滤规则,支持 shop_id 过滤。
核心逻辑:获取当前用户已分配门店列表,拼接 SQL:shop_id IN (1,2,3)。
第三步:Mapper 方法添加注解
@ShopDataScope List<BizShop>selectBizShopList(BizShop bizShop);至此,完成自定义维度的数据权限隔离,完全解耦框架源码,支持后续无限拓展。
六、特殊业务场景适配方案
6.1 详情、编辑、删除接口数据权限控制
原生数据权限仅对列表查询生效,详情、修改、删除接口默认无过滤,会出现「看不到列表,但能通过ID编辑别人数据」的越权漏洞。
解决方案:
在 Service 层统一校验数据权限,通过工具类判断当前用户是否有权操作该条数据,无权限直接抛出异常。
// 校验当前用户是否拥有该数据操作权限 DataScopeUtils.checkDataPermission(bizGoods.getCreateBy(), bizGoods.getDeptId());6.2 超级管理员数据权限放行
框架默认超级管理员自动放行所有数据权限,无需手动处理,自定义拦截器需保留该逻辑,避免管理员数据被过滤。
6.3 多角色数据权限冲突解决
用户拥有多个角色时,遵循最大权限原则:全部权限 > 自定义 > 部门及以下 > 部门 > 本人。
七、数据权限高频Bug与避坑指南
坑1:新增数据后自己看不到
原因:业务表未自动填充 dept_id、create_by 字段
解决:严格使用框架自带的自动填充规则,勿手动insert字段为空
- 坑2:数据权限不生效
原因1:Mapper查询方法未加 @DataScope 注解
原因2:测试账号为超级管理员(默认全量数据)
原因3:角色数据权限配置为「全部数据」
- 坑3:关联查询、多表查询数据权限失效
原因:原生拦截器仅针对单表字段过滤
解决:多表查询手动拼接数据权限条件,或自定义拦截器适配关联表
- 坑4:分页总数与列表数据不一致
原因:分页count查询未走数据权限拦截
解决:保证count查询和list查询均被数据权限拦截
- 坑5:导出数据越权
原因:导出接口未添加数据权限注解
解决:导出 Mapper 方法统一添加 @DataScope
八、生产级最佳实践总结
- 所有业务表必带 dept_id、create_by,这是数据权限的基石;
- 所有业务列表查询必加 @DataScope,统一开启数据过滤;
- 单表查询优先使用原生规则,不重复造轮子;
- 特殊维度使用自定义注解+AOP拓展,坚决不改源码;
- 增删改详情接口必须手动权限校验,杜绝越权漏洞;
- 导出、批量操作接口优先适配数据权限,保障数据安全。
九、下篇预告
下一篇为系列第三篇:RuoYi 第三方功能集成实战(OSS文件上传、短信验证码、微信登录、支付对接),全程无坑、可直接复制上线的生产级集成方案。
