M4 runtime story Rust migration wrap-up

This commit is contained in:
2026-04-22 20:10:46 +08:00
parent 35958d5942
commit fa373f0575
31 changed files with 3257 additions and 1556 deletions

View File

@@ -297,3 +297,125 @@ server-rs/crates/api-server/src/
这组 resolver 虽然仍是 action orchestration但已经不依赖 HTTP / `AppState`,只依赖快照 `Value`、当前故事 `currentStory`、共享 DTO 与内部 helper因此适合先作为 `api-server` 内部模块沉淀。
迁移后 [compat.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat.rs) 对这些动作只保留 functionId 分发、快照桥接与少量共享 glue code不再承载 battle / equipment / forge / NPC / quest 的具体结算细节。
## 11. 独立 crate 抽取边界
完成第二阶段后,已经可以进入第三阶段,但独立 crate 仍按最小安全边界推进:
1. 新 crate 命名为 `module-runtime-story-compat`
2. `module-runtime-story-compat` 只承接“无 HTTP / 无 `AppState`”的 compat 核心:
- runtime story action 分发与确定性结算
- battle / equipment / forge / NPC / quest action resolver
- `Value` 快照态读写 helper
- `RuntimeStoryActionResponse` 的 view model / presentation 编译
3. `api-server` 继续保留:
- Axum route handler
- `RequestContext / AuthenticatedAccessToken`
- `runtime_snapshot` 持久化与读取
- `clientVersion` 校验到 HTTP error 的映射
- `platform-llm` 动作后文本增强
4. 首批迁移不把 AI 文本增强放进新 crate因为它依赖 `AppState``platform-llm`
5. 首批迁移不把 test route boundary 放进新 crateroute boundary 仍属于 `api-server`
这一步完成后,`api-server``runtime_story/compat.rs` 应该只负责:
1. 从 HTTP 请求恢复 / 持久化 snapshot
2. 调用 `module-runtime-story-compat` 产出确定性动作结果或状态响应
3. 需要时调用本地 AI 增强
4. 将最终响应包回 `Json<Value>`
这就是从“`api-server` 内部模块”到“独立 crate”的首个可验证切片。
截至当前工作区,第三阶段首批独立 crate 已落地:
1. 已新增 [module-runtime-story-compat](D:/Genarrative/server-rs/crates/module-runtime-story-compat)。
2. 已接入 [server-rs/Cargo.toml](D:/Genarrative/server-rs/Cargo.toml) workspace。
3. [api-server/Cargo.toml](D:/Genarrative/server-rs/crates/api-server/Cargo.toml) 已新增对 `module-runtime-story-compat` 的依赖。
4. 首批迁入新 crate 的内容包括:
- `StoryResolution`
- `GeneratedStoryPayload`
- `CurrentEncounterNpcQuestContext`
- `PendingQuestOfferContext`
- `RuntimeStoryActionResponseParts`
- `CONTINUE_ADVENTURE_FUNCTION_ID`
- `MAX_TASK5_COMPANIONS`
- `simple_story_resolution`
- `resolve_action_text`
- `build_status_patch`
- `current_world_type`
5. 第三阶段继续推进后,当前已经从 `api-server` 抽到独立 crate 的纯逻辑还包括:
- `core.rs`JSON 快照读写、runtime stat、story history、progression、encounter 清理
- `game_state.rs`encounter / inventory / equipment 的基础 helper
- `forge.rs`:锻造配方、重铸成本、材料消耗、拆解产物、重铸产物、货币文本
- `forge_actions.rs``forge_craft / forge_dismantle / forge_reforge` 三条动作结算
- `npc_support.rs`:赠礼好感收益、交易价格、数量文案、满员换队招募 helper
- `battle.rs``battle_* / inventory_use` 的纯动作结算、patch 生成与胜负写回
6. 当前 [api-server 的 compat.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat.rs) 已经不再内嵌上述纯逻辑,只保留:
- Axum handler
- snapshot 读写
- `clientVersion` 校验
- functionId 分发
- HTTP error 映射
- 动作后 AI 文本增强
7. 当前 [api-server 的 forge.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat/forge.rs) 已收缩成极薄 bridge只为 NPC trade bootstrap 复用新 crate 暴露的运行时物品构造 helper锻造规则主体不再保留本地副本。
8. 当前 [api-server 的 battle.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat/battle.rs) 也已从“结算 + 展示”收缩成“展示编译 + 少量本地 helper”
- battle 动作结算主链已经迁入 `module-runtime-story-compat`
- `api-server` 本地仅继续保留 `build_battle_runtime_story_options(...)``restore_player_resource(...)` 这类仍被 presentation / NPC 辅助逻辑直接依赖的部分
- 这为下一步继续把 battle option compiler 收进独立 crate 做好了边界准备
这意味着第三阶段已经不只是“创建了新 crate”而是完成了第一批真正跨 crate 的 compat 纯逻辑迁移,并且保持 route boundary 与既有测试口径不变。
同日继续推进后battle 这块已经完成从“先迁结算主链”到“连展示编译一起迁”的下一步:
1. [module-runtime-story-compat 的 battle.rs](D:/Genarrative/server-rs/crates/module-runtime-story-compat/src/battle.rs) 当前已同时承接:
- `resolve_battle_action(...)`
- `restore_player_resource(...)`
- `build_battle_runtime_story_options(...)`
- 技能冷却读取、推荐物品挑选、战斗技能 option compiler 等 battle 展示辅助
2. [api-server 的 compat.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat.rs) 已直接从 `module-runtime-story-compat` 导入 battle 展示编译与资源恢复 helper。
3. [api-server 本地的 compat/battle.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat/battle.rs) 已删除,不再保留 battle 规则的本地副本。
4. 到这一步,`api-server` 在 runtime story compat 上对 battle 的职责已经只剩:
- functionId 分发
- route handler / snapshot bridge
- AI 文本增强后的最终响应拼装
这说明第三阶段已经不只是在“拆 crate”而是在真实压缩 `api-server` 的 compat 规则面。接下来更合理的推进方向将不再是 battle而是继续评估 `presentation` 中还能进一步抽到独立 crate 的纯 view model / option compiler 边界。
同日继续推进后,`presentation` 中最通用的一层 option DTO 编译也已经开始抽离:
1. 已新增 [options.rs](D:/Genarrative/server-rs/crates/module-runtime-story-compat/src/options.rs),统一承接:
- `build_static_runtime_story_option(...)`
- `build_runtime_story_option_with_payload(...)`
- `build_disabled_runtime_story_option(...)`
- `build_runtime_story_option_from_story_option(...)`
- `build_story_option_from_runtime_option(...)`
- `infer_option_scope(...)`
2. [module-runtime-story-compat 的 lib.rs](D:/Genarrative/server-rs/crates/module-runtime-story-compat/src/lib.rs) 已对外 re-export 这些 option helper`api-server` 直接复用。
3. [api-server 的 presentation.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat/presentation.rs) 已删除本地重复实现,只保留 NPC option 组合、view model 组装、quest currentStory 等尚未完全独立的部分。
这一步的意义不是单纯减少行数,而是先把 `RuntimeStoryOptionView` 的最小稳定编译面收敛到独立 crate。后续若继续外提 `view model``fallback option compiler`,将不需要再重复搬运这些 option 基础件。
同日继续推进后,`presentation` 中的纯 view-model builder 也已经抽到独立 crate
1. 已新增 [view_model.rs](D:/Genarrative/server-rs/crates/module-runtime-story-compat/src/view_model.rs),统一承接:
- `build_runtime_story_view_model(...)`
- `build_runtime_story_companions(...)`
- `build_runtime_story_encounter(...)`
- `resolve_current_encounter_npc_state(...)`
2. [api-server 的 presentation.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat/presentation.rs) 已删除本地 view-model 组装实现,继续只负责状态响应 orchestration、dialogue currentStory、fallback option compiler 与 quest 辅助。
3. [api-server 的 game_state.rs](D:/Genarrative/server-rs/crates/api-server/src/runtime_story/compat/game_state.rs) 当前也直接复用 crate 导出的 `resolve_current_encounter_npc_state(...)`,避免 NPC 状态查询 helper 在 `api-server` 和 crate 之间出现两套实现。
至此,`module-runtime-story-compat` 已经覆盖了 runtime story 兼容层的以下纯逻辑面:
1. JSON 快照读写与基础状态 helper
2. battle / forge / npc support 的纯规则结算
3. battle option compiler
4. runtime story option DTO 编译
5. runtime story view-model 编译
`api-server` 当前的剩余重点已经更集中在:
1. HTTP / snapshot bridge
2. functionId 分发
3. AI 文本增强
4. NPC / quest fallback option 与 currentStory 组合逻辑

