448 lines
16 KiB
Markdown
448 lines
16 KiB
Markdown
# 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 后端统一持有运行时真相,前端只负责表现和交互”的正式工程架构。**
|