Files

404 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API 与本地执行器参考
## 认证
除健康检查和登录接口外,业务接口要求:
```http
Authorization: Bearer <session-token>
```
服务端会按以下顺序解析访问边界:
```text
user → organization → workspace → project → permission
```
请求头中的 `x-user-id`、`x-organization-id`、`x-workspace-id` 和 `x-project-id` 只有在显式设置 `AI_DRAMA_ALLOW_DEV_CONTEXT=1` 时才可用于本地接口调试。正式部署不要启用该 bypass。
## 业务 API 分组
| 分组 | 主要接口 | 典型权限 |
| --- | --- | --- |
| 认证与会话 | `/api/auth/login`、`/api/auth/session`、`/api/auth/logout`、`/api/auth/password`、`/api/auth/sessions/*` | 登录用户 |
| MFA 与企业身份 | `/api/auth/login/mfa`、`/api/auth/mfa/*`、`/api/auth/sso/*`、`/api/system/identity/*` | MFA 管理或 `system:settings:*` |
| 租户上下文 | `/api/context`、`/api/organizations/*`、`/api/workspaces/*`、`/api/projects/*` | 按组织 / 工作区 / 项目角色 |
| 全局生产搜索 | `/api/search?q=&scope=workspace|project` | 登录用户,结果严格受当前作用域限制 |
| 用户通知中心 | `/api/notifications`、`/api/notifications/:id`、`/api/notifications/read-all`、`/api/notification-preferences` | 登录用户 / 当前组织 |
| 内容生产 | `/api/production/catalog`、`/api/production/graph`、`/api/production/script/*`、`/api/production/bible`、`/api/production/shots/*` | `script:edit`、`asset:edit`、`job:create` |
| 资产与声音 | `/api/assets/*`、`/api/assets/:assetId/versions/upload`、`/api/assets/:assetId/verify`、`/api/assets/:assetId/versions/:versionId/restore` | `asset:edit`、`voice:edit` |
| 生成任务 | `/api/jobs`、`/api/jobs/:jobId/*`、`/api/adapters/dry-run` | `job:create` 或 `queue:manage` |
| 流程模板编排 | `/api/workflows/templates`、`/api/workflows/templates/:id/instantiate`、`/api/workflows/runs` | 读取 / 实例化:`script:read`、`job:create` 或 `project:create`;管理:`workflow:manage` |
| 媒体证据与存储 | `/api/production/media-artifacts`、`/api/production/media-artifacts/:id/content`、`/api/usage/storage`、`/api/system/storage/*` | 媒体:`qa:review` 或 `delivery:view`;存储回收:系统设置权限 |
| 审片与交付 | `/api/production/reviews/*`、`/api/production/qa/run`、`/api/production/deliveries/*`、`/api/exports/write` | `qa:review`、`delivery:approve` |
| 组织运营 | `/api/usage`、`/api/billing`、`/api/organizations/:id/commercial`、`/api/organizations/:id/usage`、`/api/organizations/:id/usage/export`、`/api/organizations/:id/billing`、`/api/organizations/:id/quotas/:quotaId`、`/api/audit`、`/api/audit/export` | `usage:view`、`billing:manage`、`quota:manage`、`audit:view` |
| 模型中台 | `/api/platform/models`、`/api/platform/models/register`、`/api/platform/models/:id/probe` | `model:manage` |
| 全局用户治理 | `/api/system/users`、`/api/system/users/:userId`、`/api/system/users/:userId/revoke-sessions` | 仅系统管理员 |
| 系统设置 | `/api/system/config`、`/api/system/health`、`/api/system/readiness`、`/api/system/backups`、`/api/system/feature-flags`、`/api/system/notifications`、`/api/system/api-clients` | 系统管理员 / 对应系统权限 |
未登录返回 `401`;已登录但没有作用域或权限返回 `403`。接口不会因为前端菜单隐藏就跳过后端检查。
### 全局生产搜索
```http
GET /api/search?q=雷雨&scope=workspace&limit=40
GET /api/search?q=雷雨&scope=project&limit=40
```
搜索接口只在当前登录会话的组织、工作区和可访问项目集合内查询,支持项目、分集、剧本、镜头、资产、协作任务、生成任务和交付记录。`scope=workspace` 搜索当前工作区内可访问项目;`scope=project` 只搜索当前项目。返回结果包含 `type`、`typeLabel`、`title`、`subtitle`、`status`、`targetTab` 和目标作用域 ID,前端命令面板可以据此直接切换项目并跳转到对应生产页面。空查询返回空结果,不会返回未经筛选的目录数据。
搜索不会把连接器 endpoint、API key、客户交付令牌或本地存储绝对路径作为可搜索字段返回;跨组织、跨工作区或无项目访问权的关键词不会出现在结果中。
### 流程模板与生产编排
流程模板是服务端版本化对象,不是前端写死的按钮配置。平台先提供全局内置模板,组织或工作区可以用同一 `templateKey` 创建自己的版本;列表按当前工作区优先、组织其次、全局最后合并同键模板。全局内置模板只能读取,不能直接修改。
```http
GET /api/workflows/templates?includeArchived=1
POST /api/workflows/templates
PATCH /api/workflows/templates/:templateId
POST /api/workflows/templates/:templateId/instantiate
GET /api/workflows/runs?status=&limit=50
```
创建或更新模板的核心结构如下:
```json
{
"name": "AI 漫剧单镜头生产",
"templateKey": "ai-manhua-drama",
"category": "ai-drama",
"status": "active",
"description": "关键帧 -> 图生视频 -> 固定配音 -> ASR -> 合成",
"defaultAdapterId": "owned-model-platform",
"steps": [
{ "key": "keyframe", "label": "单画面关键帧", "jobKind": "单画面关键帧", "requiresShot": true },
{ "key": "video", "label": "首尾帧图生视频", "jobKind": "首尾帧图生视频", "requiresShot": true, "dependsOnPrevious": true }
],
"gates": [
{ "key": "single-frame", "label": "一图一画面", "blocking": true }
]
}
```
`steps` 最多 32 个,模板状态只能是 `draft`、`active` 或 `archived`;只有 `active` 模板可以进入生产。`POST /instantiate` 要求当前项目上下文,支持:
- `mode=plan`:只生成流程计划,不创建生成任务,适合导演确认步骤和质检门。
- `mode=queue`:按步骤创建 `generation_jobs`,默认把前一步任务写入下一步 `depends_on`,失败步骤会使流程运行标记为 `blocked`。
- `shotId`:镜头级步骤必须绑定当前项目镜头;`episodeId`、`adapter`、`priority` 和 `approveExternal` 可作为运行参数传入。
运行记录保存模板版本、步骤状态、任务 ID、当前步骤、错误信息和创建人。模板创建、修改和实例化都会写审计;外部 / 混合成本适配器仍需显式审批,默认适配器为用户自有模型平台。
### 媒体证据、首末帧与存储治理
生成任务和合成任务完成后,平台会登记文件级媒体证据,保存相对路径、MIME、文件大小、SHA-256、时长、分辨率、音视频轨道状态,以及视频实际首帧和末帧。首末帧是 QA / 连续性依据,不接受 contact sheet、故事板拼图或前端临时缩略图替代。
```http
GET /api/production/media-artifacts?shotId=&jobId=&limit=200
GET /api/production/media-artifacts/:artifactId/content
GET /api/production/media-artifacts/:artifactId/content?frame=first
GET /api/production/media-artifacts/:artifactId/content?frame=last
```
媒体列表只返回当前组织、工作区和项目可见的证据;原始文件和首末帧内容接口返回二进制,带真实 `Content-Type`、`Content-Length`、内联文件名和基于内容 SHA-256 的 `ETag`。不存在、路径不安全或文件尚未落盘分别返回 `404` / `422` / `404`,不会把数据库里登记过的路径直接当成成功生成。
存储用量和回收接口如下:
```http
GET /api/usage/storage
GET /api/system/storage/cleanup-preview?olderThanDays=30
POST /api/system/storage/reclaim
```
`/api/usage/storage` 返回当前作用域的已用空间、限额、剩余空间、占用率、项目拆分和最大文件。清理预览根据 `storage.retention_days`(默认 30 天)扫描 `outputs/`、`frames/`、`tmp/`、`cache/` 下超过保留期的临时文件,并明确列出候选文件和预计释放字节。`reclaim` 可接收 `{ "paths": ["storage/jobs/.../outputs/tmp.png"] }` 指定执行,也可以不传路径执行全部预览候选。
回收始终保护被 `media_artifacts`、`asset_versions` 或 `media_compositions` 引用的路径,并限制在 `storage/` 安全相对路径内;执行结果写入 `system.storage.reclaimed` 审计事件。`storage.provider` 当前默认是 `local-filesystem`,S3-compatible 等外部存储只作为后续适配层,不会在本地环境自动启用。
### 审计与合规中心
```http
GET /api/audit?page=1&pageSize=25&query=&action=&targetType=&targetId=&result=&actorUserId=&from=YYYY-MM-DD&to=YYYY-MM-DD
GET /api/audit/:auditId
GET /api/audit/export?pageSize=500&format=json
GET /api/audit/export?pageSize=500&format=csv
```
审计列表由服务端分页并返回 `auditLog`、`pagination`、`facets` 和 `scope`。`org_owner` / `org_admin` 默认只能看当前组织;其他拥有审计读取权限的角色只能看当前工作区 / 项目及组织级事件;系统管理员获得全局视图。查询参数只负责缩小范围,不能扩大当前作用域;跨组织、跨工作区或跨项目筛选会返回 `403`。详情接口同时返回同一对象的关联审计和可关联的账号安全事件。导出支持 JSON 和 UTF-8 CSV,并复用相同的服务端权限与筛选条件。
### 用户通知中心与组织级偏好
通知中心是用户在当前组织、当前工作区内的收件箱,不等同于管理员配置的邮件或 Webhook 投递渠道。通知事件由服务端按组织成员、工作区/项目成员和事件类型计算收件人,前端不能扩大收件范围。
```http
GET /api/notifications?limit=60&unreadOnly=1
PATCH /api/notifications/:notificationId # { "read": true | false }
POST /api/notifications/read-all
GET /api/notification-preferences
PATCH /api/notification-preferences/:category # { "enabled": true | false }
```
`limit` 会被服务端限制在 1-200;`unreadOnly=1` 只改变列表,不改变返回的 `unreadCount`。单条已读/未读和“全部已读”都只作用于当前用户、当前组织以及当前工作区可见的通知,跨用户、跨组织或不属于当前作用域的通知返回 `404`。
通知偏好按“用户 + 组织 + 类别”保存,切换组织后使用独立配置。当前类别包括:生成任务、审片、交付、协作任务、组织访问、用量配额和系统通知。服务端在创建用户通知前真正检查偏好,关闭某类后不会只在前端隐藏,而是不会写入该用户的收件箱;重新开启只影响后续事件,不会自动补发历史消息。
### 项目协作任务
协作任务是可分派、可追踪的项目记录,不等同于由生成任务或审片状态推导出来的 `/api/work-items` 待办。任务只能落在当前组织 / 工作区 / 项目作用域内,并保存标题、说明、类型、优先级、负责人、截止时间、关联页面、状态、完成时间和审计记录。
```http
GET /api/tasks?status=all&assignedTo=me&limit=100
POST /api/tasks
PATCH /api/tasks/:taskId
```
`task:manage` 可以创建、分派和编辑任务;`task:complete` 的成员只能更新自己负责任务的状态,不能修改标题、负责人、优先级或截止时间;`task:view` 只能读取当前项目任务。任务分派会按用户通知偏好创建站内消息,任务状态变更和跨组织访问也会写入权限边界和审计流。项目归档后任务写操作返回 `409 project_archived`。
系统管理员配置的 `/api/system/notifications` 仍负责组织级本地日志/Webhook 渠道和投递审计;Webhook 默认只允许投递到本地私有 HTTP 地址,云端或付费节点不会被平台自动启用。
### 项目生命周期与只读归档
项目是可审计的生产边界,不是只有名称和状态的目录项。创建项目时会持久化 `template_id`,并立即初始化系列、第一季、试播集和一个 starter shot;新项目默认处于 `draft`。
```http
PATCH /api/projects/:projectId
POST /api/projects/:projectId/lifecycle
```
生命周期动作包括:`pause`、`resume`、`activate`、`submit-review`、`archive`、`restore`。归档前服务端会检查 `queued`、`running` 和 `blocked` 生成任务;存在未完成任务时返回 `409 project_has_active_jobs`。归档会保存 `archived_at`、`archived_by` 和 `archived_from_status`,恢复时回到归档前状态。
归档项目仍可读取生产图谱、任务历史、审计和交付资料,但剧本、资产、声音、生成、QA、交付审批等写操作由后端统一返回 `409 project_archived`。前端项目工厂只负责呈现可用操作,不能替代服务端权限边界。
### 系统生产就绪度与数据库快照
系统管理员可读取部署边界和当前运行时状态:
```text
GET /api/system/health
GET /api/system/worker
GET /api/system/readiness
GET /api/system/backups
POST /api/system/backups
```
`/api/system/health` 返回数据库中的服务探针、Runner 状态、队列深度和最近心跳;`/api/system/worker` 返回本地 Worker 的租约、并发、心跳年龄和队列告警。管理概览直接使用这些服务端结果,组织管理员只能看到后端允许的健康范围,不能通过前端隐藏绕过权限。
`/api/system/readiness` 会明确区分当前激活运行时与目标 provider:业务数据库当前返回 `node:sqlite`,PostgreSQL、Redis、S3-compatible 对象存储只有在对应 `PLATFORM_*` 环境变量存在时标记为“已配置”,不会把 compose 注入误报成已经完成业务迁移。`POST /api/system/backups` 使用 SQLite `VACUUM INTO` 创建 `data/backups/` 下的快照,并写入 `system.database.backup_created` 审计事件;接口不提供在线恢复,恢复必须经过停机审查和人工确认。
### 商业运营、席位与配额
组织管理员或组织所有者可以读取当前组织的套餐、席位预留、工作区配额和本月用量:
```text
GET /api/organizations/:organizationId/commercial
GET /api/organizations/:organizationId/commercial/export
PATCH /api/organizations/:organizationId/billing
PATCH /api/organizations/:organizationId/quotas/:quotaId
PATCH /api/organizations/:organizationId/cost-centers/:costCenterId
```
`billing` 更新接受 `planName`、`billingCycle`(monthly / quarterly / annual)、`currency`、`baseFee`、`seatUnitPrice`、`storageUnitPrice`、`clipUnitPrice`、`seatLimit`、`storageGb`、`monthlyClipQuota`、`quotaWarningPercent`、`localRunnerOnly` 和 `cloudConnectorsRequireApproval`。服务端会阻止席位低于活跃成员加待处理邀请、片段额度低于本月已用量、存储额度低于已记录用量的修改,并记录 `billing.account.updated` 审计事件和可查询的 `billing_account_events` 变更记录。
工作区配额不能超过组织套餐上限,也不能低于已用量;违反时分别返回 `409 quota_above_plan` 或 `409 quota_below_usage`。普通成员即使知道接口路径,也会收到 `403 permission_denied`,前端菜单隐藏不构成权限边界。
商业运营响应额外包含 `quotaWarnings`、`costCenters`、按工作区拆分的 `costCenterDetail`、`billingHistory` 和 `usageTrend`。`usageTrend` 是最近 31 天按自然日聚合的数组,每项为 `{ day, units, estimatedCost, events }`;没有事件的日期不会伪造为零值。成本中心预算由 `billing:manage` 控制,导出接口返回可归档的完整 JSON,不会把云端或付费连接器伪装成本地成本。
事件级用量中心用于账务核对、配额追踪和成本归属:
```text
GET /api/organizations/:organizationId/usage?page=1&pageSize=25&from=YYYY-MM-DD&to=YYYY-MM-DD&workspaceId=&projectId=&userId=&kind=&unitName=&costCenter=&query=
GET /api/organizations/:organizationId/usage/export?format=json&from=YYYY-MM-DD&to=YYYY-MM-DD&workspaceId=&projectId=&userId=&kind=&unitName=&costCenter=&query=
GET /api/organizations/:organizationId/usage/export?format=csv&from=YYYY-MM-DD&to=YYYY-MM-DD&workspaceId=&projectId=&userId=&kind=&unitName=&costCenter=&query=
```
明细接口返回 `items`、`summary`、`pagination` 和 `facets`。每条事件包含时间、工作区、项目、操作者、事件类型、计量单位、估算成本、成本中心和元数据;筛选、分页和导出都在服务端执行。组织管理员可查看本组织范围,普通成员即使直接调用路径也会收到 `403`;成本中心和配额预警在管理台可以回跳到同一组明细筛选条件。
组织账单台账用于把套餐、席位、存储、片段和事件级本地计量固化为可审计的账期快照:
```text
GET /api/organizations/:organizationId/invoices?status=&query=&page=1&pageSize=25
GET /api/organizations/:organizationId/invoices/:invoiceId
POST /api/organizations/:organizationId/invoices/generate
POST /api/organizations/:organizationId/invoices/:invoiceId/status
GET /api/organizations/:organizationId/invoices/export?format=json|csv&status=&query=
```
只有拥有 `billing:manage` 的组织管理员或组织所有者可以生成账单、修改账单状态和编辑计价参数;拥有 `usage:view` 的角色可以读取账单台账。普通成员直接调用接口也会收到 `403 permission_denied`,跨组织读取会收到 `403 organization_forbidden`。
`generate` 默认按组织套餐周期生成当前账期草稿,也接受成对的 `periodStart` / `periodEnd`、`taxRate` 和 `dueDays`。同一组织同一账期通过数据库唯一约束保证幂等,重复生成返回原账单而不会覆盖原快照。账单状态机为 `draft -> issued -> paid`,`issued` 可以转为 `overdue` 或 `void`,`overdue` 可以补记为 `paid`;已支付和已作废账单不可逆修改。每次生成和状态变更都会写入账单事件及组织审计日志,详情接口会返回 `lines` 明细。
套餐接口的计价字段包括 `baseFee`、`seatUnitPrice`、`storageUnitPrice` 和 `clipUnitPrice`,默认值为 0;本地环境不会凭空产生收费。生成账单时会保存套餐、席位、工作区存储、片段用量、成本中心和税率快照,JSON/CSV 导出可用于后续财务系统适配。
### 组织邀请生命周期
组织管理员或拥有 `organization:members:invite` 的角色可以管理成员邀请:
```text
GET /api/organizations/:organizationId
GET /api/organizations/:organizationId/members
POST /api/organizations/:organizationId/invitations
POST /api/organizations/:organizationId/invitations/:invitationId/resend
POST /api/organizations/:organizationId/invitations/:invitationId/revoke
GET /api/invitations/preview?token=:token
POST /api/auth/register
```
创建或重发邀请时,服务端只在当前响应返回一次性 `inviteToken` 和 `acceptUrl`;数据库保存的是令牌哈希和短提示,不保存明文令牌。邀请默认 7 天过期,过期邀请不会继续占用席位。重发会替换令牌哈希,使旧注册链接立即返回 `404 invitation_not_found`;撤销会清空令牌哈希、返回 `revoked`,并释放待入组席位。未登录预览仍可读取邀请范围,但注册时会再次校验令牌、邮箱和有效期。
邀请角色必须匹配目标层级:不传 `workspaceId` 时只能使用组织角色 `org_member` / `org_admin`;传 `workspaceId` 时只能使用工作区角色 `producer` / `writer` / `art_director` / `voice_editor` / `reviewer`;同时传 `workspaceId` 与 `projectId` 时只能使用项目角色 `project_editor` / `project_viewer`。项目级邀请接受后会自动建立 `project_guest` 工作区成员身份和 `access_mode=project-only`,不会把项目角色升级为工作区制片,也不会展示工作区其他项目;组织所有者不能通过普通邀请授予。
普通成员调用组织邀请接口返回 `403 permission_denied`;重复邀请同一邮箱返回 `409 invitation_already_pending`,已是组织成员的邮箱返回 `409 invitation_recipient_already_member`。
### MFA
- `POST /api/auth/login` 在账号启用 TOTP 后返回 `mfaRequired` 和短时 `challengeToken`,不创建正式会话。
- `POST /api/auth/login/mfa` 使用 `challengeToken + code` 完成二次验证并创建 Bearer session;挑战 5 分钟过期,连续错误 5 次锁定。
- `GET /api/auth/mfa`、`POST /api/auth/mfa/setup`、`POST /api/auth/mfa/enable`、`POST /api/auth/mfa/setup/cancel`、`POST /api/auth/mfa/disable` 只接受真实浏览器 session。
- MFA 密钥使用 AES-GCM 加密;生产部署应设置独立的 `AI_DRAMA_MFA_ENCRYPTION_KEY`,不要依赖默认开发密钥。
### 企业身份中心
系统管理员可通过以下接口管理身份控制面:
```text
GET /api/system/identity
PATCH /api/system/identity/policy
POST /api/system/identity/providers
PATCH /api/system/identity/providers/:providerId
POST /api/system/identity/providers/:providerId/probe
POST /api/system/identity/directory-syncs
PATCH /api/system/identity/directory-syncs/:directoryId
POST /api/system/identity/directory-syncs/:directoryId/rotate-token
GET /api/auth/sso/providers
GET /api/auth/sso/start?providerId=:id&returnTo=/
GET /api/auth/sso/callback
POST /api/auth/sso/redeem
```
OIDC 已实现 Authorization Code + PKCE、state/nonce 一次性状态、ID Token 签名校验(JWKS)、claims 映射、按 Provider 绑定组织/工作区、自动创建成员和正式 Bearer session。回调不会把 session token 放进 URL,而是跳转到前端兑换 60 秒一次性票据;票据重放返回 `401`。如果用户启用了 MFA,票据兑换会返回现有 MFA challenge/enrollment challenge,再复用 `/api/auth/login/mfa` 或 `/api/auth/mfa/enroll/*`。
提供商配置只保存 `clientSecretRef` 或 `idpCertRef` 环境变量引用,不保存 Client Secret 或 SAML 证书正文。OIDC 已实现 discovery 探测、Authorization Code + PKCE、state/nonce、JWKS 签名校验和一次性 SSO ticket。SAML 已实现 HTTP-Redirect AuthnRequest、持久化 RelayState、HTTP-POST ACS、签名/Audience/时间窗口/`InResponseTo` 校验、组织/工作区入组、MFA 和一次性 SSO ticket;ACS 不接受前端提交的用户身份。
### 全局用户治理
系统管理员可以跨组织查询用户、查看组织/工作区/项目归属、MFA 状态、最近登录和活跃会话:
```text
GET /api/system/users?query=&status=&limit=100
POST /api/system/users
GET /api/system/users/:userId
PATCH /api/system/users/:userId # status: active | suspended
POST /api/system/users/:userId/reset-password
POST /api/system/users/:userId/reset-mfa
POST /api/system/users/:userId/memberships
POST /api/system/users/:userId/revoke-sessions
```
系统管理员可以手动创建账号并一次性取得初始密码、重置密码或 MFA、授予/移除系统管理员身份,并将用户加入指定组织、工作区和项目。加入关系会经过组织席位、层级归属和角色 scope 校验。停用或安全重置会立即撤销该用户的全部 Bearer session;不能停用当前系统管理员,也不能停用最后一个系统管理员或组织唯一所有者。所有状态变化、密码/MFA 重置、归属变化和强制会话撤销都会写入审计日志。组织管理员仍只能访问本组织成员接口,不能读取全局目录。
生产部署至少应设置:`AI_DRAMA_SESSION_SECRET`、`AI_DRAMA_MFA_ENCRYPTION_KEY`、`AI_DRAMA_OIDC_STORAGE_KEY`、`AI_DRAMA_FRONTEND_ORIGIN` 和每个 Provider 引用的 Client Secret 环境变量。默认回调地址为 `http://127.0.0.1:8787/api/auth/sso/callback`,多实例部署请使用固定的 `AI_DRAMA_OIDC_REDIRECT_URI`。
SCIM 目录由管理员创建后得到一次性 Bearer 令牌;令牌只保存 SHA-256 哈希。启用目录后,使用该令牌调用:
```text
GET /scim/v2.0/:directoryId/Users
POST /scim/v2.0/:directoryId/Users
PATCH /scim/v2.0/:directoryId/Users/:userId
DELETE /scim/v2.0/:directoryId/Users/:userId
```
SCIM 用户只会进入该目录绑定的组织,停用操作会同时停用组织成员资格,不能跨组织写入。
资产文件版本会登记 `fileName`、`mimeType`、`fileSize` 和 `contentSha256`。`POST /api/assets/:assetId/versions/upload` 写入新的真实文件版本,`POST /api/assets/:assetId/verify` 重新读取文件并比较哈希/大小,验证结果写入审计日志;`GET /api/assets/:assetId/content` 返回 ETag,便于下游缓存和交付校验。
## Worker API
本地 API 启动时会自动启动本地 Worker;不需要额外启动第二个 Worker 进程。Worker 会:
- 使用数据库租约 `leased_by` / `leased_at` 领取任务,避免多个 Runner 重复执行。
- 受 `AI_DRAMA_WORKER_CONCURRENCY` 限制,默认并发为 `2`,最大为 `8`。
- 按 `AI_DRAMA_WORKER_POLL_MS` 轮询,默认 `1200ms`。
- 只领取 `status=queued`、依赖已完成、连接器 `status=ready` 且 `cost_mode=local` 的任务。
- 连接器异常时保留 attempt、错误信息、审计事件和通知事件;未达到 `max_attempts` 会按指数退避自动重新排队,超过上限才保持 `failed`。
- 发现 `leased_at` 超时的 running 任务时会回收租约,按最大尝试次数重新排队或标记失败,避免 Worker 进程退出后任务永久卡住。
- 心跳、队列最老任务等待时长和告警级别会写入 Worker 状态,管理员可以批量重试或取消任务。
```text
GET /api/admin/queue
POST /api/admin/queue/batch # action: retry | cancel | priority
GET /api/system/worker
POST /api/system/worker/dispatch
```
`GET /api/system/worker` 返回 `workerId`、`healthStatus`、心跳年龄、过期阈值、并发数、轮询间隔、队列深度、队列告警、当前执行数、最近领取 / 完成 / 失败 / 回收 / 重试时间和最后错误。`POST /api/system/worker/dispatch` 用于管理员立即触发一次领取,适合排障和演示。
可选环境变量:
```text
AI_DRAMA_WORKER_ENABLED=1
AI_DRAMA_WORKER_ID=local-worker-<host>
AI_DRAMA_WORKER_POLL_MS=1200
AI_DRAMA_WORKER_CONCURRENCY=2
AI_DRAMA_WORKER_LEASE_MS=300000
AI_DRAMA_WORKER_STALE_MS=30000
```
如果设置 `AI_DRAMA_WORKER_ENABLED=0`,任务不会自动执行,后台仍会显示 Worker 已暂停。
`POST /api/jobs/:jobId/run` 允许管理员或拥有 `job:create` 的角色手动执行任务;如果请求到达时任务已经被本地 Worker 完成,服务端返回现有结果并带 `idempotent: true`,避免手动按钮与自动 Worker 竞态导致重复生成。
## 模型协议适配器
模型连接器在模型中台注册,密钥只保存为环境变量名,不保存密钥本身:
```json
{
"label": "本地单画面图片服务",
"endpoint": "http://127.0.0.1:7860/api/generate/image",
"kind": "http-json",
"capability": ["text-to-image", "single-frame"],
"costMode": "local",
"authEnv": "LOCAL_IMAGE_TOKEN",
"protocol": {
"healthRoute": "health",
"routes": { "image": "generate" },
"models": { "image": "qwen-image-local" }
}
}
```
支持三种协议:
- `http-json`:向 `endpoint` POST 完整生产合同和 `execution` 元数据。
- `openai-compatible`:按 `image`、`video`、`tts`、`asr`、`chat` 选择路由和模型;图片请求固定发送 `n: 1`。
- `comfyui`:向 `prompt` 路由提交工作流,作为可选桥接,不是默认生产链路。
图片任务会检查响应中的 `data`、`images` 或 `outputs` 数组,必须恰好返回一项;返回多张、拼图、分屏或 contact sheet 的结果会进入失败状态,不会进入后续视频链路。
外部或混合成本连接器默认需要管理员显式审批;本地 Worker 不会自动领取 `mixed` / `cloud` 任务。
## 启动与验证
```bash
cd /Users/xz/Documents/daima/ai短剧/ai-drama-platform
npm run api
npm run dev
```
验证顺序建议:
```bash
npm run build
npm run smoke:identity
npm run smoke:system-users
npm run smoke:commercial-ops
npm run smoke:oidc
npm run smoke:tenant
npm run smoke:creator-suite
npm run smoke:ops
npm run smoke:production-catalog
npm run smoke:worker
npm run smoke:all
```
多个 smoke 会写入同一个 SQLite 文件,应顺序执行。