Skip to content

数据权限

当你要实现“只能看自己/本部门/本部门及子部门数据”,或排查列表页数据越权问题时,从这篇开始。

数据权限是一种行级安全控制机制,用于限制用户只能访问其权限范围内的数据行。与功能权限(控制用户能执行哪些操作)不同,数据权限控制用户能看到哪些数据。本文涵盖:

  • 数据范围类型与并集策略
  • 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_SUBdept_id IN (SELECT id FROM sys_dept WHERE id = :deptId OR FIND_IN_SET(:deptId, tree_path))
  • DEPTdept_id = :deptId
  • SELFcreate_by = :userId
  • CUSTOMdept_id IN (:...customDeptIds)

常见问题

为什么没有生效

排查:

  1. 是否有 DataScopeGuard 写入的用户上下文(Token 是否包含 dataScopes
  2. 查询是否使用 TypeORM QueryBuilder(不是 find() / findAndCount()
  3. 是否给方法加了 @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.tsDataPermission/SkipDataPermission
src/common/plugins/data-permission.plugin.tsQueryBuilder 注入逻辑

下一步

基于 MIT 许可发布 · 由 ❤️ 和 ☕ 驱动 · 支持作者