RuoYi 框架实战笔记:深入解析 @DataScope 数据权限注解
1. 前言:为什么需要 @DataScope?
在企业级后台管理系统中,权限控制通常分为两个维度:
- 功能权限 :控制用户能看到哪些菜单、点击哪些按钮(通常通过 RBAC 模型 +
@PreAuthorize实现)。 - 数据权限 :控制用户能看到哪几行数据。例如:销售总监能看全公司的订单,部门经理只能看本部门的订单,普通销售只能看自己的订单。
如果要在业务代码中通过 if (user.isManager()) { ... } 这种方式手动拼接 SQL,代码将变得极其臃肿且难以维护。RuoYi 框架提供的 @DataScope 注解,正是利用 AOP(面向切面编程) 技术,优雅地解决了这个问题。
2. 核心原理
@DataScope 的核心思想是:"在 SQL 执行前,根据当前登录用户的角色权限,动态拼接 SQL 过滤条件。"
工作流程
- AOP 拦截 :
DataScopeAspect切面拦截所有标记了@DataScope的方法。 - 获取角色:获取当前登录用户及其绑定的角色信息。
- 生成 SQL :根据角色配置的"数据范围"(Data Scope),生成一段 SQL 片段(如
AND dept_id = 100)。 - 参数注入 :将这段 SQL 注入到参数对象(需继承
BaseEntity)的params属性中。 - MyBatis 执行 :在 XML 中通过
${params.dataScope}将过滤条件拼接到主 SQL 后面。
3. 支持的数据权限模式
RuoYi 在"角色管理"中默认支持 5 种数据范围,底层逻辑如下:
| 权限名称 | 对应代码 | 逻辑描述 | SQL 示例片段 |
|---|---|---|---|
| 全部数据权限 | DATA_SCOPE_ALL |
不进行过滤,可查看所有数据 | (无拼接) |
| 自定数据权限 | DATA_SCOPE_CUSTOM |
根据角色配置的部门列表过滤 | OR d.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = X) |
| 部门数据权限 | DATA_SCOPE_DEPT |
仅查看本部门数据 | OR d.dept_id = 101 |
| 部门及以下 | DATA_SCOPE_DEPT_AND_CHILD |
查看本部门及所有子部门数据 | OR d.dept_id IN (SELECT dept_id FROM sys_dept WHERE find_in_set(101, ancestors)) |
| 仅本人数据 | DATA_SCOPE_SELF |
仅查看自己创建的数据 | OR u.user_id = 1 |
4. 使用指南(三步走)
第一步:Service 层添加注解
在需要过滤数据的业务方法上添加注解。
deptAlias:指定 SQL 中部门表的别名(默认d)。userAlias:指定 SQL 中用户表的别名(默认u)。
java
@Service
public class SysUserServiceImpl implements ISysUserService {
@Override
// 告诉切面:SQL里部门表别名是 d,用户表别名是 u
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user) {
return userMapper.selectUserList(user);
}
}
第二步:Entity 类继承 BaseEntity
查询参数对象(如 SysUser)必须继承 BaseEntity,因为切面是将生成的 SQL 存入 BaseEntity 的 params map 中。
java
public class SysUser extends BaseEntity {
// ... 属性定义
}
第三步:Mapper XML 拼接 SQL
在 MyBatis 的 XML 文件中,将生成的 SQL 片段拼接到 WHERE 子句的最后。
注意 :必须使用
$符号(文本替换),不能使用#(预编译)。
xml
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
SELECT u.user_id, u.dept_id, u.nick_name, d.dept_name
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
<!-- 关键点:动态拼接数据权限过滤 SQL -->
${params.dataScope}
</select>
5. 避坑指南与注意事项
-
别名必须匹配
- 注解中的
deptAlias="d"必须与 XML 中LEFT JOIN sys_dept d的别名严格一致。如果 XML 中写的是LEFT JOIN sys_dept sd,则注解必须改为deptAlias="sd",否则会报Unknown column 'd.dept_id'错误。
- 注解中的
-
超级管理员特权
- 如果是超级管理员(
admin或拥有所有权限的角色),切面逻辑通常会直接跳过,不拼接任何 SQL,从而拥有查看所有数据的权限。
- 如果是超级管理员(
-
数据表关联
- 如果要使用部门权限,主查询表必须包含
dept_id或者关联了sys_dept表。 - 如果要使用"仅本人"权限,主查询表必须包含
user_id或者关联了sys_user表。
- 如果要使用部门权限,主查询表必须包含
-
参数对象限制
- 如果是自定义的 DTO 对象作为查询参数,务必确保它继承了
BaseEntity,或者你自己手动在 DTO 中添加Map<String, Object> params字段,否则切面无法注入 SQL。
- 如果是自定义的 DTO 对象作为查询参数,务必确保它继承了
-
多表连接时的歧义
- 如果多个表都有
dept_id字段,务必通过deptAlias明确指定使用哪张表的字段进行过滤,防止 SQL 字段歧义(Ambiguous column)。
- 如果多个表都有
6. 总结
@DataScope 是 RuoYi 框架中解决数据隔离问题的"神兵利器"。它通过 解耦业务代码与权限逻辑,让开发者只需关注 SQL 关联关系,而无需关心复杂的权限判断,极大地提高了开发效率。