5.9 数据权限
概述
角色数据权限(role.data_scope)是叠加在租户隔离之上的第二层行级过滤,四档可配置。模块通过 DataScope: ApplyDataScope("dept_id") 一行声明接线,仅作用于 Page/List/导出。
四档数据范围
| 档位 | 含义 | 过滤条件(作用于挂接模块的列表行) |
|---|---|---|
| 1 | 全部 | 不过滤(租户隔离之内全量) |
| 2 | 本部门及以下 | dept_id IN (自身部门 + 全部子孙部门 ID) |
| 3 | 本部门 | dept_id = 自身部门 ID |
| 4 | 仅本人 | id = 自身用户 ID |
档位文案走字典 role_data_scope(定稿种子已写入 dict/dict_item),角色表单"数据范围"下拉由字典驱动。
模块接线(一行声明)
go
// internal/logic/user.go(真实接线方式)
userSpec = ModuleSpec[...]{ ...,
DataScope: ApplyDataScope("dept_id"),
}ApplyDataScope(deptColumn) 返回 DataScopeFunc 闭包(logic/data_scope.go,共享工厂),挂进 applyFilters 末环:
text
Like → Eq → Keyword → Filter → DataScope
租户/软删隔离由 dao.Ctx 先于本链注入接线后 Page/List/导出自动生效。目前仅 user 模块接线(全库仅 goframe_user 有 dept_id 列);其他模块需要时声明同款闭包即可,无部门归属列的表可按需扩展"按创建人过滤"变体。
与租户隔离的叠加关系
text
第一层(硬规则,不可配置):租户隔离
dao.Ctx 注入 tenant_id IN(0,自身),先于一切业务过滤,任何模块不可绕过
第二层(可配置):角色数据范围
ModuleSpec.DataScope 挂点,applyFilters 末环叠加
两层独立:先租户收窄,再数据范围收窄安全语义(与直觉不同的点)
五条语义
- 多角色取最宽松(并集直觉)——合并发生在 LoadIdentity 缓存层(数值 min,1 最宽),热路径零额外查询
- 超管/平台完全绕过(IsPlatform 或 Roles 含 ROLE_SUPER_ADMIN,dataScopeBypass 双判断)
- 未分配部门(dept_id=0)用户挂档 2/3 时降级为"仅本人"(防 dept_id=0 意外放大可见面)
- 仅作用于 Page/List/导出(applyFilters 链);Detail/Update/Delete 不过滤,由 permission 节点与租户隔离兜底——管理端点授予权限时应评估该角色是否应有写权限
- 部门子树 deptSubtreeIds:拉全租户部门行(id,parent_id)后 BFS(树小直查无缓存;成热点可加短时缓存)
配置链路(修改如何生效)
text
role 表 data_scope 列(默认 1)
→ 角色表单"数据范围"下拉(字典 role_data_scope 驱动,值 1/2/3/4)
→ roleToData 归一(缺省回 1,gvalid in:1,2,3,4 校验)
→ 角色 Update 后 AfterUpdate 清该角色全部用户身份缓存
→ 生效(新请求的 LoadIdentity 读到新档位)Page/Detail 返回 dataScope + dataScopeText(字典驱动),前端可展示当前角色数据范围。
上下文读取
text
GetDataScope(ctx) 档位(1-全部;未注入视为全部——旧缓存缺字段回退安全值)
GetDeptID(ctx) 当前用户部门 ID(0=未分配)两者由 JWT 中间件从身份缓存注入,业务代码一般不直接读(基座 DataScope 闭包内部消费)。
已知边界
- 数据权限当前仅 user 模块接线;其他模块行级可见性由租户隔离单层兜底
- Detail/Update/Delete 不做行级过滤——越权改删他人记录的防线是接口 permission + 租户隔离
- 身份缓存 300s 窗口内直接改库的 dept_id/data_scope 不即时生效(API 路径均主动清缓存)
- 第 5 档自定义范围(如按部门多选)为预留能力,需部门关联表 + 角色表单扩展