51 KiB
模型平台接口文档
本文档面向前端开发者与第三方集成方。所有接口的入口是 Nginx 网关(默认
http://localhost:8888),除/api/v1/auth/jupyter由 Nginxauth_request自动调用,其他接口都通过/api/v1/...同源访问。服务端基础 URL 示例:
http://localhost:8888通用响应外壳:
{ "request_id": "01HXY...", "data": { ... }, "meta": {} }错误响应为标准 HTTP 4xx / 5xx,body 为
{"detail": "..."}或{"code": "...", "message": "...", "details": {}}。
目录
- 鉴权
- 统一约定
- 脚本 / Notebook (
/api/v1/scripts/...) - 调度 (
/api/v1/schedules/...+/api/v1/schedule-runs/...) - 数据资源 (
/api/v1/data-resources/...) - 管理后台 (
/api/v1/admin/...) - 系统管理 (
/api/v1/platform/...) - Jupyter 路由 (Nginx
auth_request) - 对象存储控制面 (
/internal/v1/objects,服务间 RPC + Token 鉴权) - 健康检查
一、鉴权
平台用 JWT (HS256)。登录后,前端在后续请求里任选一种携带方式:
- Cookie(推荐用于浏览器):登录成功后后端种
Authorization或自定义 cookie;前端无需手写。 Authorization: Bearer <token>(推荐用于脚本与第三方)。
JWT_SECRET 由后端从 Settings.jwt_secret 读取,前端不需要知道,只需要保证登录态带过来即可。
- 当
Authorization与Cookie同时存在时,后端优先使用Authorization。 - 缺失或过期 → HTTP
401。 - 有效但用户不在 workspace → HTTP
403(由 Nginxauth_request透传给客户端)。
/api/v1/auth/me与/api/v1/auth/login响应中的data.user对象额外携带以下三个字段:
role_code: string | null—— 用户的平台角色(users.platform_role_id指向的 Roles 行的role_code),取值为admin/developer/null(未分配)。注意:本字段同时也是该用户在所有 workspace 中的角色——workspace 角色始终继承自平台角色,本端点不再返回 workspace 级独立角色码。is_system_admin: bool—— 派生自users.platform_role_id指向的角色role_code == 'admin'且用户状态为active。前端据此决定是否渲染"系统管理"入口。permissions: string[]—— 当前用户通过其平台角色(platform_role_id)间接持有的菜单权限码列表(permission_code),按字典序排列;未分配平台角色时为空数组。前端据此过滤菜单与<RequirePermission>路由守卫。仅控制前端展示,不参与后端 endpoint 鉴权——后端鉴权继续由system_admin_context(role_code == 'admin')与 workspace membership 负责。详见 §7.12-7.14。
二、统一约定
| 类别 | 约定 |
|---|---|
| ID 格式 | 全部为 ULID(26 字符),如 01HXY9C5B8N3K4P7Q6RT2V0J8D |
| 时间戳 | ISO-8601 UTC,毫秒精度,如 2026-07-31T11:23:45.123 |
| 时区 | 所有 *_at 字段均为 UTC,前端需自行转换显示 |
| 分页 | 大列表接口使用 limit (≤200) + 隐式 cursor,无 offset |
| 幂等键 | 上传类接口要求 Idempotency-Key 请求头,≥8 字符,≤128 字符 |
| 软删 | 删除操作走 is_deleted 软删,不返回 404;再次查询时已软删资源 status="deleted" |
| 排序 | 列表默认按业务键倒序(更新时间 / 入队时间等) |
| 鉴权头 | 见 §一 |
三、脚本 / Notebook
业务概念:
Scripts是用户工作区里的脚本或 notebook,StorageObjects是它在对象存储里的"工作副本",Versions是 immutable 的稳定版本。 写操作受 is_locked 门禁 + owner 校验 保护(架构 V3.1 §4)。
3.1 GET /api/v1/workspace-tree (legacy / 全量视图)
Deprecated: 新代码请走 §3.3.1 (按
parent_path单层)。该接口仍保留供调试 / 兼容使用, 谓词已统一为object_status='available' AND is_deleted=0,与 §3.3.1 保持一致。
列出当前用户在 workspace 内的整个目录树(扁平数组)。
- 来源: 显式
StorageObjects行 (object_type='directory',见 §3.2) 并入 从Scripts.relative_path派生的祖先目录,去重。空目录(只有显式行、没有文件)也会出现。 - 鉴权: workspace 成员
- 请求体: 无
- 响应:
{ "request_id": "...", "data": { "directories": [ {"path": "scripts", "name": "scripts", "parent_path": ""}, {"path": "scripts/etl", "name": "etl", "parent_path": "scripts"} ] }, "meta": {"directory_count": 2} }
3.2 POST /api/v1/workspace-directories
创建一个目录。后端会落一行 StorageObjects(object_type='directory', storage_backend='rustfs', storage_uri='inline://directory/{path}', visibility='public'),因此空目录也能在 §3.1 树里出现并保留下来。
目录行默认
visibility='public'(非private)。目录是结构性导航行, 默认 public 使同一 workspace 内其他成员可以浏览彼此的目录结构(目录树 跨 owner 可见);文件级私密仍由 §3.4 / §五 的 visibility 过滤兜底 —— 其他 owner 的private脚本 / 数据资源不会返回。
-
请求体:
{ "directory_name": "etl", "parent_path": "scripts" }字段 必填 说明 directory_name是 目录名(单段,不能含 /)parent_path否 父目录相对路径,空字符串或缺省表示用户根目录 -
父目录存在性校验: 必须在
StorageObjects存在relative_path == scoped_prefix/{parent}的行,或relative_path以scoped_prefix/{parent}/开头。否则 404。 -
同名冲突: 已有
relative_path完全相等的行(无论 file / directory) → 409。冲突判定由应用层SELECT ... FOR UPDATE完成;idx_storage_workspace_path(workspace_id, storage_backend, path_hash)仅作为查找索引,不再提供 DB 级唯一性兜底。 -
响应 201:
{ "request_id": "...", "data": { "storage_object_id": "01HXY...", "path": "scripts/etl", "name": "etl", "parent_path": "scripts", "owner_user_id": "01HXX..." } }
3.3 DELETE /api/v1/workspace-directories?path=...
删除一个目录(以及目录下当前用户拥有的所有 Scripts,会触发 is_locked 校验)。
StorageObjects 行移到 trash bucket(settings.s3_trash_bucket),object_status 置为 deleted。
- 查询参数:
名 类型 必填 说明 pathstring 是 相对路径,例如 scripts/etl - 响应:
{ "data": { "path": "scripts/etl", "status": "deleted", "deleted_scripts": 3, "versions_preserved": true } }
3.3.1 GET /api/v1/workspace-directories?parent_path=...
列出指定父目录下的直接子目录(单层),用于前端懒加载。新代码请走本接口;§3.1 仅作为 legacy / 全量保留。
- 行为:
parent_path为空字符串或缺省 → 用户根目录;非空 → 该父目录的直接子目录(workspace-relative)。仅返回object_status='available' AND is_deleted=0的StorageObjects行。 - 鉴权: workspace 成员
- owner 作用域:
owner_user_id缺省时 scope 为当前请求者本人根目录(scoped_prefix = workspace/{me});传owner_user_id时 scope 为该 owner 的根目录(scoped_prefix = workspace/{owner_user_id}),用于跨 owner 浏览目录树(见 §3.4 visibility 模型)。该接口本身不施加 visibility 过滤——目录行默认visibility='public'(见 §3.2),跨 owner 均可见。 - 查询参数:
名 类型 必填 说明 parent_pathstring 否 父目录相对路径,空字符串或缺省表示用户根目录 owner_user_idstring 否 目标 owner 的 user_id;缺省=请求者本人。指定后 scope 到 workspace/{owner_user_id}/{parent_path} - 谓词(SQL 等价):
relative_path LIKE '<prefix>/%' AND relative_path NOT LIKE '<prefix>/%/%',其中prefix = scoped_prefix/{parent_path},索引走idx_storage_workspace_relative_path(workspace_id, relative_path(255))。 - 响应:
{ "request_id": "...", "data": { "directories": [ {"path": "scripts", "name": "scripts", "parent_path": "", "owner_user_id": "01HXX...", "has_children": true}, {"path": "scripts/etl", "name": "etl", "parent_path": "scripts", "owner_user_id": "01HXX...", "has_children": false}, {"path": "scripts/etl/daily", "name": "daily", "parent_path": "scripts/etl", "owner_user_id": "01HXX...", "has_children": false} ] }, "meta": {"directory_count": 3} }- 字段表(继承 §3.1):
字段 类型 说明 pathstring workspace 内相对路径 namestring path的最后一段parent_pathstring 父目录相对路径,根目录用空串 owner_user_idstring 该目录行所属 owner 的 user_id( owner_user_id参数缺省时=请求者本人)has_childrenbool 该目录下是否还有直接子目录(后端额外 EXISTS查询,可为空目录为false)
- 字段表(继承 §3.1):
- 空结果: 不返回 404,空目录列表即
directories: []。 - 错误: 401(未登录)/ 403(非 workspace 成员)同其他接口。
3.4 GET /api/v1/scripts
列出脚本,按 owner 作用域 + visibility 过滤返回。不受 is_locked 影响(读路径不锁)。
- owner 作用域:
owner_user_id缺省=当前请求者本人,scope 到workspace/{me}/...;传owner_user_id时 scope 到workspace/{owner_user_id}/...,用于跨 owner 浏览他人脚本。 - visibility 过滤(统一): 非 admin 请求者只返回
owner_user_id == me OR visibility IN (workspace, public);admin 请求者跳过过滤返回全部。- owner=me(缺省): scope 是我的子树,行都是我的 →
owner==me恒成立 → 含我的 private 脚本 ✓ - owner=other: scope 是他人的子树,
owner==me不成立 → 只剩其workspace/public脚本(排除他人的private) ✓ - 即"本人可见自己全部;他人只见其 workspace/public",私密仅在 owner==me 时可见。
- owner=me(缺省): scope 是我的子树,行都是我的 →
- 非递归: 仅返回
parent_path下的直接子级脚本(懒加载用);子目录脚本需带parent_path再次请求。 - 查询参数:
名 类型 必填 说明 parent_pathstring 否 父目录相对路径,空串/缺省=该 owner 根目录下的一级脚本 owner_user_idstring 否 目标 owner;缺省=请求者本人 keywordstring 否 名称模糊匹配 - 响应:
data为ScriptPayload数组(见 §3.10),每条带owner_user_id。
3.5 GET /api/v1/scripts/{script_id}
取单个脚本详情。
3.6 POST /api/v1/scripts
创建一个脚本(直接走 create_server_object 上传)。
- 请求体:
{ "script_name": "train.py", "script_type": "python", "content": "print('hello')", "visibility": "workspace", "parent_path": "scripts" }字段 必填 说明 script_name是 文件名;后端按 script_type补齐扩展名(.py/.ipynb)script_type是 python|notebookcontent是 文本内容( .ipynb必须是合法 JSON,含cells数组)visibility否 private|workspace(默认) |publicparent_path否 父目录路径
3.7 POST /api/v1/scripts/upload?file_name=...&parent_path=...&visibility=...
multipart/binary 形式上传大文件(走 server-proxied PUT,详见 §九)。
- 查询参数:
file_name(必填)、parent_path、visibility - 请求体: 原始文件字节(
Content-Type必须与脚本类型匹配) - 适用场景: 大于 100 KiB 的 notebook / 资源文件
3.8 PUT /api/v1/scripts/{script_id}
更新脚本工作副本。受 owner + is_locked 双重门禁:
-
admin 总是允许
-
owner 总是允许
-
非 owner +
is_locked == 0→ 允许 -
非 owner +
is_locked == 1→ 403 -
请求体:
{"content": "..."}
3.9 DELETE /api/v1/scripts/{script_id}
软删脚本。版本(Versions)会被保留以供审计。门禁同 §3.8。
StorageObjects 行移到 trash bucket(settings.s3_trash_bucket),object_status 置为 deleted。
3.10 ScriptPayload 字段
| 字段 | 类型 | 说明 |
|---|---|---|
script_id |
ULID | |
workspace_id |
ULID | |
current_object_id |
ULID | 当前工作副本指向的 StorageObjects.storage_object_id |
owner_user_id |
ULID | |
script_name |
string | |
script_type |
python | notebook |
|
visibility |
enum | |
status |
active | deleted |
|
is_locked |
boolean | 是否锁定。PUT /scripts/{id}(§3.8) 受此字段门禁;可通过 §3.16 切换 |
relative_path |
string | null | 例如 workspace/01HXX.../scripts/etl/train.py(以 user_id 为作用域) |
content_hash |
string | null | SHA-256 十六进制 |
size_bytes |
int | |
created_at / updated_at |
ISO-8601 |
3.11 POST /api/v1/scripts/{script_id}/versions
发布一个稳定版本(immutable,绑定到 S3_VERSION_BUCKET)。门禁同 §3.8。
- 请求体:
{ "source_object_id": "01HXY...", "release_note": "首次发布", "visibility": "workspace" } - 行为:
- 读
source_object_id对应的工作副本内容,算 SHA-256 - 同
content_hash已存在则返回 200 +meta.reused = true(去重) - 否则把副本内容上
versions桶,创建Versions行
- 读
- 响应:
{ "data": { "versions_id": "01HXY...", "version_no": 3, "version_label": "v3.0", "content_hash": "...", "file_size_bytes": 2048, "artifact_path": "s3://versions/<ws_id>/<ulid>", "...": "..." }, "meta": {"reused": false} }
3.12 GET /api/v1/scripts/{script_id}/versions
列出该脚本的所有版本(倒序)。
3.13 GET /api/v1/versions/{versions_id}
单版本详情。
3.14 DELETE /api/v1/versions/{versions_id}
从调度候选中隐藏此版本(不删除对象存储里的对象)。门禁:owner 校验基于所属 Scripts 的 owner——即"按整本 script 判定",而非"按版本发布者判定"。
3.15 POST /api/v1/versions/{versions_id}/download-url
生成对象存储的 presigned download URL(走 S3 兼容协议,local 模式下该 endpoint 在 s3 模式才生效)。
- 请求体:
{"expires_seconds": 300} - 响应:
{ "data": { "storage_object_id": "...", "presigned_url": "https://<gateway>/storage/<bucket>/<key>?X-Amz-...", "method": "GET", "expires_in_seconds": 300 } }
3.16 PATCH /api/v1/scripts/{script_id}/lock
切换脚本的 is_locked 状态。只切换锁,不修改脚本内容。
- 鉴权: admin 或
owner_user_id == 当前用户(沿用require_script_modify_access)。其余一律 404。 - 请求体:
{"is_locked": true}字段 必填 说明 is_locked是 目标状态。 true锁定;false解锁 - 响应 200:
data为更新后的ScriptPayload(§3.10)。{ "request_id": "...", "data": { "...ScriptPayload 字段...": "is_locked: false" }, "meta": {} } - 行为: 行级锁 (
SELECT ... FOR UPDATE) 防止并发切换;提交后立即生效,影响后续 §3.8PUT /scripts/{id}的门禁判定。 - 错误码:
码 含义 404 脚本不存在 / 非当前用户无权访问(不区分,避免暴露存在性)
四、调度
业务概念:
Schedules是 DAG 模板(nodes + edges),ScheduleRuns是 触发产生的一次执行实例,自带 snapshot 锁住当时的 DAG,ScheduleNodeRuns是 run 里每个 node 每次尝试的记录。调度链路(架构 V3.1 §2.3):
手动: POST /run ─→ schedule_runs (queued) + outbox_events cron: Executor APScheduler tick → POST /run ─→ 同上 │ Orchestrator (poll outbox 0.25s) │ → schedule_node_runs (queued) + outbox_events(job.node.execute) Worker (poll outbox) │ → 执行 → schedule_node_runs (succeeded/failed) + outbox_events(job.node.finished) Orchestrator 收 finished → 推进下一个 node / 终结 run
4.1 GET /api/v1/schedule-artifacts
列出可绑定到节点的 Versions(DAG 画布下拉框的素材源)。
4.2 POST /api/v1/cron/preview
预览一个 cron 表达式的未来 5 次触发时间。
- 请求体:
{"expression": "0 0 * * *", "timezone": "Asia/Shanghai"}
4.3 POST /api/v1/schedules/{schedule_id}/validate
校验 DAG 拓扑(环路检测、孤立节点等)。
4.4 调度模板 CRUD
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/v1/schedules |
列当前 workspace 的所有 schedule |
POST |
/api/v1/schedules |
创建(返回 201) |
GET |
/api/v1/schedules/{id} |
详情(含 nodes + edges) |
PUT / PATCH |
/api/v1/schedules/{id} |
改 cron / 时区 / 启用 / max_concurrency / failure_policy |
DELETE |
/api/v1/schedules/{id} |
软删 |
CreateScheduleRequest 字段:
{
"schedule_name": "nightly-train",
"trigger_type": "cron",
"cron_expression": "0 0 * * *",
"timezone": "Asia/Shanghai",
"enabled": true,
"max_concurrency": 3,
"failure_policy": "stop"
}
4.5 节点 CRUD
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/v1/schedules/{id}/nodes |
加节点(必填 versions_id 绑定 Versions) |
PUT |
/api/v1/schedules/{id}/nodes/{node_id} |
改节点参数/版本引用/重试策略 |
DELETE |
/api/v1/schedules/{id}/nodes/{node_id} |
删节点 |
节点 arguments / env_refs 只存引用,不存明文密钥。
4.6 边 CRUD
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/v1/schedules/{id}/edges |
加边(source_node_id / target_node_id) |
PUT |
/api/v1/schedules/{id}/edges/{edge_id} |
改 condition_expr |
DELETE |
/api/v1/schedules/{id}/edges/{edge_id} |
删边 |
⚠
condition_expr字段当前仅落库,不参与执行判定。DAG 只能表示依赖, 不能表达"父 node value > 0 才走 A 分支"等条件分支。
4.7 触发与查询
POST /api/v1/schedules/{schedule_id}/run
手动触发一次 run。
- 必填请求头:
Idempotency-Key(≥8 字符) - 可选请求体:
{"reason": "manual_run"}(默认) |{"reason": "cron"} - 响应 202:
{ "data": { "run_id": "01HXY...", "schedule_id": "...", "trigger_type": "manual", "run_status": "queued", "queued_at": "...", "schedule_snapshot": {"nodes": [...], "edges": [...]} }, "meta": {"reused": false} }- 同
Idempotency-Key已存在 → 返回原 run +meta.reused = true - 同 key 但元数据不一致 → 409 Conflict
- DAG 无效 / 节点 > 100 / 边 > 500 → 409 + 错误码
SCHEDULE_DAG_INVALID
- 同
GET /api/v1/schedule-runs
列出 run。可选 ?schedule_id=... 与 ?status=queued|running|succeeded|failed|cancelled|timed_out 过滤。
GET /api/v1/schedule-runs/{run_id}
单 run 详情 + 所有 node_run。
4.8 run 状态机
queued ──→ running ──┬─→ succeeded
├─→ failed
├─→ cancelled (未实现)
└─→ timed_out
五、数据资源
通用二进制资源(数据集、模型 checkpoint、任意文件)。Base 路径 前缀是
/api/v1/data-resources,不带脚本/notebook 的 owner 锁。
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/v1/data-resources/uploads |
创建上传会话,返回 upload_id + upload_path |
PUT |
/api/v1/data-resources/uploads/{upload_id} |
上传字节(请求体即文件内容) |
POST |
/api/v1/data-resources/uploads/{upload_id}/bind |
绑定已完成上传为数据资源 |
GET |
/api/v1/data-resources |
列表(owner 作用域 + visibility 过滤) |
GET |
/api/v1/data-resources/{id} |
详情 |
POST |
/api/v1/data-resources/{id}/download-url |
生成 presigned GET URL |
DELETE |
/api/v1/data-resources/{id} |
软删 |
同名冲突:同一 workspace 内、同一目录(target_path 相等)、同一 owner 下,resource_name 重复提交绑定返回 409。不同目录或不同 owner 允许重名。重新绑定同一 upload_id(storage_object_id 已落库)走 idempotent 复用路径,不视为冲突。
字节归档到 trash bucket(settings.s3_trash_bucket)。
请求示例(上传):POST /api/v1/data-resources/uploads
{
"file_name": "data.csv",
"content_type": "text/csv",
"expected_size_bytes": 1048576,
"expected_hash": "<optional sha256 hex>",
"idempotency_key": "client-uuid-or-similar"
}
完整上传流程(前端应实现的模式):
1. POST /uploads → {upload_id, upload_path, expires_at}
2. PUT upload_path with raw file bytes (Content-Type: application/octet-stream)
3. 服务器端走 backend.put() → 200 {data: StorageObjectPayload}
字节经过 backend 进程(server-proxied upload),最大 100 MiB,由 backend
直接调 AsyncStorageBackend.put() 写入存储(不再走 presigned PUT 直传)。
前端无需关心 S3 协议或签名。
小对象(<100 KiB)捷径:直接调 create_server_object 把字节 base64 放进
content_base64 字段(JSON 体里走),内部走同一条 AsyncStorageBackend.put
路径,前端无需分两步。
五.1 GET /api/v1/data-resources
列出数据资源,按 owner 作用域 + visibility 过滤返回(与 §3.4 GET /scripts 同一套统一语义)。
- owner 作用域:
owner_user_id缺省=当前请求者本人,scope 到object_key前缀{workspace_id}/{me}/...;传owner_user_id时 scope 到{workspace_id}/{owner_user_id}/...。 - visibility 过滤(统一): 非 admin 请求者只返回
owner_user_id == me OR visibility IN (workspace, public);admin 请求者跳过过滤返回全部。语义同 §3.4——owner=me 含自己的 private;owner=other 只见其 workspace/public。 - 非递归: 仅返回
parent_path下的直接子级资源(懒加载用)。 - 查询参数:
名 类型 必填 说明 parent_pathstring 否 父目录相对路径,空串/缺省=该 owner 根目录下的一级资源 owner_user_idstring 否 目标 owner;缺省=请求者本人 visibilitystring 否 workspace|public|private,二次过滤keywordstring 否 名称模糊匹配 - 响应:
data为ResourcePayload数组,每条带owner_user_id。
六、管理后台
Base 前缀 /api/v1/admin。
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/v1/admin/employees |
列员工(workspace 成员) |
POST |
/api/v1/admin/employees |
创建员工账号(返回 201) |
PATCH |
/api/v1/admin/employees/{user_id} |
改员工信息(角色/状态等) |
DELETE |
/api/v1/admin/employees/{user_id} |
软删员工 |
当前所有 admin 端点要求
is_admin上下文标志,具体 token 校验流程 见 §一。
6.1 POST /api/v1/admin/employees
创建员工账号。
- 请求体字段:
| 字段 | 类型 | 必填 | 限制 | 说明 |
|---|---|---|---|---|
username |
string | 是 | 2~64 字符 | 登录名,workspace 内唯一 |
display_name |
string | 是 | 1~100 字符 | 显示名称 |
email |
string | 否 | ≤255 字符 | 邮箱,全局唯一 |
role_code |
string | 否 | admin | developer |
默认 developer |
password |
string | 是 | 8~72 字符 | 登录密码 |
-
密码说明:
- 密码明文不会存入数据库,后端使用 bcrypt 哈希后保存到
password_hash。 - 请求体中
password必填,长度必须在 8~72 字符之间,否则返回422。 - 创建成功后的响应不包含
password或password_hash。
- 密码明文不会存入数据库,后端使用 bcrypt 哈希后保存到
-
响应 201:
{ "request_id": "...", "data": { "user_id": "...", "username": "...", "display_name": "...", "email": "...", "status": "active", "role_code": "developer", "role_name": "...", "created_at": "..." }, "meta": {} }
PATCH/DELETE员工接口不涉及密码字段,也不返回密码相关信息。
七、系统管理 (/api/v1/platform/...)
平台级(跨 workspace)管理接口,用于管理员工、workspace 实体与 workspace 成员。
除特别注明外,所有端点要求调用者是系统管理员——其 users.platform_role_id 指向
role_code='admin' 的角色行,且 users.status == 'active'。系统管理员判定
通过 GET /api/v1/auth/me 响应中的 data.user.is_system_admin 字段(详见 §一)。
例外 —
GET /workspaces/{id}/members:该端点对系统管理员(任意 workspace)与该 workspace 的活跃成员(workspace_members.is_deleted=0且member_status='active')均开放。这是为了让普通(非 admin)用户能在 脚本目录树里渲染同 workspace 其他成员的折叠分组(跨 owner 浏览,见 §3.4)。 其余 members 写端点(POST/PATCH/DELETE members)仍仅限系统管理员。
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/v1/platform/employees |
列全平台未软删员工;包含停用、锁定及无平台角色用户 |
POST |
/api/v1/platform/employees |
创建平台员工账号(返回 201);不自动加入任何 workspace |
PATCH |
/api/v1/platform/employees/{user_id} |
改员工资料/状态/平台角色(仅系统管理员) |
DELETE |
/api/v1/platform/employees/{user_id} |
软删员工;级联软删其 workspace 成员关系(仅系统管理员) |
GET |
/api/v1/platform/workspaces |
列 workspace(active/archived);已软删的过滤掉 |
POST |
/api/v1/platform/workspaces |
创建 workspace(返回 201);创建者自动成为 admin 成员 |
GET |
/api/v1/platform/workspaces/{workspace_id} |
单个 workspace(含已 disabled 的,用于恢复) |
PATCH |
/api/v1/platform/workspaces/{workspace_id} |
改 workspace 字段;status 仅允许 active/archived |
DELETE |
/api/v1/platform/workspaces/{workspace_id} |
软删 workspace;级联软删其成员 |
GET |
/api/v1/platform/workspaces/{workspace_id}/members |
列成员(系统管理员或该 workspace 活跃成员;为跨 owner 目录树提供成员名册,见 §7 intro 例外) |
POST |
/api/v1/platform/workspaces/{workspace_id}/members |
添加成员(返回 201) |
PATCH |
/api/v1/platform/workspaces/{workspace_id}/members/{user_id} |
改成员 member_status;不能改 role_code(workspace 角色继承自平台角色) |
DELETE |
/api/v1/platform/workspaces/{workspace_id}/members/{user_id} |
软删成员 |
GET |
/api/v1/platform/roles |
列全部 role_scope='platform' 的角色及其 permission_codes(仅系统管理员) |
GET |
/api/v1/platform/roles/{role_code}/permissions |
单个平台角色的 permission_codes(仅系统管理员) |
PATCH |
/api/v1/platform/roles/{role_code}/permissions |
整体替换角色权限集合;对 admin 角色强制保留 system:view 的最后系统管理员保护(仅系统管理员) |
不变量:
- 每个 workspace 必须始终保留至少一个
admin角色的活跃成员;对最后 admin 做降级 / 停用 / 删除 → 409。PATCH /employees/{user_id}降级 admin → developer 时同样触发 workspace last-admin 守卫(因为 workspace 角色继承自 platform 角色,降级会级联到所有活跃 membership);platform 必须始终保留至少一个active系统管理员;对最后系统管理员做降级 / 停用 / 删除 → 409。- 系统管理员不能通过
DELETE .../members/{self}把自己移除(403)。唯一退出方式是DELETE /workspaces/{id}软删整个 workspace,后者会级联软删所有成员。- workspace 与成员列表接口静默
pageSize=100上限,无客户端分页参数(YAGNI);GET /employees按契约返回全部未软删员工,不设隐藏上限。- 跨 workspace 操作不需要
?workspace_id=query 参数,与/api/v1/admin/...(workspace 内成员管理)不要混淆。
7.1 POST /api/v1/platform/workspaces
创建 workspace;创建者(当前系统管理员)自动成为该 workspace 的 admin 成员。
- 请求体字段:
| 字段 | 类型 | 必填 | 限制 | 说明 |
|---|---|---|---|---|
workspace_code |
string | 是 | regex ^[a-z0-9-]{3,32}$(类似 git repo 名) |
创建后冻结,不可改 |
workspace_name |
string | 是 | 1~150 字符 | 显示名称 |
quota_bytes |
int | 否 | ≥0,默认 0 |
配额字节数,0 表示无配额 |
description |
string | 否 | ≤1000 字符 |
-
服务端自动生成字段(不接收):
workspace_id(ULID)active_root_uri(s3://workspaces/{workspace_id}/)status("active")created_by(当前管理员user_id)created_at/updated_at(DB 自动)
-
响应 201:见下 §7.2
WorkspacePayload。
7.2 GET /api/v1/platform/workspaces/{workspace_id} / WorkspacePayload
- 响应 200:
{ "request_id": "...", "data": { "workspace_id": "01HXY...", "workspace_code": "model-development", "workspace_name": "模型开发 Workspace", "active_root_uri": "s3://workspaces/01HXY.../", "quota_bytes": 0, "status": "active", "description": null, "created_by": "01HXY...", "created_at": "2026-08-04T12:00:00.000", "updated_at": null }, "meta": {"count": ..., "page_size": 100} }
7.3 PATCH /api/v1/platform/workspaces/{workspace_id}
部分更新。不可改:workspace_id、workspace_code、active_root_uri、created_by、时间戳、软删标记。
- 请求体字段(全部可选):
| 字段 | 类型 | 限制 | 说明 |
|---|---|---|---|
workspace_name |
string | 1~150 | |
quota_bytes |
int | ≥0 | |
description |
string | ≤1000 | |
status |
string | active | archived |
不允许 disabled——软删须走 DELETE |
- 错误:
status="disabled"→ 422;已 disabled 的 workspace → 409。
7.4 DELETE /api/v1/platform/workspaces/{workspace_id}
软删除。允许从 active 或 archived 状态调用。
- 副作用:
- 该 workspace 行:
status='disabled'、is_deleted=1、deleted_at=NOW() - 级联:所有未删除的
workspace_members行同步is_deleted=1、deleted_at=NOW()
- 该 workspace 行:
- 已 disabled 的 workspace 再删 → 409。
7.5 POST /api/v1/platform/workspaces/{workspace_id}/members
添加成员。
Workspace 角色继承平台角色:本端点不接受
role_code,新成员的workspace_members.role_id始终等于其users.platform_role_id指向的角色行。不可填system_admin(那是用户级身份,不是 workspace 角色)。要改某成员的 workspace 角色,请改users.platform_role_id,即PATCH /api/v1/platform/employees/{user_id}。
- 请求体字段:
| 字段 | 类型 | 必填 | 限制 | 说明 |
|---|---|---|---|---|
user_id |
string | 是 | 26 字符 ULID |
- 服务端默认
member_status='active'。 - 用户不存在或已软删除 → 404;用户状态不是
active→ 409;用户尚未分配平台角色(users.platform_role_id IS NULL)→ 409 "目标用户尚未分配平台角色,无法加入 workspace"。 - 用户已是该 workspace 成员 → 409 "用户已是该 workspace 成员;workspace 角色继承自平台角色,要变更请 PATCH /api/v1/platform/employees/{user_id} 修改 role_code"。
- 新员工必须先通过
POST /api/v1/platform/employees建立账号(可以同时传role_code=admin|developer)。
7.6 PATCH /api/v1/platform/workspaces/{workspace_id}/members/{user_id}
修改成员的状态。不能通过本端点修改 role_code——workspace 角色始终继承自 users.platform_role_id;要改角色请 PATCH /api/v1/platform/employees/{user_id}。
- 请求体字段(全部可选):
| 字段 | 类型 | 限制 | 说明 |
|---|---|---|---|
member_status |
string | active | disabled | locked |
停用 / 锁定最后 admin → 409 |
- 提交
role_code字段 → 422(Pydanticextra='forbid'),不是静默忽略。
7.7 DELETE /api/v1/platform/workspaces/{workspace_id}/members/{user_id}
软删除成员。
- 自我移除保护:
user_id == 当前管理员 user_id→ 403 "系统管理员不能把自己从 workspace 移除;如需退出,请删除整个 workspace" - 末位 admin 保护:若删除的是最后一个
admin角色活跃成员 → 409 - 不存在的成员 → 404
7.8 GET /api/v1/platform/employees
返回全平台所有未软删除员工。调用者必须是系统管理员;已认证但不是系统管理员时返回 403。
-
无请求体或查询参数。
-
包含
active、disabled、locked状态及未分配平台角色的员工。 -
users.is_deleted != 0的员工不会返回。 -
不设隐藏数量上限,结果按
created_at、user_id排序。 -
role_code、role_name表示users.platform_role_id对应的平台角色;未分配时均为null。 -
响应 200:
{ "request_id": "...", "data": [ { "user_id": "01HXY...", "username": "developer", "display_name": "开发人员", "email": "developer@example.com", "status": "active", "role_code": null, "role_name": null, "created_at": "2026-08-06T12:00:00.000" } ], "meta": {"count": 1} }
7.9 POST /api/v1/platform/employees
只创建平台员工账号,不创建任何 workspace_members 记录。调用者必须是系统管理员;已认证但不是系统管理员时返回 403。
- 请求体字段:
| 字段 | 类型 | 必填 | 限制 | 说明 |
|---|---|---|---|---|
username |
string | 是 | 2~64 字符 | 全平台唯一登录名 |
display_name |
string | 是 | 1~100 字符 | 显示名称 |
email |
string | 否 | ≤255 字符 | 邮箱,全平台唯一 |
password |
string | 是 | 8~72 字符 | 登录密码 |
role_code |
string | 否 | admin | developer |
平台角色;不传或 null 表示不分配角色(用户无法加入任何 workspace,见 §7.5) |
-
新用户状态固定为
active;role_code决定users.platform_role_id指向role_code='admin'/'developer'的 Roles 行,未传则platform_role_id=NULL。 -
平台仅有
admin/developer两个平台角色;不存在第三个角色枚举值。 -
密码使用 bcrypt 哈希保存;响应不包含
password或password_hash。 -
用户名或邮箱重复 → 409;
role_code取值非法 → 422。 -
创建成功后,可调用
POST /api/v1/platform/workspaces/{workspace_id}/members将用户加入指定 workspace(会要求用户已有platform_role_id,否则 409)。 -
响应 201:字段与 §7.8 的员工元素一致;当
role_code传入时,role_code/role_name反映platform_role_id;未传入时均为null。meta为空对象。
7.10 PATCH /api/v1/platform/employees/{user_id}
修改平台员工的显示名、邮箱、状态或平台角色。调用者必须是系统管理员。
- 请求体字段(全部可选):
| 字段 | 类型 | 限制 | 说明 |
|---|---|---|---|
display_name |
string | 1~100 | trim 后写入 |
email |
string | null | ≤255 | trim 后写入;空字符串归一为 null |
status |
string | active | disabled | locked |
直接写入 users.status |
role_code |
string | admin | developer |
同步设置 users.platform_role_id;admin 指向 role_code='admin' 的 Roles 行,developer 指向 role_code='developer' 的 Roles 行 |
-
禁止通过该端点修改:
username、password、password_hash、platform_role_id;请求体中包含这些字段 → 422。 -
role_code只能取admin/developer;不能通过本端点把platform_role_id置为null(降级为"无平台角色"须走单独的内部流程,前端不要尝试)。 -
role_code改动会立即级联同步该用户所有活跃 workspace 成员行的workspace_members.role_id(PATCH /employees在事务内UPDATE workspace_members SET role_id=:new WHERE user_id=:uid AND is_deleted=0)。否则/me返回的workspaces[].role_code与下游request_context都会读到过期角色。本端点是调整任何成员 workspace 角色的唯一入口。 -
自保护:
- 修改自身
status为非active→ 409 "不能停用当前登录账号"。 - 修改自身
role_code为developer(即降级系统管理员身份)→ 409 "不能降级自身管理员角色"。
- 修改自身
-
最后系统管理员保护:当目标用户当前为 active 系统管理员,本次变更会让其离开"active 系统管理员"集合(降级角色 / 停用账号)时,平台必须仍保留至少一名 active 系统管理员,否则 → 409 "platform 必须保留至少一个 active 系统管理员"。自保护在前、last-admin 计数在后(参考 CLAUDE.md 工程笔记)。
-
workspace last-admin 守卫(降级路径):因为
role_code改动会级联同步workspace_members.role_id,把admin降级为developer时必须额外检查该用户在每个他是唯一 active admin 的 workspace 中仍有替补 admin,否则 → 409,错误信息列出将失去 admin 的workspace_code列表;提示"请先在这些 workspace 中指定其他 admin,再降级该用户"。developer → admin升级路径不受此守卫约束(只增不减)。 -
目标用户不存在或已软删除 → 404 "用户不存在";
role_code对应的角色行不存在 → 422。 -
响应 200:返回更新后的
PlatformEmployeePayload,role_code/role_name反映最新的platform_role_id。
7.11 DELETE /api/v1/platform/employees/{user_id}
软删除平台员工;级联软删其所有 workspace_members 行。调用者必须是系统管理员。
-
行为:
- 设置
users.status='disabled'、users.is_deleted=1、users.deleted_at=NOW()。 - 同事务内
UPDATE workspace_members SET is_deleted=1, deleted_at=NOW() WHERE user_id=:user_id AND is_deleted=0。 - 不级联修改
workspaces记录,workspace 仍可被单独管理。
- 设置
-
保护:
- 删除自身 → 409 "不能删除当前登录账号"。
- 目标为唯一 active 系统管理员 → 409 "platform 必须保留至少一个 active 系统管理员"。
- 目标不存在或已软删除 → 404 "用户不存在"(与 §7.4 DELETE workspace 对已 disabled 返回 409 不同,本端点对已软删用户统一返回 404)。
-
软删后行为:
GET /api/v1/platform/employees不再返回该用户。POST /api/v1/platform/workspaces/{id}/members用同一user_id重新加入 → 404。POST /api/v1/platform/employees用同username重新创建 → 409(唯一索引)。
-
本端点不提供恢复接口,与其他 DELETE 端点行为一致。
-
响应 200:
{ "request_id": "...", "data": { "user_id": "01HXY...", "deleted": true }, "meta": {} }
7.12 GET /api/v1/platform/roles
列出所有 role_scope='platform' 的角色及其当前 permission_codes。调用者必须是系统管理员。
-
用于平台管理员配置页加载左侧"角色"下拉与权限矩阵。
-
返回结果按
role_code升序。 -
permission_codes与 DB 中role_permissions关联表的活跃(is_deleted = 0)行一致,按字典序排列;空集合表示该角色当前没有任何菜单权限。 -
is_builtin反映roles.is_builtin;当前 seed 的admin/developer均为1。 -
响应 200:
{ "request_id": "...", "data": [ { "role_id": "01HXY...", "role_code": "admin", "role_name": "管理员", "is_builtin": true, "permission_codes": [ "dashboard:view", "schedule:view", "script:view", "system:project:view", "system:user:view", "system:view" ] }, { "role_id": "01HXY...", "role_code": "developer", "role_name": "开发人员", "is_builtin": true, "permission_codes": [ "dashboard:view", "schedule:view", "script:view" ] } ], "meta": { "count": 2 } }
7.13 GET /api/v1/platform/roles/{role_code}/permissions
获取单个平台角色的当前 permission_codes。调用者必须是系统管理员。
-
用途:角色权限管理面板加载右侧"已分配"列;也用于前端做 diff 显示。
-
角色不存在 → 404;角色存在但
role_scope != 'platform'(例如只有workspace角色行匹配) → 404。 -
响应 200:
{ "request_id": "...", "data": { "role_id": "01HXY...", "role_code": "admin", "role_name": "管理员", "is_builtin": true, "permission_codes": [ "dashboard:view", "schedule:view", "script:view", "system:project:view", "system:user:view", "system:view" ] }, "meta": {} }
7.14 PATCH /api/v1/platform/roles/{role_code}/permissions
整体替换指定平台角色的 permission_codes(diff-based 写入,见下文)。调用者必须是系统管理员。
本端点只控制前端菜单可见性——不修改
system_admin_context的鉴权判定(role_code == "admin"始终等价于"拥有所有平台菜单权限")。若需调整 API 鉴权,请改backend.api.platform.system_admin_context,不要绕过本端点。
- 请求体字段:
| 字段 | 类型 | 必填 | 限制 | 说明 |
|---|---|---|---|---|
permission_codes |
string[] | 是 | 0~64 项;内部去重(首次出现优先);元素必须是现存且未软删的 permissions.permission_code |
完整替换集合(非 patch);传 [] 表示清空该角色的全部菜单权限 |
-
守卫顺序(load-bearing,不可调换):
- 角色不存在 /
role_scope != 'platform'→ 404。 admin角色:提交的permission_codes必须包含system:view,否则 → 409 "admin 角色必须保留 system:view 权限"。- 非
admin角色:permission_codes中禁止含system:*项 → 422 "非 admin 角色不能拥有 system:* 权限: [...]"。menu permission 只控前端展示,后端/api/v1/platform/*鉴权仍按role_code == "admin";若让 developer 拿到system:view,前端会渲染"系统管理"入口但所有 platform API 调用 403,UX 割裂。 - 任意
permission_code不在permissions表活跃行中 → 422 "未知的 permission_code: [...]"。 - 写入策略:diff-based —— 只 soft-delete
current \ new的关联,只 INSERTnew \ current的关联。重复提交同 payload 是 no-op;包含原有 codes 的 patch 不会触发(role_id, permission_id)主键冲突。严禁先全量 soft-delete 再全量 INSERT(会IntegrityError,因为软删行仍占主键 slot)。
- 角色不存在 /
-
写入后,响应
data.permission_codes为本次写入后的活跃集合,与再次GET §7.13完全一致。 -
响应 200:
{ "request_id": "...", "data": { "role_id": "01HXY...", "role_code": "admin", "role_name": "管理员", "is_builtin": true, "permission_codes": ["dashboard:view", "system:view"] }, "meta": {} } -
当前 seed 的 permission_code 全集(由 baseline
e1f2a3b4c5d6_rebuild_baseline直接写入 MySQL,不要在客户端另造一份):
permission_code |
module_code |
admin | developer |
|---|---|---|---|
dashboard:view |
dashboard | ✓ | ✓ |
script:view |
script | ✓ | ✓ |
schedule:view |
schedule | ✓ | ✓ |
system:view |
system | ✓ | |
system:user:view |
system | ✓ | |
system:project:view |
system | ✓ |
与现有 7.x 端点的语义差异(避免 reviewer 误读):
- 本端点不修改
users.platform_role_id,只调整role_permissions关联表。- "清空 developer 的全部权限"是合法操作;只有
admin受system:*强制约束。role_code不是permission_code,前端不要用前者去判断菜单可见性。
八、Jupyter 路由
本节是 Nginx 行为,不是直接 HTTP 端点。前端不要直接调用。
8.1 浏览器 → 用户打开 notebook
用户在前端点击某个 notebook,前端拼出 URL:
GET /jupyter/{workspace_id}/notebooks/{相对路径}.ipynb
GET /jupyter/{workspace_id}/lab/tree/{相对路径}.ipynb
GET /jupyter/{workspace_id}/api/contents/{相对路径}.ipynb
WS /jupyter/{workspace_id}/api/kernels/...
8.2 Nginx auth_request 鉴权
Nginx 收到上述请求后,先发一个内部子请求:
GET /internal-auth
↓
Nginx: 抽 X-Original-Workspace-Id + X-Original-URI + Cookie + Authorization
↓
GET /api/v1/auth/jupyter
↓
Backend 流程:
1. verify_jwt_token (HS256, JWT_SECRET)
2. require_workspace_member (WorkspaceMembers JOIN)
3. extract_notebook_path (只对 /notebooks/*.ipynb 做 lock 校验)
4. check_notebook_is_locked (Scripts.is_locked + owner)
5. runtime_client.get_workspace / start_workspace (拿子进程地址 + token)
↓
Backend 响应 200 + 响应头:
x-upstream-addr: <base_url>:<jupyter 子进程端口>
x-jupyter-internal-token: <子进程 token>
↓
Nginx: auth_request_set 捕获这两个变量,proxy_pass 到子进程并注入
Authorization: token $jupyter_token
↓
浏览器收到响应,**自始至终未接触 Jupyter Token**
8.3 鉴权失败码
| 状态 | 触发条件 |
|---|---|
| 401 | JWT 缺失/过期/校验失败 |
| 403 | 用户不在 workspace / notebook 被锁定且非 owner |
| 404 | workspace 不存在 |
| 503 | runtime 容器不可达(子进程启动失败) |
Nginx 把这些状态原样透传给浏览器,前端可在 onerror 里判断。
九、对象存储控制面
范围:Schedule worker 调用 backend 写
run_log/run_result用的 单端点 RPC。前端不要直接调用,也不要把这个路径用于任何用户输入。 历史上有 6 个/internal/v1/*端点,经过 P0-1 修复后只剩这 1 个; 删除的端点全部已迁移到 JWT 保护的/api/v1/data-resources/*与/api/v1/scripts/*路由(见 §五、§三)。
底层抽象:common.storage.AsyncStorageBackend(put/get/delete/exists/stat/ list/get_url/copy)。按 settings.storage_backend 选实现:"s3" 走
S3-兼容服务,"local" 走 LOCAL_STORAGE_BASE_DIR 子目录。
鉴权(P0-1 后)
所有 /internal/v1/* 路由都会校验请求头里的 service token:
X-Internal-Service-Token: <settings.internal_service_token>
- backend 与 schedule 必须把同一个值注入到
INTERNAL_SERVICE_TOKEN环境变量。 - 缺失或不相符 →
401。 - backend 配置为空 →
503(internal service token not configured)。
9.1 POST /internal/v1/objects
单步创建(不走两步上传,字节 base64 进 JSON 体)。适用 < 100 KiB
对象(避免 multipart/大请求体的前端复杂度)。内部直接调
AsyncStorageBackend.put(key, content, content_type=..., metadata={"sha256": ...})。
{
"workspace_id": "...",
"user_id": "...",
"usage_type": "run_log",
"file_name": "log.txt",
"content_type": "text/plain",
"content_base64": "PHN0ZXAtY29udGVudD4=",
"visibility": "private",
"is_immutable": true,
"idempotency_key": "...",
"relative_path": null
}
9.2 usage_type → 桶路由(自动)
| usage_type | 实际桶(env var) | 默认桶名 |
|---|---|---|
working_copy, public_script, data_resource, snapshot |
S3_WORKSPACE_BUCKET |
workspaces |
version_artifact |
S3_VERSION_BUCKET |
versions |
run_log, run_result |
S3_RUN_LOG_BUCKET |
run-logs |
| (soft-delete target) | S3_TRASH_BUCKET |
trash |
若 Workspaces.artifact_bucket 非空,优先用 per-workspace 桶(覆盖 usage_type 路由)。
桶在 STORAGE_BACKEND=s3 时是 4 个独立 S3 bucket,在
STORAGE_BACKEND=local 时是 LOCAL_STORAGE_BASE_DIR 下的 4 个子目录。
9.3 已删除的端点(P0-1 收纳)
| 旧端点 | 替代路由 | 说明 |
|---|---|---|
POST /internal/v1/uploads |
POST /api/v1/data-resources/uploads(JWT) |
前端走公开路径 |
PUT /internal/v1/uploads/{id} |
PUT /api/v1/data-resources/uploads/{id} |
同上 |
POST /internal/v1/uploads/{id}/abort |
(客户端取消即可) | 无后端状态 |
POST /internal/v1/objects/{id}/download-url |
各自的公开路由生成 presigned URL | download URL 由公开路由返回 |
DELETE /internal/v1/objects/{id} |
公开路由的删除操作 | 与 data-resource 联动 |
十、健康检查
| 方法 | 路径 | 用途 |
|---|---|---|
GET |
/ |
简单服务标识 |
GET |
/health/live |
进程存活(不检查依赖) |
GET |
/health/ready |
依赖就绪(可选 TCP 探测列表) |
GET |
/api/v1/health |
公开健康检查(前端可访问) |
/health/ready 支持 READINESS_TARGETS 环境变量,逗号分隔的 host:port
列表,例如 mysql:3306,s3:9000,全部 TCP 通则返回 200,否则 503。
STORAGE_BACKEND=local 模式下不需要 S3 host,列表里删掉即可。
附录 A — 错误码参考
| HTTP | 业务码 / 含义 | 触发场景 |
|---|---|---|
| 400 | 参数错误 | Pydantic 校验失败 |
| 401 | 未鉴权 | JWT 缺失/无效 |
| 403 | 鉴权失败 | 非 workspace 成员 / is_locked 阻写 / 非系统管理员访问 /api/v1/platform/* / 系统管理员自我移除 workspace 成员 |
| 404 | 不存在 | resource_id / script_id / schedule_id 找不到 |
| 409 | 冲突 | DAG 无效 / 同 idempotency_key 不同元数据 / 目标已存在 / is_immutable 阻删 / workspace 末位 admin 保护 |
| 412 | 条件失败 | source_object_id 与当前工作副本不一致 |
| 413 | 太大 | 内容超过 100 MiB / 10 MiB |
| 422 | 语义错误 | 文件名非法 / cron 表达式非法 / 路径逃逸 / workspace_code 不匹配 ^[a-z0-9-]{3,32}$ / status="disabled" 走 PATCH |
| 500 | 内部错误 | DB / 存储不可达 |
附录 B — 状态枚举
| 类型 | 取值 |
|---|---|
Workspaces.status |
active / archived / disabled(disabled 由 DELETE 设置,PATCH 不允许设) |
WorkspaceMembers.member_status |
active / disabled / locked |
StorageObjects.usage_type |
data_resource / version_artifact / snapshot / run_log / run_result / working_copy / public_script |
StorageObjects.object_status |
available / deleted |
StorageObjects.visibility |
private / workspace / public |
Scripts.status |
active / deleted |
Schedules.trigger_type |
manual / cron / api |
Schedules.failure_policy |
stop / continue |
ScheduleRuns.run_status |
queued / running / succeeded / failed / cancelled / timed_out |
ScheduleNodeRuns.node_status |
queued / running / succeeded / failed / skipped / cancelled / timed_out |
UploadSessions.upload_status |
created / uploading / completed / expired / aborted / failed |
附录 C — 通用枚举字段
| 字段 | 取值 |
|---|---|
visibility |
private / workspace / public |
is_locked |
0 / 1(Scripts 表 TINYINT) |
附录 D — 跨域与 Cookie
- Nginx 同源代理,前端与 API 同源,不需要 CORS 配置。
- 鉴权通过 Cookie 或
Authorization头携带(见 §一)。 - 上传类接口要求
Idempotency-Key,前端应在请求构造时就生成稳定 UUID 并缓存,失败重试时复用同一 key。