# 前端逻辑后移实施方案(2026-04-21) 更新时间:`2026-04-21` ## 1. 目标 本方案只回答一件事: **怎样把当前仍残留在前端的正式运行时逻辑、正式会话真相与正式生成编排,继续收回到 Express 后端。** 这份文档不是泛泛而谈的方向说明,而是直接面向本轮与后续几轮编码落地的实施基线。 --- ## 2. 本轮确定的硬边界 根据仓库约束与当前审计结果,本轮继续冻结以下边界: 1. 前端只负责表现、输入采集、临时 UI 状态与服务端结果渲染。 2. 后端负责正式鉴权、正式会话、正式运行时快照、正式任务生成、正式运行时物品意图生成、正式自定义世界生成。 3. `codex/backend-rewrite-spacetimedb` 目标分支的鉴权仍以服务端签发 JWT、前端 Bearer token 携带为准;本轮合入不采用 `codex/dev` 的 access cookie 会话方案。 4. 浏览器内不再把浏览历史作为本地正式真相,不再保留正式 quest / runtime item / custom world 生成编排。 5. 运行时主链必须继续向“前端提交意图,后端解释快照并返回展示模型”收敛。 --- ## 3. 现状拆分 当前残留问题已经收敛为三批: ### 3.1 第一批:正式真相仍在前端 1. `src/services/apiClient.ts` - 浏览器当前仍保存 access token,并在请求层拼接 `Authorization: Bearer ...` - 该链路在 `codex/backend-rewrite-spacetimedb` 仍是既定正式实现,不再按 cookie access session 改写 2. `src/services/authService.ts` - 登录、微信绑定、回调消费流程都要与 JWT/Bearer 方案保持一致,避免混入 access cookie 分支语义 3. `src/components/game-shell/PreGameSelectionFlow.tsx` - 浏览历史仍是本地写入 + 后端回填的双真相 4. `src/services/platformBrowseHistory.ts` - 维护浏览历史本地存储、迁移标记与同步状态 ### 3.2 第二批:运行时主链仍依赖前端预写快照 1. `src/hooks/story/runtimeStoryCoordinator.ts` - 在请求 runtime state / runtime action 前,仍先 `PUT /runtime/save/snapshot` 2. `src/hooks/story/npcEncounterActions.ts` - 待接委托的“更换任务”“放弃任务”仍由前端正式结算 ### 3.3 第三批:正式生成编排仍残留在浏览器 1. `src/services/questDirector.ts` 2. `src/services/runtimeItemAiDirector.ts` 3. `src/services/aiService.ts` 的 custom world profile 生成入口 4. `src/services/ai.ts` 中仍保留的浏览器侧 legacy AI orchestration --- ## 4. 分批实施策略 ## 4.1 第一批:先收正式真相 ### 鉴权 目标状态: 1. 后端继续通过 JWT 承载 access token,并只从 `Authorization: Bearer ...` 读取当前访问身份。 2. 前端请求层继续负责保存、刷新和携带 access token;公开请求与静默探测不得误清正式 token。 3. access cookie 会话方案不进入 `codex/backend-rewrite-spacetimedb`,避免和目标分支已有 JWT 方案并存。 4. `AuthGate` 通过 refresh cookie / `/api/auth/me` 恢复出用户后,必须先确保本地 access token 可用,再把 `readyUser` 暴露给运行时、设置、作品列表等受保护业务 hook;业务 hook 不能只凭 `user.id` 在 token 尚未补齐时启动远端请求。 本批涉及: 1. `server-node/src/routes/authRoutes.ts` 2. `server-node/src/middleware/auth.ts` 3. `src/services/apiClient.ts` 4. `src/services/authService.ts` 5. `src/components/auth/AuthGate.tsx` ### 浏览历史 目标状态: 1. 浏览历史唯一真相在 `runtimeRepository`。 2. 前端不再保留本地浏览历史、迁移标记、同步标记。 3. 浏览历史只通过 `storageService` 读取和写入。 本批涉及: 1. `src/components/game-shell/PreGameSelectionFlow.tsx` 2. `src/components/game-shell/PlatformHomeView.tsx` 3. `src/services/storageService.ts` 4. `src/services/platformBrowseHistory.ts` ## 4.2 第二批:把 runtime story 快照解释权收回后端 目标状态: 1. 前端不再通过单独的 `PUT /runtime/save/snapshot` 预写快照再触发动作。 2. runtime state / runtime action 允许前端提交当前快照上下文,由后端内部决定是否写入、如何解释、何时持久化。 3. NPC 待接委托的 replace / abandon / accept 全部走后端 runtime action。 建议实施方式: 1. 扩展 `packages/shared/src/contracts/story.ts` - `RuntimeStoryActionRequest` 增加可选 `snapshot` - 新增 `RuntimeStoryStateRequest` 2. 新增 `POST /api/runtime/story/state/resolve` 3. `storyActionService` 内部统一处理“请求携带快照上下文时的服务端同步” 4. 把 `npc_chat_quest_offer_replace` / `npc_chat_quest_offer_abandon` 接到后端 runtime action ## 4.3 第三批:把正式生成编排收成后端唯一出口 目标状态: 1. `questDirector` 只保留轻量 SDK。 2. `runtimeItemAiDirector` 只保留轻量 SDK。 3. custom world profile 正式生成走后端 route。 4. 浏览器侧 `src/services/ai.ts` 不再承担正式浏览器主链。 建议实施方式: 1. `server-node/src/routes/runtimeRoutes.ts` - 补 `custom-world/profile` 正式 route 2. `src/services/aiService.ts` - custom world 入口改走后端 3. `src/services/questDirector.ts` - 只请求 `/api/runtime/quests/generate` 4. `src/services/runtimeItemAiDirector.ts` - 只请求 `/api/runtime/items/runtime-intent` --- ## 5. 本轮落地范围 本轮优先完成以下内容: 1. 鉴权维持 `codex/backend-rewrite-spacetimedb` 既有 JWT/Bearer 方案,不合入 `codex/dev` 的 access cookie 访问认证。 2. 浏览历史从前端本地真相后移到后端唯一真相。 3. custom world profile 正式生成入口补齐后端 route,并把前端收成 SDK。 4. `questDirector` / `runtimeItemAiDirector` 收缩为前端 SDK。 5. runtime story contract 开始补“随请求提交快照上下文”的后端承接能力,并把 NPC 待接委托 replace / abandon 接到后端。 ### 5.1 已完成 1. `codex/backend-rewrite-spacetimedb` 本轮保留 JWT access token + refresh cookie 组合方案,不合入 access cookie 写入与读取链路。 2. 浏览历史已收敛为后端唯一真相,前端不再维护正式本地 browse history 链。 3. runtime story 已支持随请求提交 snapshot,由后端内部解释与持久化。 4. NPC 待接委托 `replace / abandon / accept` 已以后端 runtime action 为准。 5. custom world profile 浏览器正式入口已改走后端 route。 6. `questDirector` / `runtimeItemAiDirector` 已收缩为前端 SDK,不再承担正式浏览器编排。 7. NPC 招募正式结算已迁到后端: - 前端只负责招募对白展示与 release 目标选择 - 后端负责 `npcStates / companions / roster / currentEncounter / storyHistory` 正式结算 - 满员换队招募已由后端承接 ### 5.2 剩余未完成 1. `src/services/ai.ts` 仍保留 legacy fallback / test 能力,尚未彻底压缩出正式浏览器主链。 2. 仍需继续审视是否存在其他 NPC / 运行时分支,把正式状态裁决留在前端。 --- ## 6. 验收标准 ### 第一批验收 1. 浏览器继续保存 access token,并由 `fetchWithApiAuth` 稳定拼接 `Authorization: Bearer ...`。 2. 401 刷新链只在已发送 Bearer token 时触发,并且刷新响应必须返回新的 JWT。 3. 浏览历史仅通过远端接口读写。 4. `src/services/platformBrowseHistory.ts` 不再是正式链路依赖。 5. 手机验证码登录、微信回调或 refresh cookie 会话恢复完成后,首屏并发读取设置、存档、个人看板、浏览历史、作品列表前,必须已经完成 access token 写入,避免出现“用户已 ready 但请求缺少 Authorization Bearer Token”的竞态。 ### 第二批验收 1. `runtimeStoryCoordinator.ts` 不再在动作前独立 `PUT /runtime/save/snapshot`。 2. `NPC` 待接委托 replace / abandon / accept 都以后端返回结果为准。 ### 第三批验收 1. `questDirector.ts` 与 `runtimeItemAiDirector.ts` 不再保留正式 fallback orchestration。 2. custom world profile 的浏览器正式入口不再直接 import legacy `./ai`。 --- ## 7. 一句话结论 这轮迁移的重点不是“把几个 helper 挪到 server-node 目录”,而是: **把前端里仍然承担正式真相、正式运行时解释和正式生成编排的那一层职责,继续收回到 Express 后端。**