Files
model-platform/API.md
T

55 KiB
Raw Blame History

模型平台接口文档

本文档面向前端开发者与第三方集成方。所有接口的入口是 Nginx 网关(默认 http://localhost:8888),除 /api/v1/auth/jupyter 由 Nginx auth_request 自动调用,其他接口都通过 /api/v1/... 同源访问。

服务端基础 URL 示例: http://localhost:8888

通用响应外壳:

{
  "request_id": "01HXY...",
  "data": { ... },
  "meta": {}
}

错误响应为标准 HTTP 4xx / 5xx,body 为 {"detail": "..."}{"code": "...", "message": "...", "details": {}}

目录

  1. 鉴权
  2. 统一约定
  3. 脚本 / Notebook (/api/v1/scripts/...)
  4. 调度 (/api/v1/schedules/... + /api/v1/schedule-runs/...)
  5. 数据资源 (/api/v1/data-resources/...)
  6. 管理后台 (/api/v1/admin/...)
  7. 系统管理 (/api/v1/platform/...)
  8. Jupyter 路由 (Nginx auth_request)
  9. 对象存储控制面 (/internal/v1/objects,服务间 RPC + Token 鉴权)
  10. 健康检查

一、鉴权

平台用 JWT (HS256)。登录后,前端在后续请求里任选一种携带方式:

  • Cookie(推荐用于浏览器):登录成功后后端种 Authorization 或自定义 cookie;前端无需手写。
  • Authorization: Bearer <token>(推荐用于脚本与第三方)。

JWT_SECRET 由后端从 Settings.jwt_secret 读取,前端不需要知道,只需要保证登录态带过来即可。

  • AuthorizationCookie 同时存在时,后端优先使用 Authorization
  • 缺失或过期 → HTTP 401
  • 有效但用户不在 workspace → HTTP 403(由 Nginx auth_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。

1.1 登录 / 会话

方法 路径 说明
POST /api/v1/auth/login 校验用户名密码,种 access_token HttpOnly Cookie,返回 user + workspaces
POST /api/v1/auth/logout 清除 Cookie(幂等)
GET /api/v1/auth/me 返回当前登录用户与可访问 workspace 列表

1.2 PATCH /api/v1/auth/me — 修改本人资料

当前登录用户修改自己的显示名 / 邮箱。不需要 workspace_id

  • 请求体(至少提供一个字段;多余字段 → 422):
字段 类型 限制 说明
display_name string 1~100 trim 后写入
email string | null ≤255 trim 后写入;空字符串归一为 null;与其他未软删用户冲突 → 409 "邮箱已存在"
  • 不可通过本端点修改:usernamepasswordstatusplatform_role_id
  • 响应 200:data.userGET /me 中的 user 形状一致(含 role_code / is_system_admin / permissions)。

1.3 POST /api/v1/auth/password — 本人修改密码

  • 请求体:
字段 类型 限制 说明
current_password string 1~72 当前密码
new_password string 8~72 新密码
  • 当前密码错误 → 400 "当前密码不正确"。
  • 新密码与当前密码相同 → 400 "新密码不能与当前密码相同"。
  • 成功后清除会话 Cookie,客户端须引导用户重新登录。
  • 响应 200:data.password_changed = true

二、统一约定

类别 约定
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_pathscoped_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

  • 查询参数:
    类型 必填 说明
    path string 相对路径,例如 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=0StorageObjects 行。
  • 鉴权: 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_path string 父目录相对路径,空字符串或缺省表示用户根目录
    owner_user_id string 目标 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):
      字段 类型 说明
      path string workspace 内相对路径
      name string path 的最后一段
      parent_path string 父目录相对路径,根目录用空串
      owner_user_id string 该目录行所属 owner 的 user_id(owner_user_id 参数缺省时=请求者本人)
      has_children bool 该目录下是否还有直接子目录(后端额外 EXISTS 查询,可为空目录为 false)
  • 空结果: 不返回 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 时可见。
  • 非递归: 仅返回 parent_path 下的直接子级脚本(懒加载用);子目录脚本需带 parent_path 再次请求。
  • 查询参数:
    类型 必填 说明
    parent_path string 父目录相对路径,空串/缺省=该 owner 根目录下的一级脚本
    owner_user_id string 目标 owner;缺省=请求者本人
    keyword string 名称模糊匹配
  • 响应: dataScriptPayload 数组(见 §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 | notebook
    content 文本内容(.ipynb 必须是合法 JSON,含 cells 数组)
    visibility private | workspace (默认) | public
    parent_path 父目录路径

3.7 POST /api/v1/scripts/upload?file_name=...&parent_path=...&visibility=...

multipart/binary 形式上传大文件(走 server-proxied PUT,详见 §九)。

  • 查询参数: file_name(必填)、parent_pathvisibility
  • 请求体: 原始文件字节(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 == 1403

  • 请求体: {"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.8 PUT /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} 详情
GET /api/v1/data-resources/{id}/content 同源流式读取文件字节(预览/下载)
GET /api/v1/data-resources/{id}/preview 表格抽样预览(csv/tsv, limit 默认 100)
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_path string 父目录相对路径,空串/缺省=该 owner 根目录下的一级资源
    owner_user_id string 目标 owner;缺省=请求者本人
    visibility string workspace | public | private,二次过滤
    keyword string 名称模糊匹配
  • 响应: dataResourcePayload 数组,每条带 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
    • 创建成功后的响应包含 passwordpassword_hash
  • 响应 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=0member_status='active')均开放。这是为了让普通(非 admin)用户能在 脚本目录树里渲染同 workspace 其他成员的折叠分组(跨 owner 浏览,见 §3.4)。 其余 members 写端点(POST/PATCH/DELETE members)仍仅限系统管理员。

方法 路径 说明
GET /api/v1/platform/employees 列全平台未软删员工(cursor 分页 + q 搜索,见 §7.0
POST /api/v1/platform/employees 创建平台员工账号(返回 201);不自动加入任何 workspace
PATCH /api/v1/platform/employees/{user_id} 改员工资料/状态/平台角色(仅系统管理员)
POST /api/v1/platform/employees/{user_id}/reset-password 重置员工密码(仅系统管理员;不需要旧密码)
DELETE /api/v1/platform/employees/{user_id} 软删员工;级联软删其 workspace 成员关系(仅系统管理员)
GET /api/v1/platform/workspaces 列 workspace(active/archived)cursor 分页 + q 搜索,见 §7.0
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,后者会级联软删所有成员。
  • GET /employeesGET /workspaces 支持 cursor 分页与关键字搜索(见 §7.0);成员列表仍静默 pageSize=100 上限。
  • 跨 workspace 操作需要 ?workspace_id= query 参数,与 /api/v1/admin/...(workspace 内成员管理)不要混淆。

7.0 列表分页约定(employees / workspaces

GET /api/v1/platform/employeesGET /api/v1/platform/workspaces 使用 keyset cursor 分页(无 offset / page)。

Query 类型 默认 说明
limit int 10 每页条数,范围 1~200
cursor string 上一页返回的 meta.next_cursor;缺省为第一页;非法值 → 400
q string 关键字搜索。employees 匹配 display_name/username/emailworkspaces 匹配 workspace_name/workspace_code/description

响应 meta

{
  "limit": 10,
  "page_count": 10,
  "total_count": 156,
  "has_more": true,
  "next_cursor": "..."
}
  • total_count:当前筛选条件下的总条数(用于页码展示)。
  • next_cursor:无下一页时为 null
  • 排序键:(created_at ASC, id ASC)。前端用 cursor 栈实现「上一页 / 下一页 + 已访问页码」;不支持任意跳到未访问过的深页。

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_idworkspace_codeactive_root_uricreated_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}

软删除。允许从 activearchived 状态调用。

  • 副作用:
    • 该 workspace 行:status='disabled'is_deleted=1deleted_at=NOW()
    • 级联:所有未删除的 workspace_members 行同步 is_deleted=1deleted_at=NOW()
  • 已 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(Pydantic extra='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

  • 无请求体或查询参数。

  • 包含 activedisabledlocked 状态及未分配平台角色的员工。

  • users.is_deleted != 0 的员工不会返回。

  • 不设隐藏数量上限,结果按 created_atuser_id 排序。

  • role_coderole_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 哈希保存;响应不包含 passwordpassword_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;未传入时均为 nullmeta 为空对象。

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 行
  • 禁止通过该端点修改:usernamepasswordpassword_hashplatform_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_codedeveloper(即降级系统管理员身份)→ 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.10.1 POST /api/v1/platform/employees/{user_id}/reset-password

系统管理员为指定员工设置新密码(不校验旧密码)。JWT 无服务端吊销列表,已有会话在过期前仍可用;对方下次登录须用新密码。

  • 请求体:
字段 类型 限制 说明
new_password string 8~72 新密码;bcrypt 哈希后写入 password_hash
  • 目标不存在或已软删 → 404 "用户不存在"。
  • 响应 200:data = { "user_id": "...", "password_reset": true }

7.11 DELETE /api/v1/platform/employees/{user_id}

软删除平台员工;级联软删其所有 workspace_members 行。调用者必须是系统管理员。

  • 行为:

    • 设置 users.status='disabled'users.is_deleted=1users.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,不可调换):

    1. 角色不存在 / role_scope != 'platform' → 404。
    2. admin 角色:提交的 permission_codes 必须包含 system:view,否则 → 409 "admin 角色必须保留 system:view 权限"。
    3. admin 角色:permission_codes禁止system:* 项 → 422 "非 admin 角色不能拥有 system:* 权限: [...]"。menu permission 只控前端展示,后端 /api/v1/platform/* 鉴权仍按 role_code == "admin";若让 developer 拿到 system:view,前端会渲染"系统管理"入口但所有 platform API 调用 403,UX 割裂。
    4. 任意 permission_code 不在 permissions 表活跃行中 → 422 "未知的 permission_code: [...]"。
    5. 写入策略:diff-based —— 只 soft-delete current \ new 的关联,只 INSERT new \ 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 的全部权限"是合法操作;只有 adminsystem:* 强制约束。
  • 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)

  • Nginx 同源代理,前端与 API 同源,不需要 CORS 配置。
  • 鉴权通过 Cookie 或 Authorization 头携带(见 §一)。
  • 上传类接口要求 Idempotency-Key,前端应在请求构造时就生成稳定 UUID 并缓存,失败重试时复用同一 key。