完善部分平台治理功能,及修改看板缺陷

This commit is contained in:
wangjiming
2026-08-10 11:41:16 +08:00
parent 75cc105ebc
commit f809825a7d
18 changed files with 1873 additions and 362 deletions

View File

@@ -0,0 +1,411 @@
# 平台治理功能使用指南
> 版本v1.0
> 日期2026-08-10
> 适用版本YG Fine-Tune Platform v1.0+
---
## 目录
1. [快速入门](#1-快速入门)
2. [用户与权限管理](#2-用户与权限管理)
3. [GPU 算力分配](#3-gpu-算力分配)
4. [资源可见性与隔离](#4-资源可见性与隔离)
5. [审批流程管理](#5-审批流程管理)
6. [审计日志查询](#6-审计日志查询)
7. [常见问题与排查](#7-常见问题与排查)
---
## 1. 快速入门
### 1.1 平台治理是什么?
平台治理是一套**多租户、多角色、细粒度权限控制**体系,用于在多人协作使用 AI 微调平台时,确保:
- 每个用户只能看到和操作自己有权限的资源
- GPU 算力按需分配,避免资源争抢
- 高风险操作(删除、停止任务)有审批记录可追溯
- 所有操作都有审计日志
### 1.2 三种内置角色
| 角色 | 能做什么 | 不能做什么 |
|---|---|---|
| **超级管理员 (admin)** | 全部操作;管理用户、分配 GPU、审批、查看全部资源 | — |
| **操作员 (operator)** | 创建数据集/模型、训练/评测/推理任务 | 管理用户、分配 GPU、修改他人权限 |
| **观察员 (viewer)** | 查看被授权的资源 | 创建或修改任何资源 |
### 1.3 入口在哪里?
所有治理功能集中在左侧导航栏的 **「系统设置」** 分组下:
```
系统设置
├── 用户设置 ← 用户 CRUD + 角色权限 + 密码管理
├── 租户管理 ← 组织/团队(可选)
├── 项目空间 ← 项目级资源隔离(可选)
├── 审批模板 ← 定义哪些操作需要审批
├── 审批中心 ← 处理待审批请求
└── 审计日志 ← 查看所有操作记录
```
> ⚠️ 以上菜单**只有 admin 用户能看到**。普通用户登录后不会出现这些入口。
---
## 2. 用户与权限管理
### 2.1 创建用户
**路径**`用户设置``创建用户`
1. 以 admin 身份登录平台
2. 进入「用户设置」页面
3. 点击右上角「创建用户」按钮
4. 填写信息:
- **账号**:登录用户名(如 `zhangsan`
- **显示名称**:如 `张三`
- **密码**:初始密码(默认 `Platform@123`
- **角色**:选择 `admin` / `operator` / `viewer`
5. 点击保存
创建后用户可以立即用该账号登录。
### 2.2 管理用户权限
**路径**`用户设置` → 用户列表 → 操作列「页面权限」
#### 给普通用户分配业务模块权限
点击某用户的「页面权限」按钮,弹出对话框:
```
为 张三 分配可访问的页面模块:
☑ 服务看板 ☑ 模型训练 ☑ 模型评测
☑ 模型推理 ☑ 模型管理 ☑ 数据集管理
☐ 数据处理 ☐ 数据类型转换 ☐ 算力节点 ← 不勾选则不可见
☑ 平台性能 ☑ 查看日志
[取消] [保存]
```
勾选需要的模块,点「保存」即可。
> **注意**
> - 「用户与权限」这个选项**只有 admin 能看到**,其他用户即使被赋权也不会显示
> - admin 用户的权限**不可更改**,始终是全选状态且只读
#### 限制说明
| 权限码 | 说明 | 谁能拥有 |
|---|---|---|
| `user-settings` | 用户设置、租户管理、项目空间、审批、审计日志 | **仅 admin** |
| `compute` | 算力节点、GPU 分配 | **仅 admin** |
| 其他业务权限 | 训练、评测、推理、模型、数据集等 | admin 可分配给任何人 |
### 2.3 重置用户密码
**两种方式**
**方式一:管理员重置**
1. 在用户列表中找到目标用户
2. 点击「重置密码」
3. 输入新密码,确认
**方式二:用户自行修改**
1. 用户登录后在「用户设置」页面点击「修改密码」按钮
2. 输入旧密码 + 新密码(至少 6 位)
3. 确认修改
### 2.4 删除用户
**路径**`用户设置` → 用户列表 → 操作列「删除」
> ⚠️ 删除用户时会**级联清理**其所有关联数据:
> - 该用户创建的数据集、基座模型、微调产物、评测任务
> - 该用户的 ACL 授权记录、GPU 分配记录
> - 该用户的审批实例、审计日志、项目成员关系
> - **训练任务保留不删**(避免算力节点上的物理任务数据不一致)
---
## 3. GPU 算力分配
### 3.1 为什么需要 GPU 分配?
当服务器有多张 GPU 卡(如 8×A800需要指定**哪个用户能用哪张卡**
- 避免两个人同时选同一张卡导致训练冲突
- 按团队/项目隔离算力资源
- 控制每个用户的 GPU 配额
### 3.2 分配 GPU仅 admin
**路径**`算力节点``GPU 分配` 标签页
1. 以 admin 登录,进入「算力节点」页面
2. 点击顶部的 **「GPU 分配」** 标签(只有 admin 可见)
3. 点击 **「分配 GPU」** 按钮
4. 填写:
- **算力节点**:选择节点(如 `gpu-node-01`
- **GPU 序号**卡号0, 1, 2, ... 7
- **用户**:选择要分配给谁
5. 点「确认分配」
示例:把节点 `gpu-node-01` 的第 0、1 号卡分配给用户 `zhangsan`
```
┌─────────────────────────────────────┐
│ 分配 GPU [×] │
├─────────────────────────────────────┤
│ 算力节点: [gpu-node-01 ▼] │
│ GPU 序号: [0 ▲] │
│ 用户: [zhangsan ▼] │
│ │
│ [取消] [确认分配] │
└─────────────────────────────────────┘
```
分配后的效果:
| 用户 | 可用 GPU |
|---|---|
| admin | 全部 GPU不需要显式分配 |
| zhangsan | gpu-node-01 的 0、1 号卡 |
| lisi | (未分配,不可用) |
### 3.3 撤销分配
在 GPU 分配列表中,每条记录右侧有「撤销」按钮,点击后确认即可移除该分配。
### 3.4 用户视角:创建训练任务时的 GPU 选择
- **admin**:下拉列表显示全部可用 GPU
- **被分配了 GPU 的用户**:只显示被分配给自己的卡
- **未分配任何 GPU 的用户**:显示提示「未分配 GPU请联系管理员」无法提交训练任务
---
## 4. 资源可见性与隔离
### 4.1 自动生效的隔离规则
无需手动配置,以下规则自动生效:
| 资源类型 | admin 看到 | 普通用户看到 |
|---|---|---|
| **基座模型**(容器内注册的本地模型) | 全部 | **全部**(共享资源,有 model-manage 权限即可见) |
| **数据集** | 全部 | **自己创建的** + 被 ACL 授权的 |
| **微调产物**(训练输出的模型) | 全部 | **自己训练的** + 被 ACL 授权的 |
| **评测任务** | 全部 | **自己创建的** + 被 ACL 授权的 |
| **推理/对比任务** | 全部 | **自己创建的** + 被 ACL 授权的 |
### 4.2 实际场景示例
假设有三个用户:**admin**、**zhangsan**(算法工程师)、**lisi**(标注员)
```
zhangsan 上传了数据集 ds_alpaca、ds_sharegpt
zhangsan 训练出了模型 ft_qwen_001
lisi 上传了数据集 ds_label
admin 注册了基座模型 Qwen3-1.7B
```
各用户看到的资源:
| 用户 | 数据集 | 基座模型 | 微调产物 |
|---|---|---|---|
| **admin** | ds_alpaca, ds_sharegpt, ds_label (3个) | Qwen3-1.7B | ft_qwen_001 |
| **zhangsan** | ds_alpaca, ds_sharegpt (2个) | Qwen3-1.7B | ft_qwen_001 |
| **lisi** | ds_label (1个) | Qwen3-1.7B | (无) |
### 4.3 ACL 资源授权(高级用法)
如果 zhangsan 想让 lisi 也能看到自己的数据集 `ds_alpaca`
> 此功能需要在资源详情页提供「资源授权」按钮(前端已预留接口),当前可通过 API 直接操作:
```bash
# 授予 lisi 对 ds_alpaca 的读权限
curl -X PUT /modelTF/resources/dataset/ds_alpaca_id/acl \
-H "Authorization: Bearer platform-token-admin" \
-d '{
"acls": [
{"principal_type": "user", "principal_id": "lisi_id", "permission": "read"}
]
}'
```
---
## 5. 审批流程管理
### 5.1 哪些操作会触发审批?
| 操作 | 触发条件 | 处理方式 |
|---|---|---|
| 删除他人的数据集 | 非 admin 删除别人创建的数据集 | 创建审批实例 或 admin 直接执行 |
| 删除他人的模型 | 非 admin 删除别人创建的模型 | 同上 |
| 停止他人的训练任务 | 非 admin 停止别人发起的任务 | 同上 |
| 归档/删除项目空间 | 存在待审批变更时 | 拒绝执行 |
**核心规则**admin 做任何操作都直接执行(旁路);普通用户操作他人资源时进入审批流程。
### 5.2 审批流程示意
```
普通用户 lisi 尝试删除 zhangsan 的数据集
┌───────────────────────┐
│ 后端检查:是 admin 吗? │
└──────┬────────────────┘
│ 否
┌───────────────────────┐
│ 创建审批实例 │
│ status = pending │
│ 返回 202待审批
└──────────┬────────────┘
┌───────────────────────┐
│ admin 在「审批中心」看到 │
│ 这条待审批请求 │
│ 点击「通过」或「拒绝」 │
└──────────┬────────────┘
┌─────┴─────┐
│ │
通过 拒绝
│ │
▼ ▼
执行删除 不执行
+审计日志 +审计日志
```
### 5.3 管理审批
**路径**`系统设置``审批中心`
1. 查看待审批列表status=pending
2. 点击某条记录查看详情
3. 决策:「通过」或「拒绝」
4. 决策结果自动执行对应操作并记录审计日志
**审批模板**`系统设置``审批模板`定义每种操作需要几步审批、每步谁来审。默认模板都是单步admin 审批即可)。
---
## 6. 审计日志查询
### 6.1 什么是审计日志?
平台上所有**写操作**和**敏感操作**都会自动记录审计日志,包括:
- 用户创建/删除/修改
- 权限变更
- GPU 分配/撤销
- 资源上传/删除
- 训练任务启动/停止
- 审批决策
### 6.2 查询审计日志
**路径**`系统设置``审计日志`
支持筛选条件:
| 筛选项 | 说明 |
|---|---|
| 操作人 | 按用户 ID 过滤 |
| 动作类型 | 如 `user.create`, `dataset.delete`, `gpu.assign` 等 |
| 目标资源类型 | dataset / model / fine_tune_task 等 |
| 时间范围 | 开始时间 ~ 结束时间 |
### 6.3 导出审计日志
审计日志页面底部有「导出 CSV」按钮导出的文件包含当前筛选条件下的全部记录可用于合规审计或问题追溯。
### 6.4 日志保留策略
审计日志受**留存策略**控制(`系统设置` → 租户管理 → 绑定留存策略)。默认保留 30 天,超期自动清理。
---
## 7. 常见问题与排查
### Q1: 普通用户看不到某个菜单?
检查两件事:
1. 该用户是否有对应的**权限码**admin 在「用户设置」→「页面权限」中分配)
2. 该菜单是否属于 **admin 专属**(如「用户设置」「算力节点」——这些对非 admin 永远不可见)
### Q2: 用户创建训练任务时报错"无权使用所选 GPU"
说明该用户没有被分配所选择的 GPU 卡。解决方法:
1. admin 进入「算力节点」→「GPU 分配」标签页
2. 为该用户分配对应的 GPU
3. 用户刷新页面重新选择 GPU
### Q3: 删除用户后看板还显示残留数据?
正常情况下 `delete_user` 会级联清理关联数据。如果仍有残留:
- **训练任务**:设计上保留不删(避免算力节点物理数据不一致),这是预期行为
- **登录时长排行**:可能来自旧的 session 记录,不影响功能,新登录后会更新
### Q4: 审批实例一直 pending 没人处理?
审批实例需要 admin 在「审批中心」手动处理。如果长时间无人处理:
- 可以在数据库中直接将 `approval_instances.status` 改为 `rejected`
- 或者由 admin 直接以自身身份执行该操作admin 有旁路权限)
### Q5: 如何查看当前所有 GPU 分配情况?
```bash
# admin 调用接口
curl -H "Authorization: Bearer platform-token-admin" \
/modelTF/compute/gpu-assignments
```
返回格式:
```json
{
"data": [
{
"id": "ga_xxx",
"node_name": "A800 训练节点",
"gpu_index": 0,
"display_name": "张三",
"assigned_at": "2026-08-10T10:00:00"
}
]
}
```
### Q6: 用户忘记密码怎么办?
两种方案:
1. **admin 重置**:在「用户设置」→ 用户列表 →「重置密码」
2. **用户自助修改**:用户登录后点击「修改密码」(需知道旧密码)
如果是完全忘记且不是 admin只能由 admin 重置。
---
## 附录API 快速参考
| 功能 | 方法 | 路径 | 鉴权 |
|---|---|---|---|
| 查看我的 GPU | GET | `/compute/my-gpus` | 登录用户 |
| 查看 GPU 分配 | GET | `/compute/gpu-assignments` | admin |
| 分配 GPU | POST | `/compute/gpu-assignments` | admin |
| 撤销 GPU 分配 | DELETE | `/compute/gpu-assignments/{id}` | admin |
| 修改自己的密码 | POST | `/users/me/password` | 登录用户 |
| 查看审计日志 | GET | `/system/audit-logs` | admin |
| 导出审计日志 | GET | `/system/audit-logs/export` | admin |
| 查看审批列表 | GET | `/approvals` | 登录用户 |
| 审批决策 | POST | `/approvals/:id/steps/:idx/decision` | 审批人 |

805
docs/permissions-design.md Normal file
View File

@@ -0,0 +1,805 @@
# 平台权限设计文档
> 版本v1.0
> 日期2026-08-02
> 状态:设计基线,供后端实现和前端联调参照
---
## 目录
1. [设计目标](#1-设计目标)
2. [整体架构](#2-整体架构)
3. [角色体系](#3-角色体系)
4. [页面权限码](#4-页面权限码)
5. [资源所有权与可见性](#5-资源所有权与可见性)
6. [资源级 ACL访问控制列表](#6-资源级-acl访问控制列表)
7. [GPU 算力分配与隔离](#7-gpu-算力分配与隔离)
8. [审批拦截机制](#8-审批拦截机制)
9. [审计日志](#9-审计日志)
10. [接口鉴权流程](#10-接口鉴权流程)
11. [数据库表结构](#11-数据库表结构)
12. [API 接口清单](#12-api-接口清单)
13. [前端权限控制](#13-前端权限控制)
14. [安全设计补充](#14-安全设计补充)
15. [实施计划](#15-实施计划)
---
## 1. 设计目标
| 目标 | 说明 |
|---|---|
| **数据隔离** | 用户自己创建的数据集、模型、训练任务默认只有自己可见可操作;管理员可见全部 |
| **权限分层** | 页面级(菜单/路由可见性)+ 资源级(单条数据的读/写/删)两层控制 |
| **GPU 管控** | 多卡服务器上,管理员可指定哪些用户能使用哪些 GPU 卡 |
| **审批拦截** | 删除他人资源、停止他人任务、发布模型等高风险操作需审批或管理员旁路 |
| **审计可追溯** | 所有写操作和敏感操作产生审计日志,可按用户、动作、资源、时间筛选 |
| **权限最小变更** | 只有管理员可修改用户角色和权限码;普通用户无法提权 |
---
## 2. 整体架构
```
┌──────────────────────────────────────────────────────┐
│ 前端Vue3
│ ┌────────────┐ ┌────────────┐ ┌───────────────┐ │
│ │ 路由守卫 │ │ 侧边栏过滤 │ │ 页面内按钮控制 │ │
│ │ (permission)│ │ (permission)│ │ (ACL/owner) │ │
│ └──────┬─────┘ └──────┬─────┘ └───────┬───────┘ │
│ └───────────────┴─────────────────┘ │
│ │ HTTP (Bearer token) │
└─────────────────────────┼────────────────────────────┘
┌─────────────────────────┼────────────────────────────┐
│ 后端FastAPI
│ ┌──────────────┐ ┌────┴───────┐ ┌──────────────┐ │
│ │ get_current │ │ 资源可见性 │ │ GPU 分配校验 │ │
│ │ _user (鉴权) │ │ 过滤器 │ │ │ │
│ └──────┬───────┘ └────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌──────┴───────────────┴──────────────────┘ │
│ │ PlatformStore │ │
│ │ users | acls | roles | gpu_assignments | │ │
│ │ datasets | models | fine_tune_tasks | ... │ │
│ └───────────────────────────────────────────────────│ │
│ │ audit_logs (审计日志) │ │
│ └───────────────────────────────────────────────────│ │
└─────────────────────────────────────────────────────────┘
```
**鉴权链路**
1. 请求到达 → `get_current_user``Authorization: Bearer platform-token-{user_id}` 解析当前用户
2. 页面级权限 → 检查 `user.permissions` 是否包含路由对应的权限码
3. 资源级权限 → 检查资源的 `created_by` 字段(所有权)或 `acls`ACL 授权)
4. GPU 权限 → 检查 `gpu_assignments` 表确认用户是否被分配了请求的 GPU
---
## 3. 角色体系
### 3.1 内置角色
| 角色 code | 中文名 | 说明 |
|---|---|---|
| `admin` | 超级管理员 | 拥有全部权限码;可见全部资源;可管理用户和 GPU 分配 |
| `operator` | 操作员 | 可创建/操作自己的数据集、模型、训练任务;不可管理用户 |
| `viewer` | 观察员 | 只读权限;可查看被授权的资源;不可创建或修改 |
| `guest` | 访客 | 仅登录和看板;无业务操作权限(扩展预留) |
### 3.2 角色与权限码映射
| 权限码 | admin | operator | viewer |
|---|---|---|---|
| `dashboard` | ✅ | ✅ | ✅ |
| `fine-tune` | ✅ | ✅ | — |
| `model-eval` | ✅ | ✅ | — |
| `model-inference` | ✅ | ✅ | — |
| `model-manage` | ✅ | ✅ | — |
| `dataset` | ✅ | ✅ | — |
| `data-process` | ✅ | ✅ | — |
| `data-convert` | ✅ | ✅ | — |
| `compute` | ✅ | ✅ | — |
| `hardware` | ✅ | ✅ | ✅ |
| `logs` | ✅ | ✅ | ✅ |
| `user-settings` | ✅ | — | — |
### 3.3 权限修改规则
- **只有 admin 角色的用户**可以修改其他用户的角色和权限码
- admin 用户的 `protected=True` 标记,防止被删除或降级
- 权限修改操作产生审计日志:`action=user.permission.update`
- 用户可以查看自己的权限,不能修改自己的权限
---
## 4. 页面权限码
| 权限码 | 对应路由 | 功能 |
|---|---|---|
| `dashboard` | `/dashboard` | 服务看板 |
| `fine-tune` | `/fine-tune`, `/fine-tune/create`, `/training-log/:id` | 模型训练 |
| `model-eval` | `/model-eval`, `/model-eval/create`, `/model-eval/:id` | 模型评测 |
| `model-inference` | `/model-inference`, `/model-inference/create`, `/model-inference/chat/:id` | 模型推理 |
| `model-manage` | `/model-manage`, `/model-manage/create`, `/model-manage/:id/edit`, `/model-manage/merge` | 模型管理 |
| `dataset` | `/dataset`, `/dataset/create`, `/dataset/:id/preview` | 数据集管理 |
| `data-process` | `/data-process`, `/data-process/create`, `/data-process/:id` | 数据处理 |
| `data-convert` | `/data-convert`, `/tools` | 数据转换与工具 |
| `compute` | `/compute` | 算力节点 |
| `hardware` | `/hardware` | 平台性能 |
| `logs` | `/logs`, `/training-log/:id` | 查看日志 |
| `user-settings` | `/user-settings`, `/tenants`, `/projects`, `/approvals`, `/audit-logs` | 系统设置与平台治理 |
---
## 5. 资源所有权与可见性
### 5.1 所有权模型
每个用户可创建的资源都携带 `created_by`(或 `owner_id`)字段,标识资源所有者。
| 资源类型 | 表 | 所有者字段 | 说明 |
|---|---|---|---|
| 数据集 | `datasets` | `created_by` | 用户上传/创建的数据集 |
| 基座模型 | `models` | `created_by` | 登记的本地/API 模型 |
| 训练产物 | `trained_models` | `created_by` | 微调产出的模型 |
| 训练任务 | `fine_tune_tasks` | `payload.created_by` | 微调任务 |
| 评测任务 | `eval_tasks` | `created_by` | 评测任务 |
| 推理任务 | `compare_tasks` (payload) | `created_by` | 推理/对比任务 |
| 数据处理任务 | `data_process_tasks` | `created_by` | 数据处理任务 |
| 数据转换任务 | `data_convert_jobs` | `created_by` | 数据转换任务 |
### 5.2 可见性规则
```
资源列表查询过滤逻辑:
if user.role == "admin":
返回全部资源
elif resource.created_by == user.id:
返回(资源所有者可见自己的资源)
elif acl 中存在 (principal_type="user", principal_id=user.id, permission 包含 "read"):
返回(被 ACL 显式授权的资源)
elif acl 中存在 (principal_type="role", principal_id=user.role, permission 包含 "read"):
返回(被角色级 ACL 授权的资源)
else:
不可见
```
### 5.3 所有权操作矩阵
| 操作 | admin | 资源所有者 | 其他被授权用户 | 其他用户 |
|---|---|---|---|---|
| 查看资源 | ✅ 全部 | ✅ 自己的 | ✅ ACL 授权范围内 | ❌ |
| 编辑资源 | ✅ | ✅ 自己的 | ✅ ACL 含 write 时 | ❌ |
| 删除资源 | ✅ | ✅ 自己的(需审批) | ❌ | ❌ |
| 分享/授权 | ✅ | ✅ 自己的 | ❌ | ❌ |
| 使用资源(训练/推理/评测) | ✅ | ✅ 自己的 | ✅ ACL 含 execute 时 | ❌ |
### 5.4 数据集可见性示例
```
用户 A 创建了数据集 ds_A1 → 只有 A 和 admin 可见
用户 A 通过 ACL 把 ds_A1 的 read 权限授给用户 B → B 也可见
用户 A 通过 ACL 把 ds_A1 的 write 权限授给 operator 角色 → 所有 operator 可编辑
管理员可在任何数据集上设置 ACL
```
---
## 6. 资源级 ACL访问控制列表
### 6.1 ACL 表结构
```sql
CREATE TABLE IF NOT EXISTS acls (
id TEXT PRIMARY KEY,
resource_type TEXT NOT NULL, -- 资源类型: dataset / model / trained_model / fine_tune_task / ...
resource_id TEXT NOT NULL, -- 资源 ID
principal_type TEXT NOT NULL, -- 授权主体类型: user / role
principal_id TEXT NOT NULL, -- 授权主体 ID: user_id 或 role name
permission TEXT NOT NULL, -- 权限: read / write / execute / download / delete / admin
create_time TEXT
);
```
### 6.2 权限粒度
| 权限值 | 含义 | 覆盖关系 |
|---|---|---|
| `read` | 查看资源详情、列表 | — |
| `write` | 编辑资源内容/元数据 | 覆盖 `read` |
| `execute` | 使用资源(如用数据集训练、用模型推理) | 覆盖 `read` |
| `download` | 下载资源文件 | 独立权限 |
| `delete` | 删除资源 | 独立权限(通常需审批) |
| `admin` | 完全控制(含 ACL 管理) | 覆盖以上全部 |
### 6.3 ACL 管理接口
| 接口 | 方法 | 权限要求 | 说明 |
|---|---|---|---|
| `/resources/{type}/{id}/acl` | GET | admin 或资源所有者 | 查询资源 ACL |
| `/resources/{type}/{id}/acl` | PUT | admin 或资源所有者 | 设置资源 ACL全量替换 |
### 6.4 ACL 管理规则
- **admin** 可以在任何资源上设置 ACL
- **资源所有者** 可以在自己的资源上设置 ACL
- **被授权用户** 不能转授自己获得的权限
- ACL 变更产生审计日志:`action=resource.acl.set`
- 设置 ACL 时全量替换该资源的所有 ACL 条目
### 6.5 前端 ACL 管理入口
在数据集详情、模型详情、训练任务详情页面提供「资源授权」按钮,弹出 ACL 管理对话框:
- 显示当前 ACL 列表(主体类型 + 主体名称 + 权限勾选)
- 支持按用户或按角色添加授权
- 权限以多选框形式展示read / write / execute / download / delete
---
## 7. GPU 算力分配与隔离
### 7.1 设计背景
服务器可能安装多张 GPU 卡(如 8×A100需要精细化管控
- 管理员指定哪些用户可以使用哪些 GPU 卡
- 未被分配的 GPU 卡对用户不可见或不可选
- admin 可以使用全部 GPU
### 7.2 GPU 分配表
```sql
CREATE TABLE IF NOT EXISTS gpu_assignments (
id TEXT PRIMARY KEY,
gpu_id TEXT NOT NULL, -- gpus 表的外键node_id + gpu_index 组合)
node_id TEXT NOT NULL, -- 算力节点 ID
gpu_index INTEGER NOT NULL, -- GPU 卡序号
user_id TEXT NOT NULL, -- 被分配的用户 ID
assigned_by TEXT, -- 分配操作人 IDadmin
assigned_at TEXT NOT NULL, -- 分配时间
UNIQUE (node_id, gpu_index, user_id) -- 一张卡可分配给多个用户,但每对 (卡, 用户) 唯一
);
```
### 7.3 分配规则
| 规则 | 说明 |
|---|---|
| 谁可分配 | 只有 `admin` 角色可以分配 GPU |
| admin 使用 | admin 可使用全部 GPU不需要显式分配 |
| 普通用户 | 只能使用 `gpu_assignments` 中分配给自己的 GPU |
| 共享分配 | 一张 GPU 可分配给多个用户(非独占),但同时只能被一个任务占用 |
| 默认策略 | 新用户默认不分配任何 GPU由管理员显式分配 |
### 7.4 GPU 分配接口
| 接口 | 方法 | 权限 | 说明 |
|---|---|---|---|
| `/compute/gpu-assignments` | GET | admin | 查看全部分配关系 |
| `/compute/gpu-assignments` | POST | admin | 批量分配body: `{ assignments: [{ node_id, gpu_index, user_id }] }` |
| `/compute/gpu-assignments/{id}` | DELETE | admin | 撤销某条分配 |
| `/compute/my-gpus` | GET | 登录用户 | 查看自己可用的 GPU 列表 |
### 7.5 训练/评测/推理 GPU 选择校验
当普通用户创建训练任务、评测任务、推理任务并选择 GPU 时:
1. 后端检查 `gpu_assignments` 表,确认用户被分配了所选 GPU
2. 未被分配的 GPU → 返回 403 `"无权使用 GPU {node}:{index}"`
3. admin 用户跳过此校验
### 7.6 前端 GPU 选择交互
- 普通用户在创建任务选择 GPU 时,下拉列表只显示自己被分配的 GPU
- admin 用户在下拉列表中可看到全部 GPU
- 未分配任何 GPU 的用户GPU 选择区域显示提示:"未分配 GPU请联系管理员"
---
## 8. 审批拦截机制
### 8.1 需要审批的操作
| 操作 | 触发条件 | 审批动作 code |
|---|---|---|
| 删除他人数据集 | 非 admin 删除 `created_by != user.id` 的数据集 | `dataset.delete` |
| 删除他人模型 | 非 admin 删除 `created_by != user.id` 的模型 | `model.delete` |
| 停止他人训练任务 | 非 admin 停止 `created_by != user.id` 的任务 | `fine_tune.stop` |
| 发布模型到推理服务 | 任何用户(含 admin发布到生产环境 | `model_service.publish` |
| 删除项目空间 | 存在待审批变更时拒绝 | `project.delete` |
| 归档项目空间 | 存在待审批变更时拒绝 | `project.archive` |
| 导出训练产物 | 非 admin 导出他人训练的模型 | `trained_model.export` |
### 8.2 审批流程
```
普通用户发起高风险操作
┌──────────────┐ ┌──────────────────────┐
│ admin 旁路? │───是──▶│ 直接执行 + 审计日志 │
└──────┬───────┘ └──────────────────────┘
│ 否
┌──────────────────────┐
│ 创建审批实例 │
│ status=pending │
│ 返回 202待审批
└──────────┬───────────┘
┌──────────────────────┐
│ 管理员审批 │
│ POST /approvals/:id │
│ /steps/:idx/decision │
└──────────┬───────────┘
┌──────┴──────┐
│ │
approved rejected
│ │
▼ ▼
执行操作 不执行
+审计日志 +审计日志
```
### 8.3 审批模板
审批模板定义了特定操作需要几步审批、每步的审批人是谁:
```json
{
"id": "tpl_001",
"name": "删除数据集审批",
"action": "dataset.delete",
"steps": [
{ "approver_id": "u_admin", "step_name": "管理员审核" }
]
}
```
### 8.4 审批拦截点
在项目模块的 `_require_no_pending_approval` 函数中,当存在待审批实例时拒绝执行新操作。其他模块通过 `_require_approval_or_admin` 函数实现 admin 旁路或创建审批实例。
---
## 9. 审计日志
### 9.1 审计范围
所有写操作和敏感操作必须产生审计日志:
| 动作分类 | action 示例 |
|---|---|
| 用户管理 | `user.create`, `user.update`, `user.delete`, `user.permission.update` |
| 租户管理 | `tenant.create`, `tenant.update`, `tenant.quota.set`, `tenant.retention.set` |
| 项目管理 | `project.create`, `project.update`, `project.archive`, `project.delete`, `project.member.add`, `project.member.update`, `project.member.remove` |
| 资源 ACL | `resource.acl.set` |
| 模型管理 | `model.create`, `model.update`, `model.delete`, `model.merge` |
| 数据集 | `dataset.create`, `dataset.update`, `dataset.delete`, `dataset.upload` |
| 训练任务 | `fine_tune.create`, `fine_tune.start`, `fine_tune.stop`, `fine_tune.delete` |
| 评测任务 | `eval.create`, `eval.start`, `eval.stop` |
| 推理任务 | `inference.create`, `inference.start`, `inference.stop` |
| 审批 | `approval.create`, `approval.decide` |
| 留存策略 | `retention.create`, `retention.update`, `retention.delete` |
| GPU 分配 | `gpu.assign`, `gpu.unassign` |
### 9.2 审计日志字段
```sql
CREATE TABLE IF NOT EXISTS audit_logs (
id TEXT PRIMARY KEY,
time TEXT NOT NULL, -- ISO8601 时间戳
tenant_id TEXT, -- 租户 ID可选
project_id TEXT, -- 项目 ID可选
actor_id TEXT, -- 操作人 ID
action TEXT NOT NULL, -- 动作类型
target_type TEXT NOT NULL, -- 目标资源类型
target_id TEXT, -- 目标资源 ID
detail TEXT, -- 详情摘要
client_ip TEXT -- 客户端 IP
);
```
### 9.3 查询与导出
| 接口 | 方法 | 说明 |
|---|---|---|
| `/system/audit-logs` | GET | 分页查询,支持按 tenant_id / project_id / actor_id / action / target_type / start_time / end_time 筛选 |
| `/system/audit-logs/export` | GET | CSV 导出,与应用查询相同的过滤条件 |
---
## 10. 接口鉴权流程
### 10.1 Token 格式
```
Authorization: Bearer platform-token-{user_id}
```
登录成功后返回 `token``user` 信息。Token 中编码了 `user_id`,后端通过 `get_current_user` 解析。
### 10.2 鉴权层级
```
请求到达
├─ 1. 公开路径检查(/health, /login, /system-info→ 直接放行
├─ 2. Token 解析 → get_current_user
│ ├─ 无 token / token 无效 → 401
│ └─ 用户不存在 / 状态 disabled → 401
├─ 3. 页面级权限码检查(路由守卫 / Depends
│ └─ user.permissions 不含所需权限码 → 403
├─ 4. 资源级权限检查(路由函数内)
│ ├─ admin → 全部放行
│ ├─ resource.created_by == user.id → 放行
│ ├─ ACL 检查 has_resource_access() → 有授权则放行
│ └─ 否则 → 403
├─ 5. GPU 权限检查(训练/评测/推理创建时)
│ ├─ admin → 全部放行
│ ├─ gpu_assignments 检查 → 有分配则放行
│ └─ 否则 → 403
└─ 6. 审批拦截检查(高风险操作)
├─ admin → 旁路,直接执行
├─ 无待审批实例 → 可执行
├─ 有待审批实例 → 409 "存在待审批的变更"
└─ 需要审批 → 202 "已创建审批实例"
```
### 10.3 FastAPI 依赖注入
```python
# 任何需要登录的接口
@router.get("/datasets")
async def list_datasets(user: dict = Depends(get_current_user)):
...
# 需要管理员权限的接口
@router.post("/users")
async def create_user(user: dict = Depends(require_admin)):
...
# 需要资源级权限检查的接口
@router.delete("/datasets/{dataset_id}")
async def delete_dataset(
dataset_id: str,
user: dict = Depends(get_current_user),
):
if not has_resource_access("dataset", dataset_id, user, "delete"):
raise HTTPException(403, "forbidden")
...
```
---
## 11. 数据库表结构
### 11.1 现有表(已实现)
| 表名 | 用途 |
|---|---|
| `users` | 用户表id, username, password_hash, role, status, permissions, protected |
| `roles` | 角色定义name, permissions |
| `sessions` | 登录会话user_id, issued_at, expires_at, ip |
| `acls` | 资源访问控制列表resource_type, resource_id, principal_type, principal_id, permission |
| `audit_logs` | 审计日志actor_id, action, target_type, target_id, time |
| `datasets` | 数据集(需补充 `created_by` 字段) |
| `models` | 基座模型(需补充 `created_by` 字段) |
| `trained_models` | 训练产物(需补充 `created_by` 字段) |
| `fine_tune_tasks` | 训练任务payload 中存储 `created_by` |
| `gpus` | GPU 设备node_id, gpu_index, uuid, name, memory |
| `compute_nodes` | 算力节点 |
| `tenants` | 租户 |
| `projects` | 项目空间 |
| `project_members` | 项目成员 |
| `approval_templates` | 审批模板 |
| `approval_instances` | 审批实例 |
| `retention_policies` | 留存策略 |
### 11.2 需新增/补充的表和字段
#### 新增 `gpu_assignments` 表
```sql
CREATE TABLE IF NOT EXISTS gpu_assignments (
id TEXT PRIMARY KEY,
node_id TEXT NOT NULL REFERENCES compute_nodes(id) ON DELETE CASCADE,
gpu_index INTEGER NOT NULL,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
assigned_by TEXT,
assigned_at TEXT NOT NULL,
UNIQUE (node_id, gpu_index, user_id)
);
CREATE INDEX IF NOT EXISTS idx_gpu_assignments_user ON gpu_assignments(user_id);
CREATE INDEX IF NOT EXISTS idx_gpu_assignments_gpu ON gpu_assignments(node_id, gpu_index);
```
#### 补充 `created_by` 字段
```sql
-- 数据集表补充所有者
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS created_by TEXT;
-- 基座模型表补充所有者
ALTER TABLE models ADD COLUMN IF NOT EXISTS created_by TEXT;
-- 训练产物表补充所有者
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS created_by TEXT;
-- 评测任务表补充所有者
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
-- 推理任务表补充所有者
-- 注意inference_tasks 表尚未创建,后续建表时直接包含 created_by 字段
```
---
## 12. API 接口清单
### 12.1 鉴权接口
| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/login` | POST | 公开 | 登录,返回 token + user |
| `/modelTF/me` | GET | Bearer token | 获取当前用户信息 |
| `/modelTF/users` | GET | admin | 用户列表 |
| `/modelTF/users` | POST | admin | 创建用户 |
| `/modelTF/users/:id` | PUT | admin | 更新用户(角色/状态/权限) |
| `/modelTF/users/:id` | DELETE | admin | 删除用户protected 用户不可删) |
| `/modelTF/users/:id/reset-password` | POST | admin | 重置密码 |
| `/modelTF/system/permissions/codes` | GET | 登录 | 权限码清单 |
| `/modelTF/system/permissions` | GET | 登录 | 权限码 + 角色定义 |
### 12.2 资源 ACL 接口
| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/resources/:type/:id/acl` | GET | admin 或所有者 | 查询资源 ACL |
| `/modelTF/resources/:type/:id/acl` | PUT | admin 或所有者 | 设置资源 ACL |
### 12.3 GPU 分配接口
| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/compute/gpu-assignments` | GET | admin | 查看全部分配 |
| `/modelTF/compute/gpu-assignments` | POST | admin | 批量分配 |
| `/modelTF/compute/gpu-assignments/:id` | DELETE | admin | 撤销分配 |
| `/modelTF/compute/my-gpus` | GET | 登录 | 查看自己可用 GPU |
### 12.4 审批接口
| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/approvals/templates` | GET/POST | admin | 审批模板列表/创建 |
| `/modelTF/approvals` | GET/POST | 登录 | 审批实例列表/创建 |
| `/modelTF/approvals/:id` | GET | 登录 | 审批实例详情 |
| `/modelTF/approvals/:id/steps/:idx/decision` | POST | 审批人 | 审批决策 |
### 12.5 审计接口
| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/system/audit-logs` | GET | admin | 审计日志分页查询 |
| `/modelTF/system/audit-logs/export` | GET | admin | CSV 导出 |
### 12.6 租户/项目接口
| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/tenants` | GET/POST | admin | 租户列表/创建 |
| `/modelTF/tenants/:id` | GET/PUT | admin | 租户详情/更新 |
| `/modelTF/tenants/:id/quota` | PUT | admin | 设置配额 |
| `/modelTF/tenants/:id/retention-policy` | PUT | admin | 绑定留存策略 |
| `/modelTF/projects` | GET/POST | 登录 | 项目列表/创建 |
| `/modelTF/projects/:id` | GET/PUT | 登录 | 项目详情/更新 |
| `/modelTF/projects/:id/archive` | POST | admin 或所有者 | 归档(审批拦截) |
| `/modelTF/projects/:id/members` | GET/POST | 登录 | 成员列表/添加 |
| `/modelTF/projects/:id/members/:uid` | PUT/DELETE | admin 或所有者 | 改角色/移除 |
### 12.7 留存策略接口
| 接口 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/modelTF/retention-policies` | GET/POST | admin | 策略列表/创建 |
| `/modelTF/retention-policies/:id` | GET/PUT/DELETE | admin | 策略详情/更新/删除 |
---
## 13. 前端权限控制
### 13.1 路由守卫
```typescript
// router/index.ts
router.beforeEach((to, _from, next) => {
const auth = useAuthStore()
auth.syncSession()
if (to.meta.public) {
next()
return
}
if (!auth.isLoggedIn) {
next({ name: 'login' })
return
}
if (!to.meta.skipPermission) {
const permission = requiredPermission(to.path, to.meta.permission)
if (permission && !auth.hasPermission(permission)) {
next({ name: 'permission-denied', replace: true })
return
}
}
next()
})
```
### 13.2 侧边栏过滤
```typescript
// layouts/MainLayout.vue
const visibleMenus = computed(() =>
allMenus.filter(menu => {
if (!menu.permission) return true
return auth.hasPermission(menu.permission)
})
)
```
### 13.3 资源级按钮控制
```vue
<!-- 数据集详情页 -->
<template>
<el-button v-if="canEdit" @click="handleEdit">编辑</el-button>
<el-button v-if="canDelete" @click="handleDelete">删除</el-button>
<el-button v-if="canManageAcl" @click="showAclDialog = true">资源授权</el-button>
</template>
<script setup>
const canEdit = computed(() =>
isAdmin.value || resource.value.created_by === userId.value
)
const canDelete = computed(() =>
isAdmin.value || resource.value.created_by === userId.value
)
const canManageAcl = computed(() =>
isAdmin.value || resource.value.created_by === userId.value
)
</script>
```
### 13.4 GPU 选择过滤
```vue
<!-- 创建训练任务页 -->
<template>
<el-select v-model="selectedGpus" multiple>
<el-option
v-for="gpu in availableGpus"
:key="gpu.id"
:label="`${gpu.node_name} GPU ${gpu.gpu_index}`"
:value="gpu.id"
/>
</el-select>
<el-alert v-if="availableGpus.length === 0 && !isAdmin" type="warning">
未分配 GPU请联系管理员
</el-alert>
</template>
<script setup>
// 普通用户只看到 my-gpus 返回的列表
// admin 看到全部 GPU
const availableGpus = ref([])
async function loadGpus() {
if (isAdmin.value) {
availableGpus.value = await getAllGpus()
} else {
availableGpus.value = await getMyGpus()
}
}
</script>
```
---
## 14. 安全设计补充
### 14.1 密码安全
- 密码使用 PBKDF2-SHA256 存储salt + 390000 次迭代)
- 旧系统明文密码在首次登录时自动升级为哈希
- 管理员可重置用户密码,用户不可自行修改密码(本期设计)
- 默认密码:`platform123`(创建用户时由管理员设定)
### 14.2 会话安全
| 规则 | 说明 |
|---|---|
| Token 格式 | `platform-token-{user_id}` |
| 会话超时 | 默认 30 分钟无操作自动过期 |
| 并发会话 | 同一用户可有多会话,各自独立计时 |
| 会话续期 | 前端定时调用 `auth.refresh()` 续期 |
| 强制下线 | admin 可通过修改用户 status=disabled 使其 token 失效 |
### 14.3 操作限流
| 接口 | 限制 |
|---|---|
| `/login` | 同一 IP 5 次/分钟,失败后 30 秒冷却 |
| 文件上传 | 单文件最大由配置控制,默认 2GB |
| 训练任务创建 | 同一用户并发运行任务数受 GPU 分配限制 |
### 14.4 数据安全
| 规则 | 说明 |
|---|---|
| 软删除 | 数据集、模型、任务使用 `deleted_at` 标记,保留审计可追溯 |
| 敏感字段 | API 密钥(`api_key`)在列表接口不返回明文 |
| 下载审计 | 数据集下载产生审计日志,记录下载人和时间 |
| 导出审计 | 训练产物导出产生审计日志 |
### 14.5 多租户隔离
| 规则 | 说明 |
|---|---|
| 租户隔离 | 同一租户内的资源相互可见;跨租户默认不可见 |
| 项目隔离 | 项目内资源受项目 ACL 控制;项目间默认不可见 |
| admin 旁路 | admin 可跨租户/项目访问全部资源 |
| 配额管控 | 租户级配额限制 GPU 并发数、存储容量、最大项目数 |
---
## 15. 实施计划
### 15.1 已实现
| 功能 | 状态 |
|---|---|
| 登录/会话/Token | ✅ 已实现 |
| 用户 CRUD + 权限码 | ✅ 已实现 |
| 角色定义 | ✅ 已实现 |
| 资源 ACLacls 表 + 接口) | ✅ 已实现 |
| 审计日志(查询 + 导出) | ✅ 已实现 |
| 审批模板/实例 | ✅ 已实现 |
| 项目空间 + 成员 | ✅ 已实现 |
| 租户 + 配额 + 留存 | ✅ 已实现 |
| 审批拦截(项目归档/删除) | ✅ 已实现 |
| 资源所有权 ACL 字段适配subject_type/permissions[] | ✅ 已实现 |
### 15.2 待实现
| 功能 | 优先级 | 涉及表/接口 |
|---|---|---|
| GPU 分配表 + 接口 | P0 | `gpu_assignments` 表 + `/compute/gpu-assignments` + `/compute/my-gpus` |
| 资源 `created_by` 字段补充 | P0 | `datasets` / `models` / `trained_models` / `eval_tasks` 表 ALTER |
| 资源列表按 `created_by` + ACL 过滤 | P0 | `platform_store.py` 中 datasets/models/tasks 列表方法 |
| GPU 选择校验(训练/评测/推理创建时) | P0 | `platform.py` 中 create_task/eval/inference |
| 前端 GPU 下拉过滤 | P1 | 前端创建任务页面 |
| 前端资源授权按钮 | P1 | 前端数据集/模型/任务详情页 |
| 前端权限管理页面优化 | P1 | 前端用户设置页面 |
| 审批拦截扩展(删除数据集/模型/停止任务) | P1 | `platform.py` 中 delete/stop 接口 |
| 密码安全策略(用户自行修改) | P2 | 新增 `/users/me/password` 接口 |
| 操作限流login 限流) | P2 | 中间件或 SlowAPI |
| 多租户隔离(按 tenant_id 过滤) | P2 | 各列表接口增加 tenant_id 过滤 |
### 15.3 实施步骤
1. **数据库迁移**:创建 `gpu_assignments` 表,为资源表补充 `created_by` 字段
2. **后端接口**:实现 GPU 分配 CRUD + `my-gpus` + 创建任务时的 GPU 权限校验
3. **资源过滤**:在 `datasets()` / `models()` / `tasks()` 等列表方法中按 `created_by` + ACL 过滤
4. **前端适配**GPU 下拉过滤、资源授权按钮、权限管理页面优化
5. **审批扩展**:在删除/停止接口中接入 `_require_approval_or_admin`
6. **测试补充**:扩展 `test_governance.py` 覆盖 GPU 分配、资源过滤、审批扩展场景