View File

@@ -4,6 +4,7 @@
## 文档列表
- [RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md](./RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md):冻结 Rust 本地一键联调脚本与 Ubuntu 发布包构建脚本的执行口径,覆盖 `npm run dev:rust``npm run build:rust:ubuntu`、Vite release、Linux `api-server`、SpacetimeDB wasm、启动停止脚本和安全清库开关。
- [RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md](./RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md):记录当前 Rust `api-server` 已挂载的 96 条 Axum 路由,按 auth、assets、runtime、custom world、story、generated path 等挂载面归类,用于对照 Node 能力基线与切流 smoke 清单。
- [BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md](./BACKEND_REWRITE_CROSS_CUTTING_GOVERNANCE_2026-04-22.md):冻结后端重写收口阶段的横向治理规则,覆盖 TypeScript contract 到 Rust DTO 映射、SpacetimeDB schema 演进、大对象 / workflow cache 存储边界和文档维护门禁。
- [PLATFORM_LLM_TEXT_GATEWAY_DESIGN_2026-04-21.md](./PLATFORM_LLM_TEXT_GATEWAY_DESIGN_2026-04-21.md)`platform-llm` 文本模型网关首版设计,冻结 OpenAI 兼容 `/chat/completions`、SSE 增量解析、错误模型与重试边界。

