docs: initialize rewrite plan and freeze backend surfaces
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
## 文档列表
|
||||
|
||||
- [SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md](./SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md):基于当前 Node 后端能力清单,设计用 `SpacetimeDB + Axum + 阿里云 OSS` 重写后端的目标架构、模块映射、数据分层、迁移顺序与验收标准。
|
||||
- [REPO_NOISE_CLEANUP_BASELINE_2026-04-19.md](./REPO_NOISE_CLEANUP_BASELINE_2026-04-19.md):落实工程清理审计第一阶段后的仓库噪音清理范围、忽略规则闭合点与后续约束。
|
||||
- [PROMPT_DIRECTORY_MANAGEMENT_2026-04-19.md](./PROMPT_DIRECTORY_MANAGEMENT_2026-04-19.md):后端提示词收口到 `server-node/src/prompts/` 的目录方案、兼容策略与后续新增规则。
|
||||
- [CUSTOM_WORLD_DRAFT_GENERATION_FAILURE_ANALYSIS_AND_FIX_2026-04-20.md](./CUSTOM_WORLD_DRAFT_GENERATION_FAILURE_ANALYSIS_AND_FIX_2026-04-20.md):世界草稿生成失败后等待页误显示为“卡在编译草稿卡”的根因拆解、主链与增强链路边界,以及本次修复策略。
|
||||
|
||||
@@ -0,0 +1,745 @@
|
||||
# 基于 SpacetimeDB + Axum + 阿里云 OSS 的后端重写设计文档
|
||||
|
||||
日期:`2026-04-20`
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
这份文档不是继续扩写当前 `server-node/` 的实现细节,而是基于**当前仓库已经落地的后端能力**,为下一版 Rust 后端提供一份可以直接落地编码的重写设计。
|
||||
|
||||
目标很明确:
|
||||
|
||||
1. 保留当前项目已经具备的后端能力面,不做需求缩水。
|
||||
2. 把后端实现从 `Express + PostgreSQL + 本地 public/generated-* 文件` 重写为:
|
||||
- `Axum`:唯一 HTTP 边界层与流式接口层
|
||||
- `SpacetimeDB`:唯一运行时状态与实时订阅真相源
|
||||
- `阿里云 OSS`:唯一大文件与二进制资产存储
|
||||
3. 让前端在第一阶段尽量少改,优先兼容当前 `/api/*`、`/healthz`、SSE 与资源路径习惯。
|
||||
|
||||
## 2. 当前工程必须继承的能力基线
|
||||
|
||||
以 `docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md` 当前生成结果为准,现有 Node 后端的能力基线是:
|
||||
|
||||
- 对外挂载面:`6` 个
|
||||
- 已登记路由:`96` 条
|
||||
- 内部模块目录:`12` 个
|
||||
- 公开接口:`10` 条
|
||||
- JWT 接口:`69` 条
|
||||
- 环境开关接口:`17` 条
|
||||
- 流式接口:`6` 条
|
||||
|
||||
重写后的 Rust 后端,第一阶段必须至少完整覆盖这 6 个挂载面:
|
||||
|
||||
1. `health`
|
||||
2. `auth`
|
||||
3. `editor`
|
||||
4. `assets`
|
||||
5. `runtime-main`
|
||||
6. `runtime-story-action`
|
||||
|
||||
当前后端内部模块也不能“凭感觉重设计”,而要按现有职责做映射:
|
||||
|
||||
1. `ai`
|
||||
2. `assets`
|
||||
3. `combat`
|
||||
4. `custom-world`
|
||||
5. `editor`
|
||||
6. `inventory`
|
||||
7. `npc`
|
||||
8. `progression`
|
||||
9. `quest`
|
||||
10. `runtime`
|
||||
11. `runtime-item`
|
||||
12. `story`
|
||||
|
||||
## 3. 技术选型后的硬边界
|
||||
|
||||
### 3.1 SpacetimeDB 的平台约束与本项目边界
|
||||
|
||||
根据 SpacetimeDB 官方 Rust crate 文档,`reducer` 不能直接使用 `std::net` 或 `std::fs` 进行外部 IO;而 2.0 官方文档又提供了 `procedure + ctx.http` 的受控 HTTP 能力。
|
||||
也就是说,**平台层面并不是完全不能做外部调用**,但对于本项目这次重写,我们仍然主动把下面这些副作用统一放到 Axum,而不是塞进 SpacetimeDB:
|
||||
|
||||
1. 阿里云 OSS 上传、下载、签名
|
||||
2. DashScope / Ark / 其他 LLM 请求
|
||||
3. 微信 OAuth
|
||||
4. 短信验证码
|
||||
5. 本地文件系统读写
|
||||
|
||||
这样设计不是因为“SpacetimeDB 绝对做不到”,而是因为这类能力都要求:
|
||||
|
||||
1. 更强的重试、超时、日志和熔断能力
|
||||
2. 更自由的 SDK / multipart / 签名上传实现
|
||||
3. 与 HTTP 头、cookie、回调、对象存储策略深度耦合
|
||||
4. 与游戏状态 schema 解耦,避免把第三方供应商能力直接绑进数据库模块发布周期
|
||||
|
||||
结论:
|
||||
|
||||
- `SpacetimeDB` 负责状态、规则、订阅、命令执行与读模型。
|
||||
- `Axum` 负责所有外部副作用与 HTTP 协议。
|
||||
|
||||
### 3.2 Axum 的边界
|
||||
|
||||
根据 Axum 官方文档,`Router<S>` 通过 `.with_state(...)` 注入共享状态后才成为可真正 `serve()` 的路由树;因此 Axum 适合作为:
|
||||
|
||||
1. 统一 HTTP 入口
|
||||
2. Bearer / Cookie 鉴权入口
|
||||
3. SSE 输出入口
|
||||
4. OSS 上传凭证签发入口
|
||||
5. 与 SpacetimeDB 的应用层编排入口
|
||||
|
||||
结论:
|
||||
|
||||
- Axum 是唯一 BFF / API Gateway。
|
||||
- 前端第一阶段仍然只认识 Axum,不直接依赖 SpacetimeDB 原生接口。
|
||||
|
||||
### 3.3 OSS 的边界
|
||||
|
||||
根据阿里云 OSS 官方文档:
|
||||
|
||||
1. `PostObject` 适合浏览器表单直传,服务端可以下发 `policy`、`signature` 与上传条件。
|
||||
2. `STS` 临时访问凭证适合把上传权限以有限时、有限范围的方式下发给客户端。
|
||||
3. `PutObject` / `PostObject` 都支持 `x-oss-meta-*` 元数据与标签。
|
||||
|
||||
结论:
|
||||
|
||||
- 所有图片、动画、精灵表、场景图、封面图、视频参考素材都存 OSS。
|
||||
- SpacetimeDB 只存对象键、版本、尺寸、状态、逻辑元数据,不存二进制。
|
||||
- Axum 负责下发直传凭证、校验上传结果、补写元数据。
|
||||
|
||||
## 4. 目标总体架构
|
||||
|
||||
```text
|
||||
Web / Mobile Frontend
|
||||
├─ 继续访问 /api/*、/healthz、/generated-*
|
||||
└─ 第一阶段不直接依赖 SpacetimeDB 原生协议
|
||||
|
||||
Axum API Server
|
||||
├─ auth:登录、refresh cookie、JWT/OIDC 签发
|
||||
├─ runtime facade:兼容当前 REST / SSE contract
|
||||
├─ asset gateway:OSS 直传签名、对象确认、媒体任务编排
|
||||
├─ ai gateway:DashScope / Ark / 其他模型调用
|
||||
├─ editor gateway:开发态文件读写或对象化编辑能力
|
||||
├─ background workers:异步作业执行与回写
|
||||
└─ SpacetimeDB client:调用 reducer、查询 view / public table、订阅任务状态
|
||||
|
||||
SpacetimeDB Module
|
||||
├─ auth tables:用户、身份、session、风控、审计
|
||||
├─ runtime tables:存档、设置、浏览历史、个人面板
|
||||
├─ gameplay tables:story / npc / quest / inventory / combat / progression
|
||||
├─ custom-world tables:问答会话、agent 会话、草稿卡、操作记录
|
||||
├─ asset metadata tables:生成任务、对象清单、引用关系
|
||||
├─ reducers:唯一状态写入口
|
||||
└─ views / public tables:唯一读模型与订阅面
|
||||
|
||||
Aliyun OSS
|
||||
├─ generated-character-drafts
|
||||
├─ generated-characters
|
||||
├─ generated-custom-world-scenes
|
||||
├─ generated-qwen-sprites
|
||||
├─ generated-cover-images
|
||||
└─ editor-exports / temp-uploads / workflow-cache
|
||||
```
|
||||
|
||||
## 5. 重写后的核心原则
|
||||
|
||||
### 5.1 先兼容当前 API 面,再逐步让前端吃到实时能力
|
||||
|
||||
第一阶段不要强推前端直接改成 SpacetimeDB 客户端模式,而是:
|
||||
|
||||
1. Axum 保持当前 `/api/*` 路由空间。
|
||||
2. Axum 保持当前 `x-request-id / x-api-version / x-route-version` 头和响应 envelope。
|
||||
3. Axum 保持当前 story / custom-world-agent 的 SSE 体验。
|
||||
4. SpacetimeDB 先做后端内部真相源。
|
||||
|
||||
第二阶段再按模块把只读页改成直接订阅 SpacetimeDB。
|
||||
|
||||
### 5.2 命令与读模型分离
|
||||
|
||||
SpacetimeDB 官方文档明确说明:
|
||||
|
||||
1. reducer 是唯一可修改表的入口。
|
||||
2. reducer 不直接返回业务数据给客户端。
|
||||
3. view 可被查询与订阅,并会随底层表变化自动更新。
|
||||
|
||||
因此本项目必须采用:
|
||||
|
||||
- `Reducer = 命令入口`
|
||||
- `View / Public Table = 读模型入口`
|
||||
- `Axum = HTTP 兼容层与聚合层`
|
||||
|
||||
### 5.3 大对象不进数据库
|
||||
|
||||
当前 Node 后端把生成结果落在 `public/generated-*`。重写后统一改成:
|
||||
|
||||
- `OSS` 存二进制
|
||||
- `SpacetimeDB` 存引用和状态
|
||||
- `Axum` 输出 URL、签名 URL 或 CDN URL
|
||||
|
||||
### 5.4 Schema 必须按 SpacetimeDB 的迁移约束设计
|
||||
|
||||
SpacetimeDB 官方文档对自动迁移的限制很强:
|
||||
|
||||
1. 删表、改列类型、改列名、调整列顺序都不是安全日常操作。
|
||||
2. 新列只能追加到表末尾,且要提供默认值。
|
||||
3. reducer 改名或删除会直接影响客户端调用。
|
||||
|
||||
因此本项目的数据模型必须尽量满足:
|
||||
|
||||
1. 主表稳定、字段追加式演进
|
||||
2. 高频变化数据优先事件表化
|
||||
3. 聚合结果优先投影表 / view,而不是频繁重塑旧表结构
|
||||
|
||||
## 6. 推荐工程结构
|
||||
|
||||
建议新增 Rust 工作区,例如 `server-rs/`:
|
||||
|
||||
```text
|
||||
server-rs/
|
||||
├─ Cargo.toml
|
||||
├─ crates/
|
||||
│ ├─ api-server/ # Axum 入口
|
||||
│ ├─ spacetime-module/ # SpacetimeDB Rust 模块(编译为 wasm)
|
||||
│ ├─ application/ # 用例编排层
|
||||
│ ├─ domain/ # 纯领域类型、枚举、ID、值对象
|
||||
│ ├─ contracts/ # HTTP DTO / SSE event / 内部 command DTO
|
||||
│ ├─ auth-service/ # JWT、cookie、provider adapter
|
||||
│ ├─ oss-service/ # OSS 直传、签名、对象管理
|
||||
│ ├─ llm-service/ # DashScope / Ark / 其他模型适配
|
||||
│ ├─ spacetime-client/ # 生成 bindings 后的 DB client adapter
|
||||
│ └─ tests/ # 集成测试、contract 测试、smoke
|
||||
└─ scripts/
|
||||
├─ dev.sh / dev.ps1
|
||||
├─ spacetime-publish.sh
|
||||
└─ smoke.sh
|
||||
```
|
||||
|
||||
目录职责约束:
|
||||
|
||||
1. `spacetime-module/` 只能写纯状态逻辑,不写网络 / 文件系统。
|
||||
2. `api-server/` 只做协议装配、鉴权、中间件、handler。
|
||||
3. `application/` 编排 Axum、OSS、LLM、SpacetimeDB 之间的流程。
|
||||
4. `domain/` 只放纯数据结构和规则,不碰框架。
|
||||
5. `contracts/` 负责与当前前端兼容的 JSON / SSE 协议。
|
||||
|
||||
## 7. 目标模块映射
|
||||
|
||||
| 当前模块 | 重写后主归属 | 次归属 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `auth` | Axum `auth-service` + SpacetimeDB private tables | 无 | 登录入口、refresh cookie、JWT/OIDC、审计与风控拆为“Axum 处理副作用 + SpacetimeDB 落状态”。 |
|
||||
| `runtime` | SpacetimeDB module | Axum facade | 存档、设置、浏览历史、profile dashboard 统一进入 SpacetimeDB。 |
|
||||
| `story` | SpacetimeDB module | Axum SSE facade | story action、状态推进、可选项构造、恢复态读取以后端 reducer/view 为准。 |
|
||||
| `combat` | SpacetimeDB module | 无 | 纯规则计算,天然适合 reducer。 |
|
||||
| `inventory` | SpacetimeDB module | 无 | 背包、赠礼、交易、物品副作用全部 reducer 化。 |
|
||||
| `npc` | SpacetimeDB module | Axum for LLM dialogue | 关系、招募、状态机在 SpacetimeDB;LLM 台词生成留在 Axum。 |
|
||||
| `progression` | SpacetimeDB module | 无 | 关卡、等级、敌对 scaling、章节推进都做领域表和 reducer。 |
|
||||
| `quest` | SpacetimeDB module | Axum for AI quest drafting | 任务主状态在 SpacetimeDB;生成型内容由 Axum 生产后回写。 |
|
||||
| `runtime-item` | SpacetimeDB module | Axum for AI intent | 物品奖励和解析归 reducer;AI 意图生成由 Axum 负责。 |
|
||||
| `custom-world` | SpacetimeDB module + Axum orchestration | OSS | 会话、草稿、agent 状态放 SpacetimeDB;世界编译、资产生成、发布编排在 Axum。 |
|
||||
| `ai` | Axum `llm-service` | SpacetimeDB task tables | 外部模型调用全部放 Axum。 |
|
||||
| `assets` | Axum `oss-service` | SpacetimeDB asset metadata | 二进制进 OSS,元数据进 SpacetimeDB。 |
|
||||
| `editor` | Axum `editor` namespace | OSS / local fs | 开发态保留本地文件适配,线上默认走对象化资源。 |
|
||||
|
||||
## 8. 数据建模方案
|
||||
|
||||
## 8.1 表的分层
|
||||
|
||||
建议在 SpacetimeDB 中至少拆成 5 组表:
|
||||
|
||||
### A. 身份与会话表
|
||||
|
||||
- `user_account`
|
||||
- `auth_identity`
|
||||
- `refresh_session`
|
||||
- `auth_audit_log`
|
||||
- `auth_risk_block`
|
||||
- `sms_auth_event`
|
||||
- `wechat_auth_state`
|
||||
|
||||
说明:
|
||||
|
||||
1. 这些表默认都应为 private。
|
||||
2. 密码哈希、短信验证码校验、微信 code 换 token 的动作在 Axum 完成。
|
||||
3. Axum 再调用 reducer 写入最终结果。
|
||||
|
||||
### B. 运行时主状态表
|
||||
|
||||
- `runtime_snapshot`
|
||||
- `runtime_setting`
|
||||
- `profile_dashboard_state`
|
||||
- `profile_wallet_ledger`
|
||||
- `profile_played_world`
|
||||
- `profile_save_archive`
|
||||
- `user_browse_history`
|
||||
|
||||
说明:
|
||||
|
||||
1. `runtime_snapshot` 继续作为“恢复游戏”的主聚合表。
|
||||
2. `profile_*` 作为只读页投影表,避免每次从大快照现算。
|
||||
3. `browse_history`、`save_archive` 单独建表,不要藏进 snapshot blob。
|
||||
|
||||
### C. Gameplay 领域表
|
||||
|
||||
- `story_session`
|
||||
- `story_event`
|
||||
- `npc_state`
|
||||
- `quest_record`
|
||||
- `inventory_slot`
|
||||
- `treasure_record`
|
||||
- `battle_state`
|
||||
- `player_progression`
|
||||
- `chapter_progression`
|
||||
|
||||
说明:
|
||||
|
||||
1. `story_action` 不直接改大 JSON,而是 reducer 驱动多个领域表。
|
||||
2. 是否继续保留兼容快照,可由投影 reducer 汇总生成。
|
||||
3. 旧前端仍需要 `gameState + currentStory` 时,由 Axum 聚合成兼容 DTO。
|
||||
|
||||
### D. Custom World 表
|
||||
|
||||
- `custom_world_profile`
|
||||
- `custom_world_session`
|
||||
- `custom_world_agent_session`
|
||||
- `custom_world_agent_message`
|
||||
- `custom_world_agent_operation`
|
||||
- `custom_world_draft_card`
|
||||
- `custom_world_asset_link`
|
||||
- `custom_world_gallery_entry`
|
||||
|
||||
说明:
|
||||
|
||||
1. 传统问答流与 Agent 流不再混一个 payload。
|
||||
2. 卡片、操作、消息必须拆表,不能再都塞进一个大 JSON 会话体。
|
||||
3. 公开画廊作为独立投影,避免从 profile 运行时拼装。
|
||||
|
||||
### E. 资产与对象元数据表
|
||||
|
||||
- `asset_job`
|
||||
- `asset_object`
|
||||
- `asset_manifest`
|
||||
- `character_visual_asset`
|
||||
- `character_animation_asset`
|
||||
- `scene_image_asset`
|
||||
- `sprite_sheet_asset`
|
||||
|
||||
说明:
|
||||
|
||||
1. 任务状态在 SpacetimeDB。
|
||||
2. 二进制对象在 OSS。
|
||||
3. 所有业务实体只引用 `asset_object_key / cdn_url / version / hash`。
|
||||
|
||||
## 8.2 public / private 原则
|
||||
|
||||
依据 SpacetimeDB 官方访问权限文档:
|
||||
|
||||
1. private table 默认只给 reducer / view / owner 看。
|
||||
2. public table 才适合直接查询和订阅。
|
||||
|
||||
本项目建议:
|
||||
|
||||
- `auth_*`、`refresh_session`、风控、验证码全部 private
|
||||
- `runtime_snapshot` private
|
||||
- `story_session` private
|
||||
- `custom_world_agent_*` 绝大多数 private
|
||||
- `gallery`、公共角色卡、公共作品索引可 public
|
||||
- 如需“用户自己的读模型”,优先用 `ViewContext` view 暴露
|
||||
|
||||
## 8.3 view 设计原则
|
||||
|
||||
依据 SpacetimeDB 官方 view 文档:
|
||||
|
||||
1. view 可以被查询和订阅,底层表变化时会自动更新。
|
||||
2. `AnonymousViewContext` 可被所有用户共享物化结果,性能明显优于按用户单独计算。
|
||||
3. view 不适合全表 `.iter()` 扫描,应该通过索引查找或返回可分析的 query。
|
||||
|
||||
所以本项目的 view 设计规则必须是:
|
||||
|
||||
1. 画廊、排行榜、商店、公共世界列表,优先匿名 view。
|
||||
2. “我的背包 / 我的档案 / 我的当前会话”这类按用户隔离的读模型,才用带身份上下文的 view。
|
||||
3. 首页、资料库、历史记录、存档列表都必须先有索引,再写 view。
|
||||
|
||||
## 9. 鉴权设计
|
||||
|
||||
## 9.1 总体方案
|
||||
|
||||
建议保留当前“密码 / 手机验证码 / 微信”三类登录能力,但把实现改成:
|
||||
|
||||
1. Axum 负责登录副作用:
|
||||
- 密码校验
|
||||
- 短信发送与校验
|
||||
- 微信 OAuth code 交换
|
||||
- refresh cookie 签发与轮换
|
||||
2. Axum 负责签发**OIDC 兼容 JWT**
|
||||
3. 同一张 JWT 同时用于:
|
||||
- Axum 自身 Bearer 鉴权
|
||||
- SpacetimeDB reducer / view 的身份透传
|
||||
|
||||
这是可行的,因为 SpacetimeDB 官方文档说明其可以从 OIDC 兼容 JWT 中提取身份声明,并在模块上下文中使用这些 claims。
|
||||
|
||||
## 9.2 Token 设计
|
||||
|
||||
建议:
|
||||
|
||||
- `iss`:固定为 Axum 网关身份发行者,例如 `https://api.genarrative.example/auth`
|
||||
- `sub`:稳定用户 ID
|
||||
- 扩展 claims:
|
||||
- `sid`:session id
|
||||
- `provider`:password / phone / wechat
|
||||
- `roles`
|
||||
- `phone_verified`
|
||||
- `display_name`
|
||||
|
||||
## 9.3 Refresh Session
|
||||
|
||||
建议保留当前模式:
|
||||
|
||||
1. 浏览器短期 Bearer access token
|
||||
2. HttpOnly refresh cookie
|
||||
3. refresh session 服务端可吊销
|
||||
|
||||
但新的会话 ledger 写入 SpacetimeDB private 表即可。
|
||||
|
||||
## 10. Axum 侧设计
|
||||
|
||||
## 10.1 进程职责
|
||||
|
||||
Axum 进程建议拆成以下子系统:
|
||||
|
||||
1. `http::middleware`
|
||||
- request id
|
||||
- tracing
|
||||
- envelope / 错误标准化
|
||||
- auth extractor
|
||||
2. `http::routes`
|
||||
- `/healthz`
|
||||
- `/api/auth/*`
|
||||
- `/api/runtime/*`
|
||||
- `/api/runtime/story/*`
|
||||
- `/api/assets/*`
|
||||
- `/api/editor/*`
|
||||
3. `application::services`
|
||||
- story facade
|
||||
- runtime snapshot facade
|
||||
- custom world facade
|
||||
- asset facade
|
||||
4. `infra`
|
||||
- SpacetimeDB client
|
||||
- OSS adapter
|
||||
- DashScope / Ark adapter
|
||||
- SMS / WeChat adapter
|
||||
|
||||
## 10.2 流式接口
|
||||
|
||||
当前项目已经有 6 条流式接口,重写时不建议一上来全部改成 WebSocket。
|
||||
|
||||
第一阶段建议:
|
||||
|
||||
1. 继续使用 Axum SSE 输出流式文本与阶段事件。
|
||||
2. Axum 在处理流式过程中,持续把阶段状态写回 SpacetimeDB。
|
||||
3. 前端仍按当前 SSE 协议消费。
|
||||
|
||||
适合继续保留为 Axum SSE 的场景:
|
||||
|
||||
1. `story/initial`
|
||||
2. `story/continue`
|
||||
3. `chat/character/*`
|
||||
4. `chat/npc/*`
|
||||
5. `custom-world/generate/stream`
|
||||
6. `custom-world/agent/messages/stream`
|
||||
|
||||
## 10.3 兼容 contract
|
||||
|
||||
Axum 第一阶段需要兼容当前项目的这些约定:
|
||||
|
||||
1. 路径空间不变
|
||||
2. `x-request-id`
|
||||
3. `x-api-version`
|
||||
4. `x-route-version`
|
||||
5. `x-response-time-ms`
|
||||
6. 可选 envelope:`x-genarrative-response-envelope`
|
||||
|
||||
这样前端可以在不大改 `src/services/*` 的前提下切换后端实现。
|
||||
|
||||
## 11. OSS 设计
|
||||
|
||||
## 11.1 对象键规划
|
||||
|
||||
建议统一对象键前缀,保持与当前前端路径习惯接近:
|
||||
|
||||
```text
|
||||
generated-character-drafts/{character_id}/{job_id}/{file}
|
||||
generated-characters/{character_id}/visual/{asset_id}/{file}
|
||||
generated-characters/{character_id}/animation/{asset_id}/{action}/{file}
|
||||
generated-custom-world-scenes/{profile_id}/{landmark_id}/{asset_id}/{file}
|
||||
generated-qwen-sprites/{role_id}/{sheet_id}/{file}
|
||||
generated-cover-images/{profile_id}/{asset_id}/{file}
|
||||
editor-cache/{resource_type}/{resource_id}/{file}
|
||||
workflow-cache/{workflow_type}/{workflow_id}.json
|
||||
```
|
||||
|
||||
## 11.2 上传模式
|
||||
|
||||
建议:
|
||||
|
||||
1. 前端直传图片、封面、小文件:`PostObject`
|
||||
2. 大文件或需要细粒度控制时:`STS + PutObject / Multipart`
|
||||
3. 生成型资产:Axum worker 直接上传 OSS
|
||||
|
||||
其中:
|
||||
|
||||
- `PostObject` 用于浏览器上传时,Axum 负责生成 policy 与 signature。
|
||||
- policy 中必须明确:
|
||||
- key 前缀
|
||||
- content-type 白名单
|
||||
- content-length-range
|
||||
- success_action_status
|
||||
|
||||
## 11.3 元数据与标签
|
||||
|
||||
建议所有业务对象写入统一元数据:
|
||||
|
||||
- `x-oss-meta-owner-user-id`
|
||||
- `x-oss-meta-profile-id`
|
||||
- `x-oss-meta-entity-id`
|
||||
- `x-oss-meta-asset-kind`
|
||||
- `x-oss-meta-source-job-id`
|
||||
- `x-oss-meta-content-hash`
|
||||
- `x-oss-meta-origin`
|
||||
|
||||
注意:
|
||||
|
||||
1. OSS 官方文档要求自定义元数据使用 `x-oss-meta-*` 前缀。
|
||||
2. 所有元数据总大小不能超过 `8 KB`。
|
||||
|
||||
## 11.4 URL 策略
|
||||
|
||||
建议:
|
||||
|
||||
1. 业务表里统一存 `object_key`
|
||||
2. 对外输出 `cdn_url`
|
||||
3. 私有对象额外输出短期签名 URL
|
||||
|
||||
为了兼容当前前端相对路径使用习惯,第一阶段可以让 Axum 或 CDN 兼容以下历史前缀:
|
||||
|
||||
1. `/generated-character-drafts/*`
|
||||
2. `/generated-characters/*`
|
||||
3. `/generated-custom-world-scenes/*`
|
||||
4. `/generated-qwen-sprites/*`
|
||||
|
||||
## 12. 关键业务流设计
|
||||
|
||||
## 12.1 Story Action
|
||||
|
||||
目标:
|
||||
|
||||
1. `storyActionService` 当前承担的跨模块结算必须迁到 SpacetimeDB reducer。
|
||||
2. Axum 只做 request parse、auth、调用 reducer、读取 view、拼回当前前端响应。
|
||||
|
||||
推荐流程:
|
||||
|
||||
1. 前端 `POST /api/runtime/story/actions/resolve`
|
||||
2. Axum 校验请求并附带 JWT
|
||||
3. Axum 调用 SpacetimeDB `resolve_story_action` reducer
|
||||
4. reducer 内部联动:
|
||||
- `story`
|
||||
- `combat`
|
||||
- `inventory`
|
||||
- `npc`
|
||||
- `quest`
|
||||
- `runtime-item`
|
||||
- `progression`
|
||||
5. reducer 写回领域表
|
||||
6. Axum 再读取 `current_story_view`、`runtime_snapshot_view`
|
||||
7. Axum 返回兼容当前前端的 `RuntimeStoryActionResponse`
|
||||
|
||||
## 12.2 存档与恢复
|
||||
|
||||
目标:
|
||||
|
||||
1. 仍保留当前“完整恢复游戏”的能力。
|
||||
2. 但底层不再由 PostgreSQL 单大 JSON 承担全部职责。
|
||||
|
||||
建议:
|
||||
|
||||
1. `runtime_snapshot` 继续保留兼容聚合快照,满足现有恢复链路。
|
||||
2. gameplay 真相由领域表维护。
|
||||
3. 每次重要 reducer 提交后,异步或同步刷新 snapshot projection。
|
||||
|
||||
这样可以兼顾:
|
||||
|
||||
1. 旧前端兼容
|
||||
2. 新后端可审计
|
||||
3. SpacetimeDB 实时订阅能力
|
||||
|
||||
## 12.3 Custom World Agent
|
||||
|
||||
当前 Node 后端中,`customWorldAgentOrchestrator + SessionStore + Operation` 已经是一条清晰主链。
|
||||
重写后建议进一步正规化:
|
||||
|
||||
1. SpacetimeDB:
|
||||
- 存会话
|
||||
- 存消息
|
||||
- 存卡片
|
||||
- 存操作状态
|
||||
- 存草稿 profile
|
||||
2. Axum:
|
||||
- 调 LLM
|
||||
- 调图片生成
|
||||
- 调 OSS
|
||||
- 发 SSE
|
||||
- 把阶段结果回写 reducer
|
||||
|
||||
不再允许“一整个 agent 会话对象 JSON 一把写回”作为长期形态。
|
||||
|
||||
## 12.4 资产生成
|
||||
|
||||
资产链路拆成四步:
|
||||
|
||||
1. Axum 创建 `asset_job`
|
||||
2. Axum worker 调外部模型
|
||||
3. 产物上传 OSS,写 `asset_object`
|
||||
4. Axum 调 reducer 将对象绑定到:
|
||||
- 角色
|
||||
- 地点
|
||||
- 世界草稿
|
||||
- Qwen sprite sheet
|
||||
|
||||
所有“对象是否已经被业务引用”的事实,以 SpacetimeDB 绑定表为准,而不是以 OSS 是否存在某个 key 为准。
|
||||
|
||||
## 13. 迁移阶段建议
|
||||
|
||||
## Phase 0:冻结能力清单
|
||||
|
||||
交付:
|
||||
|
||||
1. 固定当前 `96` 条接口为重写验收基线
|
||||
2. 固定当前 `12` 个模块为迁移映射基线
|
||||
3. 固定当前前端 contract 与 SSE 协议
|
||||
|
||||
## Phase 1:先搭 Axum 外壳与鉴权
|
||||
|
||||
交付:
|
||||
|
||||
1. `/healthz`
|
||||
2. `/api/auth/*`
|
||||
3. response envelope
|
||||
4. request id / tracing
|
||||
5. Axum -> SpacetimeDB 基础 client
|
||||
6. OIDC-compatible JWT 签发
|
||||
|
||||
## Phase 2:迁移 runtime snapshot / settings / profile
|
||||
|
||||
交付:
|
||||
|
||||
1. 存档
|
||||
2. 设置
|
||||
3. 浏览历史
|
||||
4. profile dashboard
|
||||
5. save archives
|
||||
|
||||
这是最容易先跑通的闭环。
|
||||
|
||||
## Phase 3:迁移 story action 主循环
|
||||
|
||||
交付:
|
||||
|
||||
1. story reducer
|
||||
2. combat / inventory / npc / quest / runtime-item / progression 联动
|
||||
3. `/api/runtime/story/*`
|
||||
|
||||
这一阶段完成后,运行时真相就真正从 Node 版切出去了。
|
||||
|
||||
## Phase 4:迁移 custom world 与 agent
|
||||
|
||||
交付:
|
||||
|
||||
1. legacy custom world session
|
||||
2. custom world library / gallery
|
||||
3. custom world agent 会话、卡片、操作
|
||||
4. scene npc / entity generation
|
||||
|
||||
## Phase 5:迁移 assets / editor
|
||||
|
||||
交付:
|
||||
|
||||
1. OSS 直传
|
||||
2. 生成任务
|
||||
3. 对象元数据
|
||||
4. 编辑器读写开发态适配
|
||||
5. 旧 `/generated-*` 路径兼容
|
||||
|
||||
## 14. 验收标准
|
||||
|
||||
重写完成至少要满足:
|
||||
|
||||
1. 当前 `96` 条已登记路由全部有对应实现或明确兼容替代。
|
||||
2. 当前 6 个挂载面全部保留。
|
||||
3. 浏览器无需直接知道 SpacetimeDB 原生接口即可跑通主流程。
|
||||
4. story action、存档、custom world、agent、assets 都以后端为唯一真相。
|
||||
5. 所有生成图片、动画、精灵表都不再依赖本地 `public/generated-*` 持久化。
|
||||
6. Axum 和 SpacetimeDB 的职责边界稳定,不把外部网络 IO 偷放进 module。
|
||||
|
||||
## 15. 关键风险
|
||||
|
||||
### 15.1 不能把 SpacetimeDB 当 PostgreSQL 替身直接套
|
||||
|
||||
SpacetimeDB 更像“带强实时订阅能力的状态机数据库”,不是传统 SQL 仓储替身。
|
||||
如果仍沿用“单大 JSON + 巨型路由文件 + 过程式 handler”思路,重写后仍然会很快回到旧热点。
|
||||
|
||||
### 15.2 schema 演进成本高于 PostgreSQL
|
||||
|
||||
SpacetimeDB 自动迁移更适合“增量追加”,不适合高频改列结构。
|
||||
所以必须提前定好:
|
||||
|
||||
1. 稳定主键
|
||||
2. 稳定 reducer 命名
|
||||
3. 事件表 / 投影表边界
|
||||
|
||||
### 15.3 view 滥用会造成性能问题
|
||||
|
||||
如果把资料库、画廊、排行榜写成按用户逐个计算、又缺索引的 view,性能会很差。
|
||||
因此 read model 必须先建索引,再决定用匿名 view 还是按用户 view。
|
||||
|
||||
### 15.4 资产路径兼容是迁移成败关键
|
||||
|
||||
当前前端大量相对路径、角色图、场景图、动画图都是围绕 `/generated-*` 组织的。
|
||||
如果不先做路径兼容层,存档恢复和世界资料库会大面积失效。
|
||||
|
||||
## 16. 内部实现依据
|
||||
|
||||
这份设计稿对当前工程的判断,主要依据以下仓库现状:
|
||||
|
||||
1. `server-node/src/server.ts`
|
||||
2. `server-node/src/app.ts`
|
||||
3. `server-node/src/routes/authRoutes.ts`
|
||||
4. `server-node/src/routes/runtimeRoutes.ts`
|
||||
5. `server-node/src/modules/story/storyActionRoutes.ts`
|
||||
6. `server-node/src/repositories/runtimeRepository.ts`
|
||||
7. `server-node/src/services/customWorldAgentOrchestrator.ts`
|
||||
8. `docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md`
|
||||
9. `docs/technical/NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md`
|
||||
|
||||
## 17. 外部技术依据
|
||||
|
||||
以下外部依据用于确定本次新架构的技术边界,均来自官方文档或官方 crate 文档:
|
||||
|
||||
1. [SpacetimeDB Tables](https://spacetimedb.com/docs/tables/)
|
||||
2. [SpacetimeDB Table Access Permissions](https://spacetimedb.com/docs/tables/access-permissions/)
|
||||
3. [SpacetimeDB Reducers Overview](https://spacetimedb.com/docs/functions/reducers/)
|
||||
4. [SpacetimeDB Views](https://spacetimedb.com/docs/functions/views/)
|
||||
5. [SpacetimeDB Authorization](https://spacetimedb.com/docs/http/authorization/)
|
||||
6. [SpacetimeDB Authentication](https://spacetimedb.com/docs/core-concepts/authentication/)
|
||||
7. [SpacetimeDB Using Auth Claims](https://spacetimedb.com/docs/core-concepts/authentication/usage/)
|
||||
8. [SpacetimeDB Rust crate docs](https://docs.rs/spacetimedb/latest/spacetimedb/)
|
||||
9. [SpacetimeDB Rust Client SDK](https://spacetimedb.com/docs/sdks/rust/)
|
||||
10. [Axum crate docs](https://docs.rs/axum/latest/axum/)
|
||||
11. [Axum SSE](https://docs.rs/axum/latest/axum/response/sse/)
|
||||
12. [阿里云 OSS 服务端签名表单上传](https://help.aliyun.com/zh/oss/user-guide/form-upload)
|
||||
13. [阿里云 OSS STS 临时授权访问](https://help.aliyun.com/zh/oss/developer-reference/authorized-access-1)
|
||||
14. [阿里云 OSS PutObject](https://help.aliyun.com/zh/oss/developer-reference/putobject)
|
||||
15. [阿里云 OSS PostObject](https://help.aliyun.com/zh/oss/developer-reference/postobject)
|
||||
|
||||
## 18. 一句话结论
|
||||
|
||||
这次重写最正确的落点不是“把 Express 改成 Axum”,而是:
|
||||
|
||||
**让 Axum 成为唯一外部副作用和 HTTP 边界,让 SpacetimeDB 成为唯一状态机真相源,让 OSS 成为唯一资产对象仓,从而在不丢当前 96 条能力面的前提下,把项目升级成真正可持续扩展的 Rust 后端。**
|
||||
Reference in New Issue
Block a user