数据权限
当你要实现“只能看自己/本部门/本部门及子部门数据”,或排查列表页数据越权问题时,从这篇开始。
数据权限是一种行级安全控制机制,用于限制用户只能访问其权限范围内的数据行。与功能权限(控制用户能执行哪些操作)不同,数据权限控制用户能看到哪些数据。本文涵盖:
- 数据范围类型与并集策略
- TypeORM QueryBuilder 的无侵入注入思路
- 装饰器用法与落地建议
设计目标
| 目标 | 说明 |
|---|---|
| 细粒度控制 | 按组织架构维度控制数据可见范围 |
| 多角色支持 | 用户拥有多个角色时,采用并集策略合并权限 |
| 低侵入实现 | 基于 TypeORM QueryBuilder 拦截,业务代码无需手写过滤 SQL |
| 可配置 | 支持全部、部门层级、个人、自定义等多种范围 |
数据范围类型
与角色字段 dataScope 对应:
| 值 | 类型 | 说明 |
|---|---|---|
1 | 全部数据 | 可访问系统所有数据 |
2 | 本部门及子部门 | 可访问用户所属部门及其下级部门的数据 |
3 | 本部门 | 仅可访问用户所属部门的数据 |
4 | 本人 | 仅可访问自己创建的数据 |
5 | 自定义 | 可访问指定部门的数据(需配置部门列表) |
枚举位置:src/common/enums/data-scope.enum.ts。
核心组件
NestJS 的数据权限由三部分完成:
DataScopeGuard:从request.user解析userId/deptId/dataScopes写入请求上下文RequestContext:保存当前请求的用户与数据权限信息DataPermissionPlugin:拦截 TypeORM QueryBuilder 的查询方法并注入过滤条件
多角色权限合并策略
当用户拥有多个角色时,采用 并集(OR)策略 合并数据权限:
- 任一角色为
ALL(1)时,跳过过滤 - 其余角色的过滤条件使用
OR连接并用括号包裹
无侵入实现(TypeORM QueryBuilder 注入)
项目通过拦截 QueryBuilder 的 getMany/getOne/getManyAndCount/getCount 等方法,在执行前注入数据权限条件。
核心实现:src/common/plugins/data-permission.plugin.ts。
关键点:
- 必须通过
@DataPermission()显式指定查询别名(否则不注入,避免误伤) - 如果当前用户无任何
dataScopes,则注入1 = 0返回空结果
装饰器用法
DataPermission
装饰器位置:src/common/decorators/data-permission.decorator.ts。
常用配置:
deptAlias:部门字段所在表的别名deptIdColumnName:部门字段名(默认dept_id)userAlias:创建人字段所在表的别名userIdColumnName:创建人字段名(默认create_by)
示例(以 QueryBuilder 主别名 u 为例):
ts
@DataPermission({ deptAlias: "u", userAlias: "u" })
async page(...) {
return this.userRepo
.createQueryBuilder("u")
.leftJoinAndSelect("u.dept", "d")
.getManyAndCount();
}SkipDataPermission
当你明确需要绕过数据权限(例如平台级统计、定时任务),使用 @SkipDataPermission()。
过滤规则(按范围)
过滤逻辑在 DataPermissionHandler.buildRoleDataScopeExpression() 中实现:
DEPT_AND_SUB:dept_id IN (SELECT id FROM sys_dept WHERE id = :deptId OR FIND_IN_SET(:deptId, tree_path))DEPT:dept_id = :deptIdSELF:create_by = :userIdCUSTOM:dept_id IN (:...customDeptIds)
常见问题
为什么没有生效
排查:
- 是否有
DataScopeGuard写入的用户上下文(Token 是否包含dataScopes) - 查询是否使用 TypeORM
QueryBuilder(不是find()/findAndCount()) - 是否给方法加了
@DataPermission()且别名与 QueryBuilder 一致
别名怎么填
建议:
deptAlias/userAlias填 QueryBuilder 的主别名(例如u)- 多表查询时,确保部门字段/创建人字段确实在对应 alias 上
相关文件
| 文件路径 | 说明 |
|---|---|
src/common/enums/data-scope.enum.ts | 数据权限枚举 |
src/common/models/role-data-scope.model.ts | 角色数据权限模型 |
src/common/guards/data-scope.guard.ts | 数据范围上下文写入 |
src/common/decorators/data-permission.decorator.ts | DataPermission/SkipDataPermission |
src/common/plugins/data-permission.plugin.ts | QueryBuilder 注入逻辑 |