View File

@@ -0,0 +1,145 @@
# Rust 本地联调与远端发布脚本方案
日期:`2026-04-22`
## 1. 目标
本方案补齐 `server-rs` 在 M7 切流前需要的两类工程脚本:
1. 本地一键联调脚本:同时启动本地 SpacetimeDB、Rust `api-server` 与 Web 前端,并通过现有 Vite 代理开关把运行时 API 指向 Rust。
2. Ubuntu 发布包构建脚本:在仓库根目录生成 `build/<当前时间>/` 发布目录,内含前端 release、Linux `api-server`、SpacetimeDB wasm、启动脚本与停止脚本。
脚本只做部署与联调编排,不改变 HTTP contract、SpacetimeDB schema 命名、对象存储键规划和前端默认 Node 开发入口。
## 2. 本地脚本
入口:
```powershell
npm run dev:rust
```
跨平台 Bash 入口:
```bash
npm run dev:rust:sh
```
Windows 下 `dev:rust:sh``deploy:rust:remote``build:rust:ubuntu` 会通过 `scripts/run-bash-script.mjs` 优先查找 Git Bash如安装路径不标准可用 `GENARRATIVE_BASH` 指定 `bash` 可执行文件。
默认端口:
1. Web 前端:`http://127.0.0.1:3000`
2. Rust `api-server``http://127.0.0.1:8082`
3. SpacetimeDB standalone`http://127.0.0.1:3101`
4. SpacetimeDB database`genarrative-dev`
默认流程:
1. 检查 `cargo``node``spacetime` CLI。
2. 启动 `spacetime --root-dir server-rs/.spacetimedb/local start --edition standalone --listen-addr 127.0.0.1:3101`
3. 等待 `spacetime server ping http://127.0.0.1:3101` 可用。
4. 执行 `spacetime publish genarrative-dev --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module --yes`
5. 注入 `GENARRATIVE_API_*``GENARRATIVE_SPACETIME_*` 后启动 `cargo run -p api-server`
6. 注入 `GENARRATIVE_BACKEND_STACK=rust``RUST_SERVER_TARGET``GENARRATIVE_RUNTIME_SERVER_TARGET` 后启动 Vite。
7. 任一子进程退出时,脚本回收其余子进程。
Vite 代理覆盖范围:
1. `/api/runtime/*` 会在 Rust 栈下代理到 Rust `api-server`,覆盖旧 runtime story 兼容接口。
2. `/api/story/*` 会在 Rust 栈下代理到 Rust `api-server`,覆盖新 story session、battle 查询与 NPC battle 切片接口。
3. 其他 `/api/auth``/api/assets``/api/custom-world``/api/llm` 等路径仍由同一个 `GENARRATIVE_RUNTIME_SERVER_TARGET` 控制,便于 M7 按服务能力逐项做对比 smoke。
安全边界:
1. 默认不执行 `--clear-database`
2. 只有显式传入 `-ClearDatabase``--clear-database` 才允许清库重发。
3. 如需要复用已经启动的 SpacetimeDB可传 `-SkipSpacetime` / `--skip-spacetime`
4. 如只想启动进程不发布模块,可传 `-SkipPublish` / `--skip-publish`
常用示例:
```powershell
.\scripts\dev-rust-stack.ps1 -ApiPort 8090 -SpacetimePort 3110 -Database genarrative-dev
.\scripts\dev-rust-stack.ps1 -SkipSpacetime -SkipPublish
.\scripts\dev-rust-stack.ps1 -ClearDatabase
```
```bash
./scripts/dev-rust-stack.sh --api-port 8090 --spacetime-port 3110 --database genarrative-dev
./scripts/dev-rust-stack.sh --skip-spacetime --skip-publish
./scripts/dev-rust-stack.sh --clear-database
```
## 3. Ubuntu 发布包脚本
入口:
```bash
npm run build:rust:ubuntu
```
兼容入口:
```bash
npm run deploy:rust:remote
```
保留 `deploy:rust:remote` 是为了不打断既有命令习惯;当前语义已调整为“生成 Ubuntu 发布包”,不再通过 SSH 进入服务器执行部署。
默认流程:
1. 在仓库根目录创建 `build/`
2.`build/` 下创建当前时间命名的目标目录,例如 `build/20260422-153000/`
3. 使用 Vite 构建前端 release 到目标目录的 `web/`
4. 执行 `cargo build -p api-server --release --target x86_64-unknown-linux-gnu --manifest-path server-rs/Cargo.toml`,并把 `api-server` 复制到目标目录。
5. 执行 `cargo build -p spacetime-module --release --target wasm32-unknown-unknown --manifest-path server-rs/Cargo.toml`,并把 `spacetime_module.wasm` 复制到目标目录。
6. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 并把 `/api/*``/generated-*``/healthz` 反代到本包内的 `api-server`
7. 在目标目录写入 `start.sh``stop.sh`
发布包结构:
```text
build/<timestamp>/
├─ web/
├─ api-server
├─ spacetime_module.wasm
├─ web-server.mjs
├─ start.sh
├─ stop.sh
└─ README.md
```
常用示例:
```bash
npm run build:rust:ubuntu -- --name 20260422-153000
npm run build:rust:ubuntu -- --database genarrative-dev --web-port 3000 --api-port 8082 --spacetime-port 3101
```
目标服务器启动:
```bash
cd build/<timestamp>
./start.sh
./stop.sh
```
安全边界:
1. 构建脚本不读取、不传输、不打印生产密钥。
2. 目标服务器 `.env``.env.local` 或进程环境仍由服务器本身维护。
3. `start.sh` 默认不清空 SpacetimeDB只有显式执行 `./start.sh --clear-database` 才允许清库重发。
4. `start.sh` 使用 `spacetime publish --bin-path spacetime_module.wasm --yes` 发布当前包内 wasm。
5. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。
目标服务器最小要求:
1. Ubuntu x86_64。
2. 已安装 `node`,用于运行发布包内的 `web-server.mjs`
3. 已安装 `spacetime` CLI`start.sh` 会启动本地 SpacetimeDB 并发布 wasm。
4. 业务密钥通过目标服务器环境变量或发布包同目录 `.env.local` 提供。
## 4. 与 M7 的关系
这套脚本补齐 M7 的部署执行入口但不等价于完成灰度切流。M7 后续仍需要在真实 OSS、LLM、短信、微信、SpacetimeDB 数据库和反向代理环境下完成全链路 smoke、关键 SSE 联调和灰度切流验收。