# api-server 主工程 crate 占位说明 日期:`2026-04-20` ## 1. crate 职责 `api-server` 是新后端的 Axum 主工程 crate,后续负责: 1. `main.rs` 启动入口 2. `Router` 装配 3. `with_state` 共享状态注入 4. 中间件挂载 5. `/healthz`、`/api/*`、SSE 与静态资源兼容层装配 6. 由 `../../scripts/dev.ps1` 与 `../../scripts/dev.sh` 驱动的本地开发启动链路 7. 由 `../../scripts/test.ps1` 与 `../../scripts/test.sh` 驱动的本地测试链路 8. 由 `../../scripts/check.ps1` 与 `../../scripts/check.sh` 驱动的本地统一检查链路 9. 由 `../../scripts/smoke.ps1` 与 `../../scripts/smoke.sh` 驱动的本地启动与协议冒烟链路 ## 2. 当前阶段说明 当前目录已经完成以下基础骨架: 1. 目录占位 2. `Cargo.toml` 3. `src/main.rs` 4. `src/app.rs` 5. `src/state.rs` 6. `src/config.rs` 7. 基础 `TraceLayer` 挂载 8. 接入 `shared-logging` 完成 `tracing subscriber` 初始化 9. 接入 `POST /api/auth/entry` 首版密码登录链路 10. 接入 `POST /api/auth/password/change` 登录后修改密码链路 11. 接入 `POST /api/auth/password/reset` 手机验证码重置密码链路 12. 接入 `POST /api/assets/direct-upload-tickets` 直传票据接口 13. 接入 `GET /api/auth/me` 当前用户查询链路 14. 接入 `POST /api/auth/refresh` refresh token 轮换链路 15. 接入 `POST /api/auth/logout` 当前设备退出链路 14. 接入 `POST /api/assets/objects/confirm` 上传完成确认链路 15. 接入 `GET /api/auth/login-options` 登录方式探测链路 16. 接入 `POST /api/auth/phone/send-code` 手机验证码发送链路 17. 接入 `POST /api/auth/phone/login` 手机验证码登录链路 18. 接入 `GET /api/auth/wechat/start` 微信授权起跳链路 19. 接入 `GET /api/auth/wechat/callback` 微信回调换取系统登录态链路 20. 接入 `POST /api/auth/wechat/bind-phone` 微信待绑定账号补绑手机号链路 21. 接入 `POST /api/assets/objects/bind` 已确认对象绑定业务实体槽位链路 22. 接入 `POST /api/assets/sts-upload-credentials` 禁用式 STS 写权限 contract 23. 接入 `custom-world-library`、`custom-world-gallery` 与 agent `publish_world` 首批 Axum facade 24. 接入 custom world agent `session create / session snapshot` Axum facade 25. 接入旧 `runtime story` 兼容接口: - `POST /api/runtime/story/state/resolve` - `GET /api/runtime/story/state/{session_id}` - `POST /api/runtime/story/actions/resolve` - `POST /api/runtime/story/initial` - `POST /api/runtime/story/continue` 26. 接入 `POST /api/assets/character-visual/generate` 27. 接入 `GET /api/assets/character-visual/jobs/{task_id}` 28. 接入 `POST /api/assets/character-visual/publish` 29. 接入 `GET /api/assets/character-animation/templates` 30. 接入 `POST /api/assets/character-animation/import-video` 31. 接入 `GET /api/assets/character-workflow-cache/{character_id}` 32. 接入 `POST /api/assets/character-workflow-cache` 33. 接入 `POST /api/assets/character-animation/generate` 34. 接入 `GET /api/assets/character-animation/jobs/{task_id}` 35. 接入 `POST /api/assets/character-animation/publish` 36. 接入旧 `/generated-character-drafts/*`、`/generated-characters/*`、`/generated-animations/*`、`/generated-custom-world-scenes/*`、`/generated-custom-world-covers/*`、`/generated-qwen-sprites/*` 到 OSS 私有读代理 后续与本 crate 直接相关的任务包括: 1. [x] 接入统一日志与 tracing 2. [x] 接入 `request_id` 3. [x] 接入统一错误处理中间件 4. [x] 接入 response envelope 5. [x] 接入 `/healthz` 6. [x] 接入 `/api/auth/entry` 7. [x] 接入 `/api/assets/direct-upload-tickets` 8. [x] 接入 `/api/auth/me` 9. [x] 接入 `/api/auth/refresh` 10. [x] 接入 `/api/auth/logout` 11. [x] 接入 `/api/assets/objects/confirm` 12. [x] 接入 `/api/auth/login-options` 13. [x] 接入 `/api/auth/phone/send-code` 14. [x] 接入 `/api/auth/phone/login` 15. [x] 接入 `/api/auth/wechat/start` 16. [x] 接入 `/api/auth/wechat/callback` 17. [x] 接入 `/api/auth/wechat/bind-phone` 18. [x] 接入 `/api/assets/objects/bind` 19. [x] 接入 `/api/assets/sts-upload-credentials` 20. [x] 接入 `custom world library / gallery / publish_world` 首批 facade 21. [x] 接入 `custom world agent session create / snapshot` facade 22. [x] 接入旧 `runtime story` compat facade 23. [x] 接入 `character-visual generate / jobs / publish` 第一批 OSS 主链兼容 facade 24. [x] 接入 `character-animation templates / import-video` 第一批 OSS 草稿兼容 facade 25. [x] 接入 `character-workflow-cache get / save` 第一批 OSS JSON 草稿兼容 facade 26. [x] 接入 `character-animation generate / jobs / publish` 第一批 OSS 主链兼容 facade 27. [x] 接入旧 `/generated-*` 路径到 OSS 私有读同源代理 当前 tracing 约定: 1. 进程启动时通过 `shared-logging` 统一初始化 `tracing subscriber`。 2. 默认日志过滤器来自 `GENARRATIVE_API_LOG`,未提供时回落到 `info,tower_http=info`。 3. HTTP 访问日志统一通过 Axum 路由层的 `TraceLayer` 输出,后续 `request_id`、响应头与错误中间件继续在同一层扩展。 当前 request context 约定: 1. 中间件优先读取来访 `x-request-id`,未提供时生成新的 UUID。 2. `request_id` 会统一写入请求 `extensions` 与请求头,供 tracing、错误处理中间件和响应头层复用。 3. 最终响应会回写同一个 `x-request-id`,保证调用方、日志链路和后续 envelope `meta.requestId` 可对齐。 当前错误处理中间件约定: 1. 对 Axum 默认产生的空 `4xx / 5xx` 响应,统一归一化为 legacy 兼容 JSON 错误体:`{ error: { code, message, details? } }`。 2. 已经带 `content-type` 的业务错误响应不会被覆盖,避免抢走后续 response envelope 的职责。 3. 统一错误日志会复用当前请求的 `request_id`,便于后续和响应头、envelope 元信息串联。 当前 response envelope 约定: 1. `RequestContext` 已记录 `request_id`、请求开始时间、默认 `operation` 与 envelope 协商结果。 2. `json_success_body(...)` / `json_error_body(...)` 会根据 `x-genarrative-response-envelope` 自动在“裸数据 / 标准 envelope / legacy error + meta”之间切换。 3. `meta.apiVersion`、`meta.requestId`、`meta.routeVersion`、`meta.operation`、`meta.latencyMs`、`meta.timestamp` 已按当前前端契约生成,响应头回写仍留给后续独立任务。 当前基础响应头约定: 1. 所有响应都会回写 `x-request-id`。 2. 所有响应都会回写固定的 `x-api-version`,当前值与 body `meta.apiVersion` 保持一致。 3. 所有响应都会回写 `x-route-version`,当前阶段默认与 `x-api-version` 保持一致,后续再按路由粒度细分。 4. 所有响应都会回写 `x-response-time-ms`,值来源于 `RequestContext` 内记录的请求开始时间。 当前 `/healthz` 约定: 1. 路径固定为 `/healthz`。 2. 裸响应继续返回 `{ ok: true, service: "genarrative-node-server" }`,保持与当前 Node 工程兼容。 3. 当请求携带 `x-genarrative-response-envelope` 时,`/healthz` 会返回标准 success envelope。 4. `x-request-id`、`x-api-version`、`x-route-version`、`x-response-time-ms` 会在 `/healthz` 响应中一并回写。 当前本地检查链路约定: 1. `../../scripts/check.ps1` 与 `../../scripts/check.sh` 统一串联 `cargo fmt --all --check`、`cargo clippy`、`cargo check`、`cargo test`。 2. 默认检查整个 `server-rs` workspace,确保后续多 crate 扩容时仍然保持统一口径。 3. 当只需聚焦单个 crate 时,可通过 `-Package` 或 `SERVER_RS_CHECK_PACKAGE` 收窄 `clippy / check / test` 目标。 4. `cargo fmt --all --check` 仍固定覆盖整个 workspace,避免多 crate 下格式基线漂移。 当前本地 smoke 链路约定: 1. `../../scripts/smoke.ps1` 与 `../../scripts/smoke.sh` 会先构建 `api-server`,再拉起临时本地进程完成冒烟验证。 2. smoke 当前固定校验 `/healthz` 的 raw 响应、envelope 响应以及 `x-request-id`、`x-api-version`、`x-route-version`、`x-response-time-ms` 头。 3. smoke 通过后,可作为“Axum 服务可独立启动且基础 contract 可联通”的本地自动化证据。 ## 3. 边界约束 1. `api-server` 负责 HTTP、SSE、Cookie、Header、路由与协议装配。 2. 业务逻辑优先通过独立模块 crate 暴露能力,再由主工程组合。 3. 外部副作用通过 `platform-auth`、`platform-oss`、`platform-llm` 与各模块 crate 的应用层完成。 4. 不把领域规则直接堆在 handler 中。 5. 当前密码登录由 `module-auth` 负责用例编排,`api-server` 只负责请求解析、JWT 签发与 refresh cookie 写回。 6. 当前 `/api/auth/me` 复用现有 Bearer JWT 中间件与 `module-auth` 用户快照查询,不直接绕过模块边界读取内部状态。 7. 当前 `/api/auth/refresh` 复用 `module-auth` 的 refresh session 轮换能力,`api-server` 负责 refresh cookie 读取、失败清理与 access token 重签。 8. 当前 `/api/auth/logout` 复用 `module-auth` 的当前会话吊销与用户版本递增能力,`api-server` 负责 Bearer JWT、refresh cookie 读取与清理 cookie 回写。 9. 当前 `/api/assets/objects/confirm` 先由 `platform-oss` 完成私有 `HEAD Object` 校验,再通过 `spacetime-client` 调用 `spacetime-module` 的对象确认持久化入口。 10. 当前 `/api/assets/objects/bind` 只绑定已确认对象到业务实体槽位,不访问 OSS,不创建悬空 `asset_object_id`。 11. 当前手机号登录与微信登录都复用 `module-auth` 的进程内认证仓储,`api-server` 负责请求解析、场景判定、系统 JWT 签发与 refresh cookie 写回。 12. 当前微信回调不会把第三方 token 直接透传给前端或 SpacetimeDB,而是统一换成系统签发的 JWT。 13. 当前 `/api/assets/sts-upload-credentials` 按“服务器上传、Web 只下载”口径固定返回 `403`,不向浏览器下发 OSS 写权限。 14. 当前 `/api/runtime/custom-world/agent/sessions` 与 `/api/runtime/custom-world/agent/sessions/{session_id}` 只提供 deterministic session 骨架与 snapshot 读取,不承诺 message submit、operation query、card detail 的完整能力。 15. 当前 `/api/runtime/story/*` 已在 Rust 侧补齐 compat handler,但内部仍是 `runtime_snapshot` 驱动的兼容桥与确定性动作编排,不应误判为真正的 SpacetimeDB `resolve_story_action` 真相链已完成。 16. 当前 `/api/assets/character-visual/*` 第一批只保证旧接口 contract、OSS 草稿/正式对象、`asset_object` 与 `asset_entity_binding` 主链可用;真实图片模型、workflow cache 与本地角色覆盖写回仍在后续阶段。 17. 当前 `/api/assets/character-animation/import-video` 第一批只接受 `data:video/*;base64,...` 并写入 OSS 草稿区,不读取旧本地 `public/` 路径,也不创建正式 `asset_object`。 18. 当前 `/api/assets/character-workflow-cache/*` 第一批只把工作流 JSON 草稿写入 OSS,不迁移历史本地缓存,也不创建正式 `asset_object`。 19. 当前 `/api/assets/character-animation/generate` 第一批只用 Rust 占位产物打通 `AiTaskService + OSS` 草稿链;`image-sequence` 写 SVG 帧,视频类策略优先复用参考视频或仓库内可播放占位视频,不代表真实上游视频模型已完成迁移。 20. 当前 `/api/assets/character-animation/publish` 会把前端提交帧、动作级 manifest 与总 manifest 写入 OSS,并只把总 manifest 确认为 `asset_object` 后绑定到 `character / animation_set`。 21. 当前旧 `/generated-*` 读取兼容层只代理受支持 generated 前缀到 OSS 私有读签名,不回退仓库 `public/`,Stage 1 不支持视频 Range 分片。