Files
Genarrative/docs/planning/EXPRESS_BACKEND_REFACTOR_PLAN_2026-04-08.md
2026-04-10 15:37:02 +08:00

448 lines
16 KiB
Markdown
Raw 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.
# Express 后端化工程重构规划2026-04-08
## 1. 背景
当前项目已经引入 `Express` 后端,且 `server-node/` 已经承接了运行时鉴权、存档、设置、自定义世界、剧情生成、角色聊天、NPC 对话、运行时物品意图、任务生成等能力。
但从当前工程状态看,项目仍处于“后端已存在,但运行时领域层尚未完全脱前端”的过渡态,主要表现为:
- 前端 `src/hooks/useStoryGeneration.ts` 仍然承担了大量运行时编排、规则拼接与状态推进职责。
- 前端 `src/services/ai.ts` 仍然保留了完整的 AI 调用、提示词拼装和本地兜底实现。
- 前端 `src/hooks/useGamePersistence.ts` 仍在承担较重的存档恢复、schema 纠偏与归一化职责。
- `server-node/src/**` 当前仍在直接引用 `src/types``src/data``src/services` 中的内容,分层尚未真正闭合。
- 编辑器相关写接口仍然散落在前端组件与 `jsonClient` 中,运行时 API 与编辑器 API 还没有完全归口。
现在既然已经明确“前端只负责做表现,所有逻辑、数据都放到后端进行运算和存储”,就需要把这个原则升级成整个工程的硬边界,而不是只停留在一部分接口迁移完成的状态。
---
## 2. 重构总原则
本轮重构只坚持一个核心原则:
**前端不是业务执行层,而是表现层;后端才是唯一的运行时真相来源。**
进一步展开为:
- 前端只负责页面结构、动画演出、输入采集、局部交互态、加载态和错误态展示。
- 后端负责鉴权、会话、规则计算、剧情推进、AI 编排、任务推进、道具结算、Build 结算、存档读写与持久化。
- 浏览器内不再保留“正式运行时业务规则”的第二套实现。
- 浏览器内允许存在少量纯表现计算,但不允许成为游戏状态真相来源。
- 编辑器能力与正式运行时能力分离,避免 dev 工具链继续污染正式运行时边界。
---
## 3. 重构目标
## 3.1 目标状态
- 浏览器只发送“玩家意图”和必要的展示参数,不直接提交完整运行时真相。
- `Express` 后端成为唯一的运行时状态源、规则执行源和 AI 调度源。
- 运行时快照、任务状态、NPC 状态、背包、属性、Build、剧情历史全部以后端持久化结果为准。
- 前端不再直接 import 正式运行时 AI 逻辑、提示词逻辑和关键规则逻辑。
- `server-node` 不再依赖 `src/**` 中的前端实现细节,而是依赖独立共享层。
- 编辑器 API、运行时 API、资产生成 API 形成清晰命名空间和权限边界。
## 3.2 非目标
- 本轮不追求一次性重写所有玩法系统。
- 本轮不再讨论关系型数据库选型切换,当前后端以 `PostgreSQL` 为准。
- 本轮不改动已有中文剧情、设定和文案方向。
- 本轮不为了“前后端分离”牺牲移动端体验与当前主流程可玩性。
---
## 4. 职责边界
| 领域 | 前端职责 | 后端职责 |
| --- | --- | --- |
| 页面与流程壳层 | 页面切换、面板开关、布局、自适应、动效、加载态 | 不负责页面 UI |
| 用户输入 | 收集点击、拖拽、表单输入、选项选择 | 校验输入是否合法,解释输入对应的运行时动作 |
| 游戏状态 | 仅持有当前展示所需 view model 和局部 UI state | 持有完整游戏状态、快照、事件日志、版本号 |
| 剧情推进 | 展示文本流、选项、动画时间线 | 生成剧情、决定选项集合、推进故事状态 |
| 战斗与数值 | 播放攻击、受击、死亡、位移 | 计算伤害、蓝耗、CD、死亡、掉落、逃跑结果 |
| NPC/同伴交互 | 展示面板、聊天输入框、关系反馈演出 | 计算关系变化、招募条件、交易合法性、对话结果 |
| 背包/装备/Build | 展示背包、装备栏、Build 面板 | 计算背包变化、装备结果、Build 收益与约束 |
| 任务系统 | 展示任务卡片、任务进度、奖励动画 | 生成任务、推进 signal、发放奖励 |
| AI 调用 | 不直接请求正式运行时模型 | 统一做 prompt 组装、模型调用、超时重试、日志 |
| 持久化 | 最多保留极少量表现态缓存 | 负责存档、设置、用户数据、迁移、恢复 |
| 编辑器 | 调用 SDK、展示工具面板 | 负责写盘、生成任务、队列、权限与审计 |
## 4.1 前端允许保留的状态
- 当前面板是否打开
- 当前动画是否播放中
- 当前流式文本已经显示到哪一段
- 表单草稿、搜索词、临时筛选条件
- 与展示相关的 viewport / media / motion 状态
## 4.2 前端禁止继续承载的职责
- function 合法性判定
- 怪物/NPC/任务/物品结算
- 正式运行时 prompt 组装
- 正式运行时 AI fallback
- 存档 schema 迁移主逻辑
-`localStorage` 作为正式运行时主存储
- 编辑器组件直接散落 `fetch('/api/...')` 访问写接口
---
## 5. 当前工程问题归纳
## 5.1 运行时领域逻辑仍然偏前端中心
- `useStoryGeneration` 仍然是大体量编排热区承接了剧情、NPC、战斗后续、任务和部分故事引擎逻辑。
- `src/services/ai.ts` 体量很大,说明正式运行时 AI 编排尚未完全移出浏览器。
- 当前“后端接口 + 前端兜底”的过渡模式,容易让正式逻辑继续双份存在。
## 5.2 服务端分层还没真正闭合
- `server-node` 当前仍直接引用 `src/types``src/data``src/services`
- 这意味着后端虽然有了入口,但核心领域模型仍然绑在前端目录结构上。
- 继续沿着这条路开发,会让后端无法独立测试、独立构建和独立演进。
## 5.3 运行时持久化边界还不够干净
- 虽然正式存档已经走远端接口,但前端仍承担较重的恢复、归一化、迁移纠偏逻辑。
- 这会导致“存档解释权”同时存在于前后端两边,后续迭代容易失配。
## 5.4 编辑器与运行时 API 仍然混杂
- 编辑器读写接口目前仍然有散落访问点。
- 资产生成、JSON 写盘、运行时 API 还没有形成清晰的接口分域。
- 继续混用会让权限控制、生产部署和后续多人协作变得困难。
## 5.5 当前协议更像“接口迁移”,还不是“后端驱动运行时”
- 目前很多接口是把已有前端逻辑搬成了远端调用入口。
- 但真正理想状态应该是玩家点击后后端完成规则结算、状态推进、AI 调用和持久化,再把展示模型返回给前端。
---
## 6. 目标架构
```text
Browser
├─ 页面 / 动画 / 交互 / ViewModel 渲染
├─ 轻量前端 SDK只负责请求与状态绑定
└─ 局部 UI State
packages/shared
├─ contracts
├─ schemas
├─ domain-types
└─ api-client-types
server-node
├─ src/modules/auth
├─ src/modules/runtime-session
├─ src/modules/story
├─ src/modules/combat
├─ src/modules/npc
├─ src/modules/inventory
├─ src/modules/build
├─ src/modules/quest
├─ src/modules/custom-world
├─ src/modules/editor
├─ src/shared/http
├─ src/shared/infra
└─ src/shared/llm
storage
├─ postgres
├─ uploads
└─ generated
```
## 6.1 共享层原则
- `packages/shared` 只放类型、schema、协议、纯函数和序列化约定。
- 共享层不放浏览器专属实现,也不放 Node 专属 IO。
- 所有可执行运行时规则默认放后端,不放共享层。
## 6.2 前端目录目标
前端建议逐步收敛成下面的职责结构:
```text
src/
├─ app
├─ pages
├─ widgets
├─ features
├─ entities
├─ shared/api
├─ shared/ui
└─ shared/lib
```
其中:
- `shared/api` 只保留面向后端 contract 的 SDK。
- `features` 只组织交互流程和 UI 组合,不再承载正式运行时规则。
- 超大 hook 逐步拆成“页面状态协调层 + 远端 action 调用层 + 表现层状态”。
---
## 7. 关键协议重构方向
当前最值得尽快统一的,不是继续加接口数量,而是把协议升级成“意图驱动”。
推荐核心动作协议:
```json
{
"sessionId": "runtime-session-id",
"clientVersion": 12,
"action": {
"type": "story_choice",
"functionId": "fight_attack",
"targetId": "npc_merchant_01",
"payload": {
"optionId": "opt_02"
}
}
}
```
后端统一返回:
```json
{
"sessionId": "runtime-session-id",
"serverVersion": 13,
"viewModel": {},
"presentation": {
"storyText": "",
"options": [],
"battlePlayback": null,
"toast": null
},
"patches": [],
"meta": {
"requestId": "req_xxx"
}
}
```
协议约束:
- 前端不再提交完整 `gameState` 作为后端运算依据。
- 前端提交的是“玩家意图”,不是“玩家已经算好的结果”。
- 后端返回的是“下一帧该怎么演”的展示模型,而不是只回一个零散字段。
---
## 8. 分阶段重构路线
## P0先冻结边界建立共享协议层
### 本阶段目标
把“前端只做表现,后端负责运行时真相”从口头原则变成工程边界。
### 主要任务
- 提取 `shared contracts`,把 `server-node``src/**` 的依赖逐步迁出。
- 固化统一的 API 响应结构、错误结构、`requestId`、版本字段。
- 明确运行时 API 命名空间与编辑器 API 命名空间。
- 新功能一律禁止再把正式运行时规则写回前端。
- 为关键运行时入口补健康检查、日志字段、耗时统计。
### 交付物
- 共享类型与 schema 目录
- 统一 API 约定文档
- 服务端模块边界草图
- 前端 SDK 基础层
### 验收标准
- `server-node` 可以不依赖 `src/**` 中的前端运行时实现继续编译。
- 新增运行时需求不再允许“前端先写一版、后端再补一版”。
## P1把运行时状态与持久化解释权收回后端
### 本阶段目标
让后端成为运行时状态、快照、恢复和迁移的唯一解释者。
### 主要任务
- 建立 `runtime session` / `snapshot aggregate`
- 将存档恢复、版本迁移、默认值补齐、schema 纠偏迁到后端。
- 把前端 `useGamePersistence` 收敛为“拉取快照 + 触发保存 + 接收 view model”。
- 设置、快照、自定义世界库统一归入运行时仓储接口。
- 明确哪些内容允许本地缓存,哪些必须以后端结果为准。
### 交付物
- 统一的运行时 session API
- 快照版本迁移服务
- 服务端持久化 schema 文档
### 验收标准
- 前端不再承担正式存档恢复迁移的主逻辑。
- 同一份存档的解释权只存在于后端。
## P2把核心规则结算从前端迁到后端
### 本阶段目标
把“剧情推进、战斗、NPC、任务、物品、Build”这些真正影响状态的领域结算全部后端化。
### 主要任务
- 把 function 合法性过滤迁入后端。
- 把战斗结算、蓝耗、伤害、死亡、掉落、逃跑结果迁入后端。
- 把 NPC 交互决策、招募条件、关系变化、交易合法性迁入后端。
- 把任务推进 signal、奖励结算、运行时物品结果、Build 结果迁入后端。
- 前端收到的只是一份下一步展示所需的聚合 view model 与演出计划。
### 交付物
- 运行时 action resolver
- 统一领域服务接口
- 面向 UI 的 view model assembler
### 验收标准
- 前端点击一个选项时,发送的是 action不是本地先算完再上传结果。
- 正式运行时的数值、资源、状态迁移不再依赖浏览器逻辑。
## P3把 AI 编排彻底收口到后端
### 本阶段目标
让浏览器彻底退出正式运行时 AI 调用与 prompt 组装。
### 主要任务
- 把剧情生成、角色聊天、NPC 对话、自定义世界生成、任务生成、物品意图生成等统一后端执行。
- 清理前端正式运行时代码中的 AI fallback。
- 将 prompt 构造、模型容错、超时、重试、日志、SSE 转发统一收口到后端。
- 对需要复用的 prompt 纯函数进行共享层抽取,但执行权只留在后端。
### 交付物
- 后端 AI orchestration 模块
- 统一 SSE/streaming 适配层
- 精简后的前端 AI SDK
### 验收标准
- 浏览器正式运行时代码不再直接 import 大体量 AI 编排模块。
- 无后端时,正式运行时不再默默回退到另一套浏览器逻辑。
## P4把编辑器与资产流程独立成正式后端模块
### 本阶段目标
让编辑器能力不再作为运行时副产物存在,而是成为有边界的工具后端模块。
### 主要任务
- 建立 `/api/editor/*``/api/assets/*` 等明确命名空间。
- 给编辑器写接口补权限、环境门禁、审计日志。
- 统一编辑器 JSON 读写、资产生成、任务查询接口。
- 前端编辑器组件全部改走统一 SDK不再散落直连接口。
### 交付物
- editor API contract
- 统一 editor client SDK
- 生成任务与写盘适配器
### 验收标准
- 编辑器写接口不再散落在多个组件内部。
- 运行时 API 与编辑器 API 的职责边界清晰。
## P5补齐质量门禁、部署路径和观测能力
### 本阶段目标
让这次后端化重构可以稳定上线,而不是只在本地联调成立。
### 主要任务
- 为后端补单测、接口测试和关键链路 smoke。
- 为前端补 contract 测试,确保 UI 不依赖本地规则。
- 建立 `Nginx/Caddy -> dist + /api` 的同域部署路径。
- 为流式接口补代理配置、超时、取消和日志。
- 为数据库迁移、备份、回滚预留脚本。
### 验收标准
- `web + server` 可以独立构建、独立测试、联合部署。
- 关键主流程至少具备一条可自动验证的 smoke path。
---
## 9. 具体迁移清单
## 9.1 优先迁移对象
- `src/hooks/useStoryGeneration.ts`
- `src/hooks/useGamePersistence.ts`
- `src/services/ai.ts`
- `src/services/aiService.ts`
- `src/services/storageService.ts`
- `src/services/authService.ts`
- 编辑器持久化模块与 `src/editor/shared/jsonClient.ts`
## 9.2 优先抽离到共享层的内容
- 领域类型定义
- zod schema 或等价校验协议
- API 请求与响应 contract
- 纯序列化函数
- 前后端都要认识的 enum / id / status 常量
## 9.3 不建议抽到共享层的内容
- 依赖数据库、文件系统、LLM、日志的服务
- 正式运行时规则执行器
- 存档迁移执行器
- 资产生成任务调度器
---
## 10. 实施顺序建议
推荐顺序如下:
1. 先抽共享类型与协议,切断 `server-node -> src/**` 的反向依赖。
2. 再把运行时 session、快照解释权、存档迁移收回后端。
3. 再迁核心规则结算,让前端从“业务执行层”退回“表现协调层”。
4. 然后彻底收口 AI 编排,移除正式运行时浏览器 fallback。
5. 最后归整编辑器 API、部署路径、测试门禁和观测能力。
不建议的顺序:
1. 先零散把几个接口改成后端。
2. 继续保留前端完整 fallback。
3. 最后再补共享层和协议。
这个顺序会把“双份逻辑并存”的过渡期拖得很长,后面会越来越难收口。
---
## 11. 风险与控制点
- 最大风险不是“迁不动”,而是长期维持双份规则。
- 后端化期间必须避免再往前端加新的正式运行时规则。
- 协议演进要带版本号,否则快照和 UI 很容易错位。
- 前端瘦身不能牺牲移动端一屏体验,表现层拆分仍要遵守移动端优先。
- 编辑器 API 必须和正式运行时隔离,不要为了方便继续走混用路径。
---
## 12. 一句话结论
这次重构的核心不是“把几个请求改成走 Express”而是
**把项目从“前端主导运行时、后端承接部分接口”的过渡架构升级成“Express 后端统一持有运行时真相,前端只负责表现和交互”的正式工程架构。**