- 平台治理: 租户用户权限层次、资源ACL、审批中心与审批模板、访问申请 - 存储: MinIO 存储进度迁移、对象存储安全加固与测试 - 计算: GPU 资源预留、compute 轮询与同步增强 - 权限: permission v2 迁移、权限安全验收测试 - 日志: 后端运行日志中文说明、操作日志整合 - 数据处理/评测: 数据转换与模型评测优化 Co-Authored-By: Claude <noreply@anthropic.com>
629 lines
28 KiB
Markdown
629 lines
28 KiB
Markdown
# 租户与用户权限体系梳理及纠偏流程
|
||
|
||
> 版本:v1.0
|
||
> 梳理日期:2026-08-20
|
||
> 适用范围:YG_FT 平台后端、前端、Compute Agent、MinIO、PostgreSQL 及离线部署目录
|
||
> 目的:明确租户、用户、角色、权限、资源和审批之间的层级关系,识别当前实现中的概念冲突,并为后续优化提供唯一设计口径。
|
||
|
||
## 一、结论摘要
|
||
|
||
当前系统已经具备用户登录、租户字段、租户成员、资源 ACL、审批、GPU 分配和资源级动作校验,但概念来源还没有完全收敛,主要表现为:
|
||
|
||
1. **用户和租户没有混为一个数据库对象,但业务流程中边界不清**:用户表保存 `tenant_id`,同时又通过 `tenant_members` 表表达租户关系,导致“主租户”和“成员关系”的判定规则并存。
|
||
2. **平台角色、租户角色、页面权限码、资源动作权限同时存在,但职责没有彻底分离**:`users.role`、`users.permissions`、`tenant_members.role`、`roles.permissions` 可能产生不一致。
|
||
3. **资源归属字段不统一**:部分资源使用独立的 `created_by`,部分任务把创建者放在 `payload.created_by`,部分资源的 `tenant_id` 允许为空,历史数据因此出现“ACL 已授权但列表不可见”的问题。
|
||
4. **租户创建、用户创建、成员加入和资源授权是四条不同流程,目前在页面上有交叉**:创建用户时直接写入租户成员,租户的 `owner_user_id` 却没有同步建立 owner 成员关系。
|
||
5. **项目表和项目字段仍在数据库及部分接口中保留**,但当前产品已经取消项目作为用户可见的业务隔离层,继续把 `project_id` 当权限条件会造成新的歧义。
|
||
6. **正确的目标模型应当是**:平台管理员管理平台身份和租户;用户通过租户成员关系进入一个或多个租户;资源属于一个租户并有一个所有者;资源使用通过 ACL 动作权限;高风险动作通过审批;GPU 和配额是资源使用前的运行时约束。
|
||
|
||
本文件的纠偏原则是:**先统一口径,再做兼容迁移;先保证后端判定一致,再调整前端菜单和数据库约束;历史字段暂不删除,但不再新增新的业务依赖。**
|
||
|
||
## 二、目标层级模型
|
||
|
||
```text
|
||
平台 Platform
|
||
│
|
||
├── 平台身份与平台角色
|
||
│ ├── platform_admin:平台管理员,可管理租户、用户、策略和全部资源
|
||
│ └── platform_user:普通平台用户,不因登录自动获得资源权限
|
||
│
|
||
├── 租户 Tenant
|
||
│ ├── 租户基本信息、状态、配额、留存策略
|
||
│ ├── 租户成员 TenantMember
|
||
│ │ ├── owner:租户所有者
|
||
│ │ ├── admin:租户管理员
|
||
│ │ ├── member:普通成员
|
||
│ │ └── viewer:只读成员
|
||
│ └── 租户内资源
|
||
│ ├── 数据集
|
||
│ ├── 基座模型
|
||
│ ├── 训练任务与训练模型
|
||
│ ├── 评测任务
|
||
│ ├── 推理任务
|
||
│ └── 数据处理/转换任务
|
||
│
|
||
├── 资源授权 Resource ACL
|
||
│ ├── principal:user 或 tenant role
|
||
│ ├── read:查看
|
||
│ ├── write:编辑
|
||
│ ├── execute:训练、推理、评测等使用
|
||
│ ├── download:下载或导出文件
|
||
│ ├── delete:删除
|
||
│ └── admin:资源授权和完整管理
|
||
│
|
||
├── 运行时资源
|
||
│ ├── Compute Node
|
||
│ ├── GPU
|
||
│ ├── GPU assignment:用户获批可使用哪些 GPU
|
||
│ ├── GPU reservation:任务当前占用哪些 GPU
|
||
│ └── Tenant quota reservation:租户当前预留了多少运行资源
|
||
│
|
||
└── 审批与审计
|
||
├── resource access:申请资源 ACL
|
||
├── gpu.assign:申请算力卡
|
||
├── tenant.quota.update:申请租户配额变更
|
||
├── 高风险删除/导出/合并操作
|
||
└── 全部结果写入审计日志
|
||
```
|
||
|
||
### 2.1 四个必须分开的概念
|
||
|
||
| 概念 | 正确含义 | 不应承担的职责 |
|
||
|---|---|---|
|
||
| 用户 User | 可登录平台的人或服务账号 | 不代表租户,也不自动代表资源权限 |
|
||
| 租户 Tenant | 资源、成员、配额和审计的隔离边界 | 不直接作为登录账号 |
|
||
| 角色 Role | 某个范围内的一组职责 | 不应直接等同于某个用户的全部资源权限 |
|
||
| 权限 Permission | 对页面或资源的具体操作能力 | 不应只靠前端按钮控制 |
|
||
|
||
## 三、当前实现盘点
|
||
|
||
### 3.1 用户身份层
|
||
|
||
当前主要实现位置:
|
||
|
||
- `users` 表:保存账号、密码哈希、平台角色、状态、页面权限 JSON、主租户字段。
|
||
- `sessions` 表:保存登录会话、过期时间和注销时间。
|
||
- `backend/app/core/auth.py`:解析 Token、校验用户状态、校验页面权限和租户上下文。
|
||
- `/users` 接口:当前只有管理员可以创建、修改、禁用和删除用户。
|
||
|
||
当前用户对象包含:
|
||
|
||
```text
|
||
id / username / display_name / status
|
||
role / protected / permissions
|
||
tenant_id
|
||
```
|
||
|
||
存在的混淆:
|
||
|
||
- `role=operator/viewer/user` 是平台用户字段,但 `tenant_members.role` 还有 `owner/admin/member/viewer`,两套角色名称不在同一层级。
|
||
- `role=user` 在创建接口中被接受,但设计文档主要定义的是 `admin/operator/viewer/guest`,前端又同时显示“普通用户”。
|
||
- 创建用户时直接生成 `tenant_members` 活跃成员记录;这相当于“创建用户”和“加入租户”一次完成,没有邀请、待确认或成员审批边界。
|
||
- `users.permissions` 是用户级 JSON,`roles` 表只提供查询接口,当前没有形成“角色变更自动计算权限”的唯一来源。
|
||
- 非管理员创建/更新用户时会被剥离 `compute` 和 `user-settings`,但初始化 SQL 中的 `u_operator` 种子数据仍包含 `compute`,代码默认值和 SQL 种子不一致。
|
||
|
||
### 3.2 租户层
|
||
|
||
当前主要实现位置:
|
||
|
||
- `tenants` 表:保存租户名称、状态、owner_user_id、配额和留存策略。
|
||
- `tenant_members` 表:保存租户与用户的多对多关系、租户角色、状态、邀请人和有效期。
|
||
- `tenant_membership()`、`user_tenant_ids()`:判定当前用户的租户范围。
|
||
- `/tenants/{tenant_id}/members`:管理员或租户 owner/admin 管理成员。
|
||
|
||
当前逻辑:
|
||
|
||
```text
|
||
当前用户租户范围 = users.tenant_id + tenant_members 中 status=active 的 tenant_id
|
||
当前请求租户 = X-Tenant-ID(仅在用户属于该租户时生效)
|
||
资源租户 = 资源 tenant_id,部分任务还会从 payload 中读取 tenant_id
|
||
```
|
||
|
||
主要问题:
|
||
|
||
1. `users.tenant_id` 和 `tenant_members` 都能表达租户关系,但没有明确谁是主数据、谁是兼容字段。
|
||
2. `tenants.owner_user_id` 创建租户时写入,但创建租户流程没有自动写入对应的 `tenant_members(role='owner')`。
|
||
3. 平台管理员创建普通用户时直接加入租户,租户管理员也可以直接添加成员,邀请/加入申请流程虽已具备接口,但不是主流程。
|
||
4. `tenant_id` 在资源表中有的为 `NOT NULL`,有的允许 `NULL`;历史资源的 `NULL` 租户需要特殊兼容判断。
|
||
5. 租户删除是状态删除,但成员、资源、ACL、审批、MinIO 对象和本地缓存的后续处理没有统一的生命周期编排入口。
|
||
|
||
### 3.3 页面权限层
|
||
|
||
页面权限由两部分组成:
|
||
|
||
```text
|
||
前端:路由 meta.permission + 侧边栏过滤 + 按钮显示
|
||
后端:MODULE_PERMISSIONS + get_current_user / require_admin
|
||
```
|
||
|
||
页面权限码包括:
|
||
|
||
```text
|
||
dashboard, fine-tune, model-eval, model-inference,
|
||
model-manage, dataset, data-process, data-convert,
|
||
compute, hardware, logs, user-settings
|
||
```
|
||
|
||
问题:
|
||
|
||
- 页面权限码是“能否进入模块”,不是“能否使用某一条数据或 GPU”,但历史实现中一度把 `compute` 页面权限阻断了普通用户读取自己获批 GPU 的接口,造成“有 GPU 分配但训练页面看不到”的问题。
|
||
- 普通用户的 `compute` 管理菜单应保持隐藏,但训练、推理、评测所需的个人节点/GPU查询应当是独立的自助只读接口。
|
||
- 前端权限控制和后端权限控制分别维护,按钮隐藏不能作为安全边界。
|
||
|
||
### 3.4 资源权限层
|
||
|
||
当前资源权限主要由以下条件组合:
|
||
|
||
```text
|
||
平台管理员旁路
|
||
或
|
||
资源属于当前租户
|
||
且(资源所有者 == 当前用户
|
||
或 ACL 明确授权
|
||
或当前用户是租户 owner/admin)
|
||
且满足具体动作 read/write/execute/download/delete/admin
|
||
```
|
||
|
||
已接入资源类型包括:
|
||
|
||
```text
|
||
dataset / model / trained_model / fine-tune / eval / inference
|
||
data_process / data_convert / compare / project(历史兼容)
|
||
```
|
||
|
||
当前差异:
|
||
|
||
| 资源 | 当前所有者来源 | 当前租户来源 | 主要风险 |
|
||
|---|---|---|---|
|
||
| datasets | `created_by` | `tenant_id`,部分历史为空 | 列表、详情、训练权限可能不一致 |
|
||
| models | `created_by` | `tenant_id`,基座模型存在平台共享规则 | 基座模型共享与训练产物共享容易混淆 |
|
||
| trained_models | `created_by` | `tenant_id` | 血缘来源、默认授权需统一 |
|
||
| fine_tune_tasks | 多数在 `payload.created_by` | 多数在 payload 或派生字段 | 结构化查询和授权容易漏字段 |
|
||
| eval_tasks | `created_by` | `tenant_id` | 与数据集/模型执行权限必须同时校验 |
|
||
| compare_tasks | 表字段和 payload 并存 | 表字段和 payload 并存 | 推理任务与模型使用权限容易分裂 |
|
||
| data_process_tasks | `created_by` | `tenant_id` | 处理结果派生数据集授权不明确 |
|
||
| data_convert_tasks | `created_by` | `tenant_id` | 历史表结构和初始化脚本存在演进痕迹 |
|
||
| projects | `create_by` 等历史字段 | `tenant_id` | 当前已取消项目业务层,但表和接口仍在 |
|
||
|
||
### 3.5 GPU 与运行时权限层
|
||
|
||
GPU 相关关系实际上有三层:
|
||
|
||
```text
|
||
gpus
|
||
└── gpu_assignments:管理员批准“用户可以使用哪张卡”
|
||
└── gpu_allocations / gpu_reservations:任务运行时“当前占用了哪张卡”
|
||
```
|
||
|
||
正确判定顺序应为:
|
||
|
||
1. 用户拥有租户成员资格且账号 active。
|
||
2. 用户已经获得该 GPU 的分配或管理员旁路。
|
||
3. 节点 online、enabled,GPU 存在且没有被其他任务占用。
|
||
4. 租户配额可以完成原子预留。
|
||
5. 训练、推理或评测任务提交到指定节点。
|
||
|
||
目前已经有分配申请、审批后写入 `gpu_assignments`、运行时预留和释放逻辑,但仍需把“审批结果、GPU 分配、配额预留、节点故障回收”统一成一个状态机。
|
||
|
||
## 四、当前数据库关系与问题
|
||
|
||
### 4.1 当前表的职责分组
|
||
|
||
```text
|
||
身份
|
||
├── users
|
||
├── sessions
|
||
└── roles
|
||
|
||
租户
|
||
├── tenants
|
||
├── tenant_members
|
||
└── tenant_quota_reservations
|
||
|
||
资源权限
|
||
├── acls
|
||
├── resource_access_requests
|
||
├── approval_templates
|
||
├── approval_instances
|
||
└── approval_steps
|
||
|
||
业务资源
|
||
├── datasets / dataset_files / dataset_file_versions / dataset_records
|
||
├── models / trained_models / model_lineage / model_artifacts
|
||
├── fine_tune_tasks / eval_tasks / compare_tasks
|
||
├── data_process_tasks / data_convert_tasks
|
||
└── projects / project_members(历史兼容)
|
||
|
||
运行资源
|
||
├── compute_nodes
|
||
├── gpus
|
||
├── gpu_assignments
|
||
├── gpu_allocations
|
||
├── gpu_reservations
|
||
└── resource_replicas / storage_objects / storage_cache_jobs
|
||
|
||
审计
|
||
└── audit_logs
|
||
```
|
||
|
||
### 4.2 数据库设计中的主要问题
|
||
|
||
#### 问题 A:租户归属字段可空且没有统一外键
|
||
|
||
`models`、`trained_models`、`datasets`、`eval_tasks`、`compare_tasks`、数据处理相关表的 `tenant_id` 演进过程不同,部分字段仍允许空值。多数资源表也没有对 `tenants(id)` 的外键约束。
|
||
|
||
后果:
|
||
|
||
- 资源可能被创建但没有租户归属。
|
||
- 列表接口和详情接口的可见性不同。
|
||
- ACL 授权是否跨租户无法仅靠数据库判断。
|
||
- 历史数据需要特殊分支,增加权限代码复杂度。
|
||
|
||
#### 问题 B:所有者字段不统一
|
||
|
||
当前同时存在 `created_by`、`owner_id`、`create_by` 和 `payload.created_by`。这会导致:
|
||
|
||
- 列表判断资源所有者时需要按资源类型写特殊逻辑。
|
||
- 用户删除时无法保证所有资源都能被一致地软删除。
|
||
- 审计目标和资源所有者可能不是同一个字段。
|
||
|
||
#### 问题 C:角色权限没有单一来源
|
||
|
||
当前存在以下可能来源:
|
||
|
||
```text
|
||
users.role
|
||
users.permissions(JSON)
|
||
tenant_members.role
|
||
roles.permissions(JSON)
|
||
前端 PermissionCode
|
||
后端 ALL_PERMISSIONS / MODULE_PERMISSIONS
|
||
```
|
||
|
||
如果更新了角色但没有同步用户权限 JSON,页面和接口可能出现不同结果;如果只修改前端权限码,后端仍可能拒绝;如果只修改 `tenant_members.role`,并不会自动改变平台页面权限。
|
||
|
||
#### 问题 D:资源 ACL 不是所有资源的唯一授权来源
|
||
|
||
基座模型在当前设计中对租户用户默认共享 read/execute;资源所有者又有隐式权限;租户管理员又有管理旁路;ACL 只是其中一层。这个设计可以成立,但必须在文档和代码中严格区分:
|
||
|
||
```text
|
||
平台共享规则 ≠ ACL 授权
|
||
资源所有权 ≠ 租户管理员权限
|
||
页面权限 ≠ 资源动作权限
|
||
GPU 分配 ≠ 资源 ACL
|
||
```
|
||
|
||
#### 问题 E:项目字段与当前产品边界不一致
|
||
|
||
`projects`、`project_members`、`project_id` 仍在数据库中存在,但当前产品已经决定不把项目作为用户可见的隔离层。它们应被标记为 legacy,不应再参与新资源的权限判定,也不应在新建页面继续要求填写。
|
||
|
||
## 五、目标唯一口径
|
||
|
||
### 5.1 用户与租户的最终关系
|
||
|
||
```text
|
||
一个用户可以属于多个租户
|
||
一个租户可以拥有多个用户
|
||
用户在一次请求中只能选择一个 active_tenant
|
||
资源必须归属于 active_tenant
|
||
```
|
||
|
||
推荐保留:
|
||
|
||
- `users.tenant_id`:暂时保留为兼容字段,表示用户的 primary tenant,不再作为多租户关系的唯一来源。
|
||
- `tenant_members`:升级为租户成员关系的唯一权威来源。
|
||
- `X-Tenant-ID`:明确表达当前请求租户,必须来自 active membership。
|
||
|
||
最终判定:
|
||
|
||
```text
|
||
有效租户 = tenant_members(status=active, 未过期)
|
||
primary tenant = users.tenant_id(仅兼容和默认登录上下文)
|
||
当前租户 = 请求 X-Tenant-ID,否则 primary tenant
|
||
```
|
||
|
||
### 5.2 平台角色与租户角色
|
||
|
||
推荐收敛为两层:
|
||
|
||
| 层级 | 字段/表 | 允许的角色 | 作用 |
|
||
|---|---|---|---|
|
||
| 平台层 | `users.platform_role`,兼容当前 `users.role` | `platform_admin`、`platform_user` | 管理租户、平台配置、平台用户和全局策略 |
|
||
| 租户层 | `tenant_members.role` | `owner`、`admin`、`member`、`viewer` | 管理本租户成员、资源和租户级审批 |
|
||
|
||
不建议继续把 `operator`、`user`、`member` 混在一个角色字段中。过渡期可以保留旧值,但新增代码只允许通过映射函数转换:
|
||
|
||
```text
|
||
admin/protected -> platform_admin
|
||
operator/user -> platform_user + tenant_members.member
|
||
viewer -> platform_user + tenant_members.viewer
|
||
```
|
||
|
||
### 5.3 页面权限与资源动作
|
||
|
||
页面权限只解决“能否进入模块”:
|
||
|
||
```text
|
||
module permission: dataset / fine-tune / model-inference / ...
|
||
```
|
||
|
||
资源动作解决“能否操作某个资源”:
|
||
|
||
```text
|
||
read / write / execute / download / delete / admin
|
||
```
|
||
|
||
统一授权函数输入应固定为:
|
||
|
||
```text
|
||
authorize(
|
||
actor_id,
|
||
active_tenant_id,
|
||
resource_type,
|
||
resource_id,
|
||
action,
|
||
)
|
||
```
|
||
|
||
推荐判定顺序:
|
||
|
||
1. 会话有效且用户 active。
|
||
2. 当前租户 active,用户是该租户成员。
|
||
3. 平台管理员旁路,或资源属于当前租户。
|
||
4. 资源所有者拥有默认动作集合。
|
||
5. 租户 owner/admin 按租户范围拥有管理动作。
|
||
6. ACL 明确授予当前用户或租户角色对应动作。
|
||
7. 对 download、delete、admin 等高风险动作执行额外审批检查。
|
||
|
||
### 5.4 资源标准字段
|
||
|
||
所有新资源应统一具备:
|
||
|
||
```text
|
||
id
|
||
tenant_id NOT NULL
|
||
created_by NOT NULL
|
||
created_at
|
||
updated_at
|
||
deleted_at
|
||
deleted_by
|
||
```
|
||
|
||
历史 `owner_id`、`create_by` 和 `payload.created_by` 只在迁移阶段读取,最终写入标准字段。任务配置 JSON 可以保留业务参数,但不再存放权限事实。
|
||
|
||
## 六、标准业务流程
|
||
|
||
### 6.1 创建租户流程
|
||
|
||
```text
|
||
平台管理员
|
||
│
|
||
├─ 创建租户基本信息
|
||
├─ 设置租户配额和状态=active
|
||
├─ 指定或创建租户 owner
|
||
├─ 写入 tenant_members(tenant_id, owner_user_id, owner)
|
||
├─ 初始化审批策略
|
||
└─ 记录 tenant.create / tenant.member.add 审计
|
||
```
|
||
|
||
纠偏要求:`tenants.owner_user_id` 和 `tenant_members(role='owner')` 必须在同一事务中维护;不能只写一个。
|
||
|
||
### 6.2 创建用户与加入租户流程
|
||
|
||
#### 平台管理员创建用户
|
||
|
||
```text
|
||
平台管理员
|
||
│
|
||
├─ 创建 users 记录
|
||
├─ 选择初始平台角色:platform_user 或 platform_admin
|
||
├─ 选择 primary tenant
|
||
├─ 写入 tenant_members
|
||
│ ├─ owner 仅平台管理员明确指定时允许
|
||
│ └─ 普通用户默认 member 或 viewer
|
||
├─ 生成页面权限快照(过渡期)
|
||
└─ 记录 user.create / tenant.member.add 审计
|
||
```
|
||
|
||
#### 已存在用户加入新租户
|
||
|
||
```text
|
||
租户 owner/admin 或平台管理员
|
||
│
|
||
├─ 发出邀请或创建加入申请
|
||
├─ tenant_members.status = pending
|
||
├─ 被邀请用户接受
|
||
├─ tenant_members.status = active
|
||
└─ 用户选择 X-Tenant-ID 后访问该租户资源
|
||
```
|
||
|
||
纠偏要求:不能把“创建登录用户”和“拥有某租户全部资源”理解为同一件事;新用户默认没有任何资源 ACL,也没有 GPU 分配。
|
||
|
||
### 6.3 创建业务资源流程
|
||
|
||
```text
|
||
登录用户
|
||
│
|
||
├─ 会话校验
|
||
├─ 确定 active_tenant
|
||
├─ 校验租户 active 和用户成员状态
|
||
├─ 写入资源 tenant_id + created_by
|
||
├─ 写入资源业务表
|
||
├─ 写入 MinIO 对象元数据(如有文件)
|
||
├─ 生成资源血缘/版本快照
|
||
└─ 记录资源创建审计
|
||
```
|
||
|
||
数据处理结果生成数据集时,必须继承任务的租户和创建者;不能因为是系统生成就把 `tenant_id` 和 `created_by` 留空。
|
||
|
||
### 6.4 资源查看和授权流程
|
||
|
||
```text
|
||
用户请求资源列表/详情
|
||
│
|
||
├─ 页面权限检查
|
||
├─ active_tenant 检查
|
||
├─ 资源 tenant_id 检查
|
||
├─ 所有者 / 租户角色 / ACL 判定
|
||
├─ 返回资源元数据
|
||
└─ 文件、版本、MinIO 对象再次按同一资源动作校验
|
||
```
|
||
|
||
资源授权:
|
||
|
||
```text
|
||
资源所有者或租户管理员/平台管理员
|
||
│
|
||
├─ 选择用户或租户角色
|
||
├─ 选择 read/write/execute/download 等动作
|
||
├─ 设置有效期
|
||
├─ 写入 ACL 或创建访问审批
|
||
└─ 记录授权来源、授权人和审计
|
||
```
|
||
|
||
### 6.5 训练、推理、评测使用资源流程
|
||
|
||
```text
|
||
创建任务
|
||
│
|
||
├─ 页面权限:fine-tune / model-inference / model-eval
|
||
├─ active_tenant 和任务资源归属校验
|
||
├─ 基座模型:平台共享规则或 model.execute
|
||
├─ 训练模型:trained_model.execute
|
||
├─ 数据集:dataset.execute
|
||
├─ GPU assignment:用户是否获批使用所选卡
|
||
├─ 节点:GPU 所属节点是否 online/enabled
|
||
├─ 原子预留 GPU 和租户配额
|
||
├─ MinIO/节点缓存准备
|
||
├─ 提交计算任务
|
||
└─ 成功、失败、取消、超时、节点故障统一释放预留
|
||
```
|
||
|
||
### 6.6 高风险操作审批流程
|
||
|
||
```text
|
||
用户发起高风险操作
|
||
│
|
||
├─ 检查当前用户是否有基础资源权限
|
||
├─ 检查是否为管理员旁路
|
||
├─ 匹配 tenant + action + resource_type 审批策略
|
||
├─ 创建 pending 审批实例
|
||
├─ 审批人从当前会话确定
|
||
├─ approved -> 幂等执行具体效果
|
||
│ ├─ ACL 授权
|
||
│ ├─ GPU 分配
|
||
│ └─ 配额更新
|
||
├─ rejected/cancelled/expired -> 不产生资源效果
|
||
└─ 全流程记录审计
|
||
```
|
||
|
||
## 七、纠偏开发计划
|
||
|
||
### 阶段 0:冻结口径
|
||
|
||
1. 书面确认:不再把项目作为新业务隔离层。
|
||
2. 确认平台角色只有平台管理员和平台用户两类;租户职责由 `tenant_members.role` 表达。
|
||
3. 确认 `tenant_members` 是租户关系权威表,`users.tenant_id` 仅作为 primary tenant 兼容字段。
|
||
4. 确认所有资源必须有 `tenant_id`、`created_by`、生命周期字段。
|
||
5. 确认基座模型共享不等于训练模型和数据集共享。
|
||
|
||
### 阶段 1:身份和租户纠偏
|
||
|
||
1. 为平台角色建立统一映射,停止新增 `role='user'` 的特殊逻辑。
|
||
2. 用户创建、租户创建、成员邀请、成员接受、成员移除统一走成员服务。
|
||
3. 创建租户时同步 owner 成员记录。
|
||
4. 租户成员变更、用户禁用、用户软删除时统一撤销会话、GPU 分配、ACL、待审批流程。
|
||
5. 新增当前租户查询和切换接口,前端显示“当前租户”,避免仅显示用户主租户。
|
||
|
||
### 阶段 2:资源数据纠偏
|
||
|
||
1. 对所有资源表补齐并回填 `tenant_id`、`created_by`、`created_at`、`updated_at`。
|
||
2. 将任务 payload 中的创建者和租户迁移到结构化字段。
|
||
3. 对历史 `tenant_id IS NULL` 数据按以下顺序处理:
|
||
- 能从创建者找到租户的,回填创建者所属租户。
|
||
- 系统生成数据集能从源任务找到租户的,继承源任务租户。
|
||
- 仍无法确定归属的,进入待治理清单,不自动公开。
|
||
- 管理员明确授权的历史资源可临时通过用户级 ACL 访问,直到完成归属迁移。
|
||
4. 为资源和租户建立必要索引;在数据清洗完成后再逐步收紧 `NOT NULL` 和外键。
|
||
|
||
### 阶段 3:权限判定收敛
|
||
|
||
1. 建立统一 `authorize()` 服务,所有列表、详情、文件、版本、下载、MinIO、缓存和任务接口调用同一套判定。
|
||
2. 页面权限只用于模块入口;资源动作必须在后端独立判断。
|
||
3. 统一基座模型、训练模型、数据集和任务的血缘权限来源。
|
||
4. 统一 GPU assignment、GPU reservation 和租户配额 reservation 的状态流转。
|
||
5. 审批实例增加幂等键和过期处理任务,避免重复审批和重复执行。
|
||
|
||
### 阶段 4:数据库和初始化脚本
|
||
|
||
1. 将完整初始化 SQL 与迁移 SQL 的职责分开:新库只执行一份完整脚本,旧库执行版本化迁移。
|
||
2. 初始化 SQL 中统一种子用户、种子角色、默认租户和租户成员关系,不能出现 operator 仍拥有普通用户不应有的 `compute` 权限而代码又剥离该权限的矛盾。
|
||
3. 给所有需要的关联增加外键或由应用层统一保证引用完整性。
|
||
4. 为 `tenant_id + status`、`created_by + deleted_at`、ACL 主体、审批状态和 GPU 状态建立索引。
|
||
5. 主工程与 `docker/offline/src` 的完整 SQL、迁移、代码和部署文档做 SHA-256 一致性校验。
|
||
|
||
### 阶段 5:前端流程纠偏
|
||
|
||
1. 用户管理页面分别显示平台角色、所属租户、租户角色、页面权限和资源权限,不再把它们放在一个“权限”字段中。
|
||
2. 租户详情页显示成员、租户角色、配额使用、审批策略和资源统计。
|
||
3. 资源详情页显示资源所属租户、创建者、授权主体、动作、有效期和授权来源。
|
||
4. 训练/推理/评测页面只显示当前用户有 `execute` 权限且已获批 GPU 的资源。
|
||
5. 对“无权访问”“未授权”“审批中”“资源已删除”“租户已停用”分别展示原因,不统一显示为数据为空或 500。
|
||
|
||
## 八、建议的验收矩阵
|
||
|
||
| 场景 | 预期结果 |
|
||
|---|---|
|
||
| 用户未加入租户访问租户资源 | 403 |
|
||
| 用户属于租户但没有资源 ACL | 资源列表不可见,详情 403 |
|
||
| 用户获得 dataset.read | 数据集可见,可预览,但不能训练或上传 |
|
||
| 用户获得 dataset.execute | 数据集可用于训练/评测,但不能编辑或下载 |
|
||
| 用户获得 dataset.download | 可以下载文件,但不能训练 |
|
||
| 用户获得 trained_model.execute | 可以推理/评测,不能导出 |
|
||
| 用户获得 trained_model.download | 可以导出或下载,不能执行推理 |
|
||
| 用户获批 GPU 但没有数据集 execute | 不能创建训练任务 |
|
||
| 用户有数据集 execute 但没有 GPU assignment | 不能创建训练任务 |
|
||
| 管理员撤销 ACL | 列表、详情、执行、下载均立即重新校验 |
|
||
| 用户被禁用 | 会话失效,成员和 ACL 不能继续生效 |
|
||
| 租户被停用 | 租户资源不可创建和执行,运行任务进入待处理/失败策略 |
|
||
| 历史资源 tenant_id 为空且无 ACL | 普通用户不可见 |
|
||
| 历史资源 tenant_id 为空且有管理员直接用户 ACL | 仅指定用户在有效期内可见和按动作使用 |
|
||
| 资源删除 | 资源不可见,ACL、审批、对象清理流程被触发 |
|
||
| MinIO 不可用 | 资源元数据权限仍可判断,涉及对象的操作返回明确存储故障 |
|
||
|
||
## 九、纠偏完成判定
|
||
|
||
满足以下条件,才认为租户与用户权限完成统一,而不是“接口能够调用”:
|
||
|
||
- `tenant_members` 成为租户关系唯一权威来源,`users.tenant_id` 只作为兼容主租户。
|
||
- 平台角色和租户角色职责分离,角色权限不再通过多个 JSON/字段重复维护。
|
||
- 新资源全部具备非空租户和创建者,历史资源有明确归属或进入治理清单。
|
||
- 所有资源接口、文件接口、MinIO 接口和任务接口使用统一动作授权。
|
||
- 数据集、基座模型、训练模型和任务之间的派生关系不会自动扩大访问范围。
|
||
- 用户创建、成员邀请、资源授权、GPU 申请、配额申请和审批都能追溯到用户、租户、资源、动作和请求 ID。
|
||
- 初始化 SQL、迁移 SQL、在线数据库和离线源码目录的表结构与权限逻辑一致。
|
||
- 双用户、跨租户、ACL 撤销、权限过期、用户禁用、租户停用、GPU 冲突和 MinIO 故障场景全部通过验证。
|
||
|
||
## 十、2026-08-20 本轮改造结果
|
||
|
||
本轮已按目标层级模型完成一轮可运行的后端、数据库和前端收敛:
|
||
|
||
1. `users.platform_role` 已作为平台角色标准字段,值为 `platform_admin` 或 `platform_user`;历史 `users.role` 继续返回,供旧客户端兼容。
|
||
2. `tenant_members` 已作为租户成员关系的权威来源。创建租户时会在同一事务中写入 `owner` 成员;创建用户时单独使用 `tenant_role` 写入成员角色,不再因为平台角色是管理员就自动成为租户 owner。
|
||
3. 用户接口返回 `platform_role`、`tenant_memberships` 和 `tenant_count`,组织页面已区分平台身份与租户成员关系。
|
||
4. `fine_tune_tasks` 已增加结构化 `tenant_id`、`created_by`、`deleted_at`、`deleted_by`;训练列表、详情和 owner 判定优先使用结构化字段,历史 payload 仅作回退。
|
||
5. 新建数据集、训练、评测、推理任务会绑定当前认证租户;普通用户不能仅通过请求体伪造其他租户。平台管理员仍可为指定租户创建资源。
|
||
6. 普通用户的资源列表继续通过租户成员、所有者和 ACL 统一过滤;项目不再出现在前端路由、组织页面和新权限判断中。
|
||
7. 租户删除已补充 `deleted_at/deleted_by` 软删除,并同步禁用该租户的成员关系。
|
||
8. `000_full_init.sql` 已包含本轮标准字段、索引、owner 成员修复和无项目业务依赖的种子数据;`docker/offline/src/backend/app/db/sql` 仅保留这一份完整初始化脚本。
|
||
9. `docker/offline/src` 已同步当前 backend、frontend、compute 源码和构建后的 `frontend-dist`。
|
||
|
||
本轮已在线验证:数据库字段存在、普通用户身份返回、用户租户成员关系返回、租户 owner 自动建立、租户软删除、跨租户伪造创建被 403 拒绝;前端 `npm run build` 通过。项目表、项目字段及旧项目 API 仅保留历史兼容读取,不再作为新业务隔离条件。
|
||
|
||
## 十一、下一步开发计划
|
||
|
||
1. 为资源详情和资源授权页面补充租户、创建者、授权来源、有效期和动作权限的可视化信息。
|
||
2. 将数据处理、数据转换等派生资源的租户和创建者继承改为统一服务,并清理历史 `tenant_id IS NULL` 待治理清单。
|
||
3. 将 GPU assignment、GPU reservation、租户配额 reservation 和审批实例整理为同一状态机,补充节点故障回收验收。
|
||
4. 执行双租户、双用户、ACL 过期/撤销、用户禁用、租户停用、MinIO 故障和多 GPU 冲突的完整回归测试。
|