Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-06 23:19:00 +08:00
parent d678929064
commit ddcb5d5c8c
241 changed files with 19805 additions and 2478 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,744 @@
# PixelMotion 技术方案拆解2026-04-04
## 1. 文档目的
拆解 [PixelMotion](https://www.pixelmotion.art/#features) 当前线上版本的产品形态与技术实现思路,重点回答下面几个问题:
- 它到底是不是“AI 一键做像素动画”
- 前端、后端、数据层分别怎么分工
- 它为什么能比普通图生图更稳定地产出可用 Sprite Sheet
- 如果我们在自己的项目里复刻类似能力,应该保留哪些关键设计
本次拆解时间截至:`2026-04-04`
---
## 2. 调研范围与证据等级
本次只基于**线上公开可见信息**进行拆解,未拿到 PixelMotion 的私有后端代码。
### 2.1 已实际核对的公开对象
- 首页 HTML
- `manifest.json`
- `sw.js`
- 前端打包产物 `assets/index-BHdWGq2s.js`
- 前端样式产物 `assets/index-DFENcMpn.css`
- 公开接口:
- `/api/utils?type=stats`
- `/api/utils?type=gifs`
- `/api/showcase?limit=3`
- 线上响应头
### 2.2 本文的标记规则
- `【已确认】`:可直接从公开页面、脚本、接口返回或响应头确认
- `【高概率推断】`:无法看到后端源码,但从前端契约和数据形态可以较高把握推断
- `【推测】`:仅作为方案猜测,不能当成已确认事实
---
## 3. 先说结论
PixelMotion 本质上不是“运行时实时动画引擎”,而是一个**面向社交传播和轻量游戏资产生产的 AI Sprite Sheet 工坊**。
它的核心不是:
- 生成视频后再让前端临时播放
- 在浏览器里做复杂骨骼动画
- 给任意图片自由发挥动作
它真正的关键设计是:
1. 把输出格式强约束成 **4x4、16 帧的 Sprite Sheet PNG**
2. 把动作空间强约束成 **模板动作 + 极强提示词规则**
3. 把生成后的可用性问题交给 **前端二次编辑** 解决:
- 隐藏坏帧
- 调整帧序
- 调整 FPS
- 导出 PNG / GIF / Set
4. 把账号、存储、积分、分享、审核全部接进同一套 BaaS 与 API 网关
一句话概括:
**它不是在做“无限自由的 AI 动画”,而是在做“高约束、可编辑、可分享的像素精灵表生成流水线”。**
---
## 4. 产品形态拆解
## 4.1 用户主流程
【已确认】从前端路由、文案和接口可以还原出主流程:
1. 上传角色图
2. 选择动作
3. 可选开启 AI 角色分析,或直接用图片参考
4. 提交生成
5. 得到一张 16 帧 Sprite Sheet
6. 在编辑页中调帧序、隐藏帧、改 FPS
7. 导出 PNG / GIF / 多角色 Set
8. 保存到个人资产库
9. 可投稿到 Showcase 社区
## 4.2 它卖的不是视频,而是精灵表
【已确认】前端内部把结果当成一个 `4 x 4` 的 sprite sheet 来处理:
- 默认按 `rows=4``cols=4`
- 一共 `16`
- GIF 导出是从单张图里切格子,不是播放视频
- PNG 导出也是从同一张大图里重组
这说明 PixelMotion 的核心资产格式是:
- 生成结果主文件:`Sprite Sheet PNG`
- 预览导出:浏览器侧再转 `GIF`
所以它更像:
- 游戏资产小工坊
- 社交素材制作器
而不是:
- 视频生成器
- 骨骼动画编辑器
---
## 5. 前端技术栈
## 5.1 应用框架
【已确认】
- 前端是 **React SPA**
- 打包方式很像 **Vite**
- 路由是 **React Router**
- 国际化使用 **i18next**
- 样式是 **Tailwind 风格的 utility CSS**
确认依据:
- HTML 使用 `type="module"` 加载单入口 bundle
- bundle 中可见 React 生产构建标识
- 存在 `/`, `/home`, `/edit`, `/terms`, `/privacy`, `/admin/review` 等前端路由
- CSS 中存在大量 `--tw-*` 变量与 utility class
## 5.2 UI 结构
【已确认】它不是纯 landing page而是两层产品
- 落地页:首页、功能说明、社区展示、定价入口
- 工具页:`/edit`
这种拆法的好处是:
- SEO 与转化页保持轻量
- 真正复杂的工具状态集中在编辑页
## 5.3 PWA 与缓存
【已确认】
-`manifest.json`
- 注册了 `serviceWorker`
- `sw.js` 会缓存核心静态资源
- 运行时采用近似 **network first + cache fallback**
这说明它把产品定位成:
- 可被收藏到手机桌面
- 网络不稳时仍能较快打开外壳
---
## 6. 部署与托管
## 6.1 静态站托管
【已确认】
- 响应头里有 `x-vercel-id`
- 也有 Cloudflare 相关头
这说明它大概率是:
- **Vercel 托管应用**
- **Cloudflare 在前层做缓存 / 统计 / 防护**
## 6.2 API 组织方式
【已确认】所有业务接口都挂在同域 `/api/*` 下,例如:
- `/api/analyze-proxy`
- `/api/transform-action`
- `/api/generate-proxy`
- `/api/generate-beta-actions`
- `/api/remove-background`
- `/api/payment/create`
- `/api/payment/creem-create`
- `/api/showcase`
- `/api/user-actions`
- `/api/utils?type=geo`
- `/api/utils?type=stats`
【高概率推断】这类路径风格非常像:
- Vercel Serverless Functions
- 或同类 Node/Edge API 层
其目的非常明确:
- 把模型供应商密钥藏到服务端
- 把支付和鉴权放到服务端
- 把不同模型调用统一封装成同域 API
---
## 7. 数据层与 BaaS 方案
## 7.1 Supabase 接入
【已确认】前端 bundle 中直接包含 Supabase 项目地址,并创建了 Supabase client。
可确认能力包括:
- `auth`
- `storage`
- `database`
- `rpc`
## 7.2 已暴露出的主要表与存储桶
【已确认】
存储桶:
- `pixel-assets`
- `showcase`
数据表:
- `user_assets`
- `user_actions`
- `user_credits`
RPC
- `redeem_credits`
## 7.3 数据职责推断
【高概率推断】这些表/桶的职责大致如下:
### `user_assets`
- 保存用户生成结果
- 主文件是 sprite sheet 的存储路径
- 还会附带 `action_metadata`
- `action_metadata` 里至少保存:
- action 信息
- `activeFrames`
- `frameOrder`
- `fps`
### `user_actions`
- 保存动作收藏 / 用户动作偏好
- 也可能承载“每日刷新动作配额”相关信息
### `user_credits`
- 保存积分余额
### `showcase`
- 保存社区投稿所需的上下文图和结果 GIF
- 可见字段包括:
- `title`
- `content`
- `author`
- `context_images`
- `result_gifs`
- `is_private`
- `private_flags`
---
## 8. 生成链路拆解
## 8.1 不是单模型一步到位
【已确认】从接口命名和编辑页流程看PixelMotion 至少拆成了 4 类能力:
1. 角色理解:`/api/analyze-proxy`
2. 动作文本结构化:`/api/transform-action`
3. 精灵表生成:`/api/generate-proxy`
4. 背景去除:`/api/remove-background`
【高概率推断】这意味着它后端不是一个模型完成全部事情,而是多步骤流水线。
## 8.2 两种输入模式
【已确认】编辑器存在两种模式:
- `AI Character Analysis`:更慢、更精确
- `Direct image reference`:更快
【高概率推断】对应两条链路:
### 模式 A分析后生成
1. 上传图片
2.`analyze-proxy`
3. 把角色描述文本 + 动作模板一起送去生成
优点:
- 更容易抽出稳定的角色语义
- 对脏图、照片、复杂背景更友好
缺点:
- 多一次模型调用
- 时延更高
### 模式 B直接图参考
1. 上传图片
2. 跳过分析
3. 直接把参考图 base64 传给 `generate-proxy`
优点:
- 更快
缺点:
- 对输入质量更敏感
## 8.3 自定义动作不是直接裸喂一句话
【已确认】自定义动作会先走 `/api/transform-action`
【高概率推断】这一步的作用是:
- 把用户自由文本改写成更结构化的动作描述
- 降低“自定义动作太抽象、太文学化”导致的失控
这其实非常关键,因为 PixelMotion 并不是让用户把任何想法直接丢给绘图模型,而是先做一层“动作编排翻译”。
## 8.4 最终输出契约
【已确认】`generate-proxy` 返回的是单个 `imageBase64`,前端将其当成一张 4x4 精灵表处理。
【高概率推断】服务端最终对前端暴露的契约就是:
- 输入prompt + 可选参考图
- 输出:一张最终可切片的 Sprite Sheet
这有两个可能实现:
### 方案 1直接让模型产出 4x4 精灵表
优点:
- 前端最简单
- 一次请求得到最终结果
风险:
- 帧间一致性很难控
- 容易出现局部漂移
### 方案 2后端逐帧生成后再拼表
优点:
- 更可做重试和筛帧
风险:
- 成本更高
- 延迟更大
【我的判断】结合它前端动作模板的写法PixelMotion 当前更像是**强约束 prompt 直接产出整张精灵表**,或者至少对模型暴露的是“直接出 16 帧表”的任务,而不是先生成视频再切帧。
---
## 9. 为什么它比普通图生图更稳
PixelMotion 真正有价值的不是“模型多强”,而是它把自由度砍掉了。
## 9.1 固定 16 帧、固定网格
【已确认】
- 固定 `4 x 4`
- 固定 `16`
好处:
- 模型目标明确
- 导出逻辑简单
- 编辑器也更容易做
## 9.2 动作模板非常强约束
【已确认】游戏动作模板不是只写“run”“attack”这种词而是把动作分成明确阶段
- 起势
- 主动作
- 收势 / 回正
并且强制约束:
- 始终朝同一方向
- 不允许左右镜像翻转
- 身体水平位置固定
- 武器不换手
- 循环动作必须首尾闭合
【高概率推断】这类 prompt 工程是 PixelMotion 稳定性的核心来源之一。
它本质上是在把“动画导演规则”提前写进 prompt而不是把结果完全交给模型即兴发挥。
## 9.3 后编辑能力兜底
【已确认】它允许:
- 拖拽调整帧序
- 双击隐藏坏帧
- 调整 FPS
- 保存布局
这一步非常重要,因为 AI 结果不可能永远 16 帧都完美。
PixelMotion 的思路不是追求“模型一次全对”,而是:
- 先把结果做出来
- 再让用户低成本修整
这比试图用一次模型调用直接得到完美动画更现实。
## 9.4 导出链路放到浏览器
【已确认】
- PNG 导出使用 canvas 重排
- GIF 导出使用 `gif.js`
- 透明背景导出时按需调用 `/api/remove-background`
这意味着:
- 昂贵的 AI 只负责生成主资产
- 廉价的导出和重排交给前端本地完成
这个分工非常合理。
---
## 10. 编辑器能力拆解
## 10.1 编辑页不是“看结果页”,而是轻量资产编辑器
【已确认】编辑页至少具备这些能力:
- 查看单张 sprite sheet
- 播放动画预览
- 调 FPS
- 隐藏帧
- 调整帧顺序
- 保存布局
- 导出 PNG
- 导出 GIF
- 多选导出 Set
## 10.2 布局持久化
【已确认】保存布局时会把状态写回 `user_assets.action_metadata`
这说明它没有把“AI 结果”和“人工修整结果”分离成两套系统,而是把二次编辑直接沉淀到资产元数据里。
这个做法很实用,因为:
- 同一资产可反复继续编辑
- 资产库里保存的是“可用版本”,不是原始毛坯
---
## 11. 社区与增长设计
## 11.1 Showcase
【已确认】首页有公开社区展示,数据来自 `/api/showcase?limit=20`
公开返回里能看到:
- 原始上下文图
- 结果 GIF
- 作者名
- 标题和故事文案
这说明 PixelMotion 不是只强调“工具效率”,还在做:
- UGC 内容传播
- 首页案例社证
- 情绪价值展示
## 11.2 审核后台
【已确认】前端存在 `/admin/review` 页面,审核通过后可发布 Showcase。
说明社区能力不是完全开放式自动发布,而是有人工审核流。
## 11.3 隐私模式
【已确认】Showcase 数据里存在:
- `is_private`
- `private_flags`
【高概率推断】它支持对原始参考图做局部隐私隐藏或模糊,只公开结果,不完全公开全部上下文。
---
## 12. 账号、积分与支付
## 12.1 账号体系
【已确认】
- 使用 Supabase Auth
- 支持邮箱注册登录
- 支持找回密码
- 支持 OAuth 登录入口
## 12.2 积分体系
【已确认】
- 生成主流程消耗 credits
- 刷新动作推荐也可能消耗 credits
- 前端存在 `NO_CREDITS``freeCreditsExhausted` 等状态
- 可通过 `redeem_credits` 兑换码充积分
## 12.3 定价方案
【已确认】前端内置了三档套餐:
| 套餐 | 人民币 | 美元 | 基础积分 | 赠送 |
| --- | --- | --- | --- | --- |
| `basic` | `9.9` | `4.99` | `5` | `0` |
| `pro` | `49.5` | `24.9` | `25` | `3` |
| `studio` | `99` | `46.9` | `50` | `10` |
## 12.4 区域支付分流
【已确认】
- 中国区:`/api/payment/create`
- 国际区:`/api/payment/creem-create`
结合文案还可确认:
- 中国区支持微信 / 支付宝语境
- 国际区有信用卡语境
这说明它做了明显的地域分流支付设计。
---
## 13. 一个更完整的技术架构图
```mermaid
flowchart LR
A["首页 / 编辑页<br/>React SPA"] --> B["同域 API 层<br/>/api/*"]
A --> C["Supabase Auth"]
A --> D["Supabase Storage / DB"]
B --> E["角色分析服务<br/>analyze-proxy"]
B --> F["动作文本结构化<br/>transform-action"]
B --> G["精灵表生成服务<br/>generate-proxy"]
B --> H["去背景服务<br/>remove-background"]
B --> I["支付服务<br/>China / Intl"]
G --> J["返回 4x4 Sprite Sheet PNG"]
J --> A
A --> K["本地导出层<br/>Canvas + gif.js"]
A --> L["Showcase 投稿"]
L --> D
```
---
## 14. 如果我们复刻,最该学什么
如果要学 PixelMotion我认为最值得学的不是 UI而是下面 6 个工程判断。
## 14.1 先把资产格式定死
不要一开始就追求:
- 任意帧数
- 任意布局
- 任意动作长度
先定死一个可生产、可编辑、可导出的资产标准,例如:
- 16 帧
- 4x4
- PNG sprite sheet
## 14.2 模型前面一定要有动作模板层
用户说“跑步”“攻击”“比心”,不应该原样送给模型。
应该先转换成:
- 朝向约束
- 位移约束
- 起承转合
- 武器规则
- 循环规则
## 14.3 允许生成后修帧
不要试图只靠模型一次完成。
至少要有:
- 隐藏帧
- 排序
- FPS
否则大量“差一点能用”的结果会直接报废。
## 14.4 导出放前端,本地完成
像 PNG 重排、GIF 预览、透明导出这类低成本操作,完全没必要都放到后端。
## 14.5 资产库和社区是增长飞轮
PixelMotion 不只是一个生成按钮,它还做了:
- 我的资产库
- 作品展示
- 投稿审核
- 分享内容化
这能把一次性生成工具,变成可留存产品。
## 14.6 支付、积分、地域适配要早做
它不是等产品成熟后才补商业化,而是从一开始就把:
- credits
- 兑换码
- 地域支付
- 国际支付
串进主流程里了。
---
## 15. 对 PixelMotion 后端方案的推断版复原
下面给一个我认为比较接近它真实实现的“后端内部分层草图”。
## 15.1 推荐复原结构
### API Gateway 层
- 鉴权
- 积分扣减
- 配额判断
- 路由到不同 AI 服务
### Prompt Builder 层
- 读取动作模板
- 合成角色描述
- 合成单方向、单中心点、16 帧规则
### Generation Worker 层
- 调视觉模型生成 sprite sheet
- 失败时重试
- 返回 base64 PNG
### Asset Service 层
- 存储到 `pixel-assets`
-`user_assets`
- 保存布局元数据
### Export Layer
- 前端本地切帧
- 前端本地生成 GIF
### Community Layer
- 投稿
- 审核
- 公开展示
---
## 16. 风险与局限
因为没有后端源码,本拆解仍有边界。
## 16.1 不能确认的部分
- 实际调用了哪一家模型供应商
- 是单次整图生成,还是逐帧生成后拼图
- 支付后端的真实实现细节
- 服务端有没有额外的安全审核与图像后处理
## 16.2 但可以较确定的部分
- 前端是 React + Vite 风格 SPA
- 数据层是 Supabase
- 输出主资产是 16 帧 sprite sheet
- 编辑器支持帧编辑和本地导出
- 产品采用积分制
- 社区、审核、展示是正式能力,不是临时 demo
---
## 17. 最终判断
PixelMotion 的成功点,不在于它“偷偷用了某个神秘模型”,而在于它把一个本来极不稳定的问题,拆成了一个高度约束的资产生产流程:
- 输入被约束
- 动作被模板化
- 输出被标准化
- 错误被编辑器兜底
- 结果被资产库与社区承接
如果我们要借鉴它,最应该借鉴的是这套**产品约束 + 资产契约 + 编辑兜底**的整体设计,而不是只学“上传一张图点生成”这层表面交互。
---
## 18. 资料来源
本次拆解实际使用了以下公开来源:
- 官网首页:
[https://www.pixelmotion.art/](https://www.pixelmotion.art/)
- PWA Manifest
[https://www.pixelmotion.art/manifest.json](https://www.pixelmotion.art/manifest.json)
- Service Worker
[https://www.pixelmotion.art/sw.js](https://www.pixelmotion.art/sw.js)
- 公开统计接口:
[https://www.pixelmotion.art/api/utils?type=stats](https://www.pixelmotion.art/api/utils?type=stats)
- 公开 GIF 列表接口:
[https://www.pixelmotion.art/api/utils?type=gifs](https://www.pixelmotion.art/api/utils?type=gifs)
- 公开 Showcase 接口示例:
[https://www.pixelmotion.art/api/showcase?limit=3](https://www.pixelmotion.art/api/showcase?limit=3)
- 首页前端 bundle 与样式产物:
- `https://www.pixelmotion.art/assets/index-BHdWGq2s.js`
- `https://www.pixelmotion.art/assets/index-DFENcMpn.css`

14
docs/technical/README.md Normal file
View File

@@ -0,0 +1,14 @@
# 技术方案
这一组文档偏技术选型、实现路线和外部产品形态拆解。
## 文档列表
- [AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md](./AI_CHARACTER_ANIMATION_TECHNICAL_SOLUTION_2026-04-04.md)AI 生成角色形象与角色动画的技术路线。
- [PIXELMOTION_TECHNICAL_BREAKDOWN_2026-04-04.md](./PIXELMOTION_TECHNICAL_BREAKDOWN_2026-04-04.md)PixelMotion 产品形态与能力拆解。
- [SERVER_DEPLOYMENT_AND_CORS_TECHNICAL_SOLUTION_2026-04-05.md](./SERVER_DEPLOYMENT_AND_CORS_TECHNICAL_SOLUTION_2026-04-05.md):服务端部署、代理层与 CORS 方案。
## 使用建议
- 做实现选型时,优先看这一组。
- 做阶段排期时,把这一组和 `docs/planning/``docs/prd/` 一起看,更容易判断先后顺序。

View File

@@ -0,0 +1,957 @@
# 服务端部署与 CORS 完整技术方案
日期:`2026-04-05`
## 1. 文档目标
本文要解决的不只是“当前部署到服务器后浏览器报 CORS”而是同时解决下面几类问题
1. 浏览器访问大模型、图片生成等第三方服务时的跨域问题
2. 前端不能安全暴露 API Key 的问题
3. 当前项目开发期依赖 Vite middleware生产环境缺少正式 API 服务的问题
4. 后续服务端能力扩展时,避免再次推倒重来
目标结论很明确:
- **浏览器以后不再直连第三方大模型 / 图片服务**
- **浏览器只访问我们自己的站点域名下的 `/api/*`**
- **生产环境新增独立 Node API 服务Vite 代理只保留给开发环境**
- **把运行时接口、编辑器接口、异步任务、存储能力分层设计**
---
## 2. 结合当前仓库的现状判断
从当前仓库可以确认几件事:
### 2.1 前端已经按“走本地代理”在写
当前前端代码并不是直接请求第三方接口,而是请求:
- `/api/llm/chat/completions`
- `/api/custom-world/scene-image`
- `/api/item-overrides`
- `/api/npc-visual-overrides`
- `/api/character-overrides`
- `/api/scene-overrides`
- `/api/state-function-overrides`
- `/api/character-visual/publish`
- `/api/animation/publish`
这说明项目方向本身就是对的:**前端应该访问自己的 API 层,而不是浏览器直连外部服务。**
### 2.2 当前 API 层还只是开发期能力
这些接口现在主要由 [scripts/dev-server/localApiPlugins.ts](/E:/Repos/Genarrative/scripts/dev-server/localApiPlugins.ts) 挂在 Vite dev/preview 服务器里。
这在本地开发阶段很方便,但生产环境存在几个明显问题:
1. `Vite dev server / preview server` 不适合长期承担正式后端职责
2. 编辑器写接口、文件读写、代理转发都混在构建配置链路里
3. 未来加入鉴权、限流、审计、任务队列、数据库时会非常难扩展
4. 浏览器部署后如果没有这层正式 API 服务,就会重新退回“直连第三方接口”,于是重新触发 CORS
### 2.3 工程审查文档也已经指出同样风险
[docs/experience/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md](/E:/Repos/Genarrative/docs/experience/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md) 明确沉淀过一条经验:
- 浏览器直连会遇到 CORS
- 更稳的方案是开发服务器代理,再由前端请求 `/api/llm/...`
[docs/audits/engineering/ENGINEERING_OPTIMIZATION_REVIEW_2026-03-29.md](/E:/Repos/Genarrative/docs/audits/engineering/ENGINEERING_OPTIMIZATION_REVIEW_2026-03-29.md) 也明确指出:
- 编辑器、运行时、类后端能力全部耦合在 Vite 配置里
- 未来如果做独立部署、多人协作、远程编辑、权限控制,会非常难迁移
所以这次不是简单“补个 CORS header”就完了而是应该顺势把正式服务端边界立起来。
---
## 3. CORS 问题的根因
## 3.1 浏览器的跨域限制不是第三方接口“能调用”就能绕过
只要浏览器页面域名和目标接口域名不一致,就可能触发跨域限制。
典型场景:
- 页面在 `https://game.example.com`
- 浏览器直接请求 `https://ark.cn-beijing.volces.com/...`
- 或直接请求 `https://dashscope.aliyuncs.com/...`
此时就算第三方服务本身可用,只要对方没有返回允许你站点的 CORS 头,浏览器依然会拦截。
## 3.2 这类请求几乎一定会触发预检
因为现在请求通常具备以下特征:
- `Content-Type: application/json`
- `Authorization: Bearer ...`
- `POST`
- 流式返回 / SSE
这类请求很容易先发一个 `OPTIONS` 预检请求。只要预检没过,真正请求根本发不出去。
## 3.3 即使第三方临时支持 CORS也不应该让浏览器直连
因为浏览器直连还有更大的问题:
1. API Key 会暴露到前端
2. 无法统一限流和熔断
3. 无法统一记录调用日志
4. 无法对不同上游做协议适配
5. 无法在后续接入鉴权、配额、用户级审计
所以**正确方向不是“让浏览器跨域成功访问第三方”,而是“让浏览器根本不需要跨域访问第三方”。**
---
## 4. 推荐的总体方案
## 4.1 核心原则
1. **同源优先**:浏览器尽量只访问当前站点同域下的 `/api/*`
2. **密钥只在服务端存在**:前端不再持有真实第三方 Key
3. **运行时接口与编辑器接口分层**
4. **长耗时任务异步化**:图片、视频、资产发布不要长期卡在同步 HTTP 请求里
5. **存储外置化**:生产环境不要继续把生成文件直接写回源码目录
6. **可观测性内建**:日志、追踪、限流、告警一开始就留口子
## 4.2 推荐目标架构
```mermaid
flowchart LR
A["玩家浏览器 / 编辑器浏览器"] --> B["CDN / Nginx"]
B --> C["静态前端 dist"]
B --> D["Node API Gateway / BFF"]
D --> E["LLM Provider Adapter"]
D --> F["Image / Media Adapter"]
D --> G["业务服务层"]
D --> H["Redis / Queue"]
D --> I["PostgreSQL"]
D --> J["对象存储 OSS / S3"]
H --> K["Worker 异步任务进程"]
K --> F
K --> J
K --> I
```
这套架构的意思是:
- `Nginx` 负责统一入口、静态资源、反向代理
- `Node API Gateway / BFF` 负责真正的 API 接入
- 第三方大模型、图片服务不再暴露给浏览器
- `Redis / Queue + Worker` 负责后续长任务
- `PostgreSQL` 负责持久化业务数据
- `OSS / S3` 负责图片、动画、导出资产等对象存储
---
## 5. 服务分层设计
## 5.1 Web 层
职责:
- 托管前端 `dist`
- 提供 SPA 回退到 `index.html`
-`/api/*` 代理给 Node API 服务
- 统一做 TLS、压缩、缓存、静态资源头
推荐:
- `Nginx``OpenResty`
- 线上前面可再加 CDN
## 5.2 API Gateway / BFF 层
职责:
- 接收浏览器请求
- 做 CORS、鉴权、限流、审计
- 统一代理 LLM、图片生成、编辑器接口
- 把前端协议转换成上游协议
- 屏蔽第三方差异
推荐技术:
- `Node.js 22 + TypeScript`
- Web 框架可选 `Fastify``Express`
建议取舍:
- 如果要**最快迁移当前项目**,可以先上 `Express`
- 如果要**从一开始就更重视插件化、schema、性能**,可以直接上 `Fastify`
本方案不强绑定框架,重点是边界,而不是框架名。
## 5.3 Adapter 层
不要让业务代码直接到处写第三方接口细节,建议抽成单独适配层:
- `llmAdapter`
- `imageAdapter`
- `storageAdapter`
- `queueAdapter`
职责:
- 统一请求签名
- 统一错误结构
- 统一超时、重试、熔断
- 统一日志字段
## 5.4 业务服务层
建议把业务服务按域拆开,而不是继续长在 Vite 插件里:
- `runtimeService`
- 聊天/剧情推进
- 自定义世界生成
- 场景图片生成
- `editorService`
- 预设读写
- override 管理
- 资源发布
- `assetService`
- 生成图片入库
- 角色/动画资源清单
- `authService`
- 登录
- 用户角色
- 会话/Token
## 5.5 Worker 层
以下能力建议逐步迁移到异步任务:
- 自定义世界场景图生成
- 角色动画素材生成
- 视频/大图后处理
- 导出包构建
理由:
1. 任务时间长,浏览器同步等待容易超时
2. 失败重试不方便
3. 需要排队与限流
4. 后续多用户并发时,不能把 Node API 线程长期占住
---
## 6. 推荐的域名与流量策略
## 6.1 最推荐:前端与 API 同域
推荐站点入口:
- `https://game.example.com/` 提供前端静态资源
- `https://game.example.com/api/*` 提供 API
这种方式下:
- 浏览器看见的是**同源**
- 主站绝大多数请求**根本不需要 CORS**
- 部署最稳
- Cookie、会话、CSRF、防缓存等策略也更容易统一
这是**解决 CORS 的首选方案**。
## 6.2 备选:前后端分子域
如果以后要把 API 单独托管,也可以用:
- 前端:`https://app.example.com`
- API`https://api.example.com`
此时才需要真正开启 CORS 白名单:
- 只允许 `https://app.example.com`
- 管理后台再额外允许 `https://admin.example.com`
- 不允许 `*`
## 6.3 不推荐:浏览器直接访问第三方服务
以下方式不再推荐:
- 浏览器直接请求火山、DashScope、OpenAI 等服务
- 浏览器直接持有厂商 Key
- 浏览器直接上传生成资产到第三方后再回写本地
这种方式即使短期能跑,也会反复被 CORS、安全和审计问题反噬。
---
## 7. CORS 策略设计
## 7.1 总体策略
### 运行时主站
如果使用同域反代:
- 玩家访问 `game.example.com`
- API 也在 `game.example.com/api/*`
此时**运行时主站可以不依赖 CORS**。
### 管理后台 / 编辑器
如果未来编辑器单独部署到:
- `https://editor.example.com`
而 API 在:
- `https://api.example.com`
才对编辑器开启白名单 CORS。
## 7.2 允许策略
建议只允许这些 Origin
- `https://app.example.com`
- `https://editor.example.com`
- 开发环境 `http://localhost:3000`
并通过环境变量配置:
```env
CORS_ALLOW_ORIGINS=https://app.example.com,https://editor.example.com,http://localhost:3000
ADMIN_CORS_ALLOW_ORIGINS=https://editor.example.com,http://localhost:3000
```
## 7.3 响应头建议
如果命中白名单,返回:
```http
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
Access-Control-Allow-Headers: Authorization,Content-Type,X-Request-Id,X-CSRF-Token
Access-Control-Max-Age: 600
```
只有在你明确使用 Cookie 会话时,才加:
```http
Access-Control-Allow-Credentials: true
```
## 7.4 不建议的配置
不要这样配:
```http
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
```
这既不安全,也不符合规范。
## 7.5 预检请求处理
`OPTIONS` 请求必须在 API 层快速返回 `204`,不要把它继续转发到业务逻辑。
建议:
- 统一中间件处理
- 只要 Origin 不在白名单,直接拒绝
- 预检通过后才进入业务处理
## 7.6 流式接口注意项
`/api/llm/chat/completions` 这种流式输出接口,还要补这些处理:
- `Cache-Control: no-cache`
- `Connection: keep-alive`
- `X-Accel-Buffering: no`
否则 `Nginx` 可能把流式内容缓存/聚合后再一次性吐给浏览器,导致前端看起来“流式失效”。
---
## 8. API 边界重构建议
建议不要继续把所有接口都平铺在根 `/api` 下,最好按域分层。
## 8.1 运行时 API
建议:
- `/api/runtime/llm/chat/completions`
- `/api/runtime/custom-world/scene-image`
- `/api/runtime/story/*`
- `/api/runtime/save/*`
职责:
- 面向玩家运行时
- 高并发、可限流
- 不允许直接写源文件
## 8.2 编辑器 API
建议:
- `/api/editor/item-overrides`
- `/api/editor/npc-visual-overrides`
- `/api/editor/character-overrides`
- `/api/editor/scene-overrides`
- `/api/editor/state-function-overrides`
- `/api/editor/assets/character-visual/publish`
- `/api/editor/assets/animation/publish`
职责:
- 面向创作者、运营、内部编辑器
- 必须鉴权
- 必须审计
- 不建议对公网完全开放
## 8.3 内部任务 API
建议:
- `/api/internal/jobs/*`
- `/api/internal/hooks/*`
职责:
- Worker 回调
- 内部系统同步
- 不给浏览器直接调用
---
## 9. 生产环境最小可行架构
如果你希望先尽快上线,而不是一上来就做重微服务,推荐先落这个版本。
## 9.1 单机版拓扑
```mermaid
flowchart TD
A["Browser"] --> B["Nginx"]
B --> C["dist 静态站点"]
B --> D["Node API Service"]
D --> E["LLM / DashScope"]
D --> F["本机持久化目录或对象存储"]
D --> G["PostgreSQL可后补"]
```
## 9.2 单机版适合解决的事
- 当前 CORS
- API Key 服务端托管
- 基础流式代理
- 简单编辑器接口
- 初步日志与限流
## 9.3 单机版暂时接受的妥协
- 图片生成先继续同步请求
- 小规模文件先存本机挂载目录
- 先不拆 Worker
- 编辑器暂时只给内网/白名单用户
这能保证你**先把项目稳稳部署起来**,而不是为了“最终形态”迟迟不落地。
---
## 10. 中期演进架构
当出现这些需求时,再进入下一阶段:
- 多人同时在线
- 多创作者协作
- 图片/视频生成任务变多
- 需要账号体系、存档、云同步
- 需要审计和版本回滚
推荐演进为:
```mermaid
flowchart LR
A["CDN / Nginx"] --> B["Web"]
A --> C["API Cluster"]
C --> D["Redis"]
C --> E["PostgreSQL"]
C --> F["OSS / S3"]
C --> G["Worker Cluster"]
G --> H["LLM / Image Vendor"]
```
演进重点:
1. API 服务可多实例部署
2. 任务通过队列解耦
3. 文件写对象存储
4. 业务状态入数据库
5. 后台操作有审计日志
---
## 11. 对当前仓库最关键的改造建议
## 11.1 第一优先级:把 Vite 里的 API 能力抽出来
当前 [scripts/dev-server/localApiPlugins.ts](/E:/Repos/Genarrative/scripts/dev-server/localApiPlugins.ts) 里的能力,建议分三类迁移:
### A. 运行时代理接口
- `LLM_PROXY_PATH`
- `CUSTOM_WORLD_SCENE_IMAGE_PATH`
这两类要迁移到正式 `server/` 服务里。
### B. 编辑器读写接口
- `item-overrides`
- `npc-visual-overrides`
- `character-overrides`
- `monster-overrides`
- `scene-overrides`
- `scene-npc-overrides`
- `state-function-overrides`
这类接口要保留,但必须加:
- 登录鉴权
- 角色权限
- 操作日志
- 环境隔离
### C. 资源发布接口
- `character-visual/publish`
- `animation/publish`
这类接口后续最适合迁移到:
- 对象存储
- 任务队列
- 元数据表
生产环境不建议继续“直接写 `public/generated-*` + 回写源码 JSON”。
## 11.2 第二优先级:把写源码文件改成写业务存储
开发期把 JSON 直接写回 `src/data/*.json` 可以接受,但生产环境不建议继续这样做。
建议演进路径:
### 短期
- 写到 `data/overrides/*.json`
- 作为运行数据目录挂载到服务器磁盘
- 和源码目录分离
### 中期
- `override` 元数据写 PostgreSQL
- 原始 JSON 内容也可写数据库 JSONB
- 大文件、图片、动画写对象存储
## 11.3 第三优先级:前端环境变量收敛
前端建议只保留少量公开变量,例如:
```env
VITE_API_BASE_URL=/api
VITE_LLM_PROXY_BASE_URL=/api/runtime/llm
VITE_SCENE_IMAGE_PROXY_BASE_URL=/api/runtime/custom-world/scene-image
```
而这些变量必须只存在服务端:
```env
LLM_BASE_URL=...
LLM_API_KEY=...
DASHSCOPE_BASE_URL=...
DASHSCOPE_API_KEY=...
DATABASE_URL=...
REDIS_URL=...
OBJECT_STORAGE_BUCKET=...
JWT_SECRET=...
```
---
## 12. 推荐的目录结构
在当前仓库基础上,建议逐步演化成:
```text
src/ # 前端
server/
app.ts # API 入口
routes/
runtime/
llm.ts
customWorld.ts
editor/
overrides.ts
assets.ts
internal/
jobs.ts
services/
llmService.ts
imageService.ts
overrideService.ts
publishService.ts
adapters/
llmAdapter.ts
dashscopeAdapter.ts
storageAdapter.ts
queueAdapter.ts
middleware/
cors.ts
auth.ts
rateLimit.ts
requestId.ts
config/
env.ts
workers/
mediaWorker.ts
storage/
uploads/
generated/
docs/
```
这样做的好处:
1. 前后端职责清晰
2. Vite 回到构建职责
3. 服务端逻辑可以独立部署
4. 以后做测试、监控、扩容都更自然
---
## 13. Nginx 参考配置
下面是一份适合当前项目思路的参考配置。
```nginx
server {
listen 80;
server_name game.example.com;
root /srv/genarrative/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
}
location /api/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-Id $request_id;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# 对流式接口很关键
proxy_buffering off;
add_header X-Accel-Buffering no;
}
}
```
如果未来拆成 `app.example.com` + `api.example.com`,再在 API 服务层开启精确 CORS 白名单。
---
## 14. API 服务中的 CORS 中间件建议
伪代码如下:
```ts
const allowOrigins = new Set([
'https://app.example.com',
'https://editor.example.com',
'http://localhost:3000',
]);
function applyCors(req, res) {
const origin = req.headers.origin;
if (!origin) {
return;
}
if (!allowOrigins.has(origin)) {
return;
}
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
res.setHeader(
'Access-Control-Allow-Methods',
'GET,POST,PUT,PATCH,DELETE,OPTIONS',
);
res.setHeader(
'Access-Control-Allow-Headers',
'Authorization,Content-Type,X-Request-Id,X-CSRF-Token',
);
res.setHeader('Access-Control-Max-Age', '600');
}
app.use((req, res, next) => {
applyCors(req, res);
if (req.method === 'OPTIONS') {
res.status(204).end();
return;
}
next();
});
```
要点:
1. 返回的是**请求方 origin 原值**,不是 `*`
2. 一定带 `Vary: Origin`
3. `OPTIONS` 在中间件层直接结束
4. 未命中白名单就不放行
---
## 15. 鉴权与安全建议
## 15.1 运行时接口
如果当前只是单人原型或内测,可以先允许匿名访问部分运行时接口,但仍建议至少加:
- IP 级限流
- 请求大小限制
- 上游超时
- 请求日志
## 15.2 编辑器接口
编辑器接口不能继续裸奔。
至少加:
1. 登录态
2. 角色权限
3. 操作人标识
4. 审计日志
5. 管理后台 Origin 白名单
建议角色最少拆成:
- `player`
- `editor`
- `admin`
## 15.3 密钥管理
所有第三方 Key 都只放在服务端:
- `.env.production`
- 服务器 secret manager
- 容器 secret
不要放到:
- `VITE_*`
- 浏览器 localStorage
- 前端 bundle
## 15.4 限流与熔断
建议至少做两层:
### Nginx 层
- IP 限流
- 突发连接限制
### API 层
- 用户 / Token 限流
- 按接口限流
- 上游失败熔断
这对图片生成和大模型请求尤其关键。
---
## 16. 存储设计建议
## 16.1 当前最容易出问题的点
当前开发期接口里存在:
- 直接读写 JSON 文件
- 直接写入 `public/generated-*`
这在单机开发是方便的,但上线后会遇到:
1. 多实例之间文件不同步
2. 发布新版本时生成文件可能丢失
3. 容器重启后本地文件丢失
4. 无法做版本与审计
## 16.2 推荐存储分层
### 结构化数据
用 PostgreSQL
- 用户
- 存档
- override 元数据
- 任务记录
- 审计日志
### 大文件 / 资源文件
用对象存储:
- 场景图
- 角色图
- 动画帧
- 导出资源
### 短生命周期状态
用 Redis
- 任务队列
- 限流计数
- 短期缓存
- 会话
---
## 17. 日志与可观测性
至少记录这些字段:
- `requestId`
- `userId` / `editorId`
- `route`
- `origin`
- `upstreamVendor`
- `model`
- `statusCode`
- `latencyMs`
- `timeoutMs`
- `errorCode`
- `errorMessage`
对大模型/图片接口尤其要记录:
1. 请求耗时
2. 上游状态码
3. 失败正文摘要
4. 重试次数
5. 任务 ID
这样以后排查时,才不会再次回到“只看到浏览器报 CORS不知道真因”的状态。
---
## 18. 推荐实施顺序
## 阶段 1先解决正式上线
目标:
- 前端可部署
- API Key 不暴露
- 不再浏览器直连第三方
- 当前 CORS 问题彻底解决
动作:
1. 新建独立 `server/` 服务
2.`/api/llm/chat/completions``/api/custom-world/scene-image` 迁过去
3. Nginx 统一反代 `/api/*`
4. 前端保持原有 `/api/*` 调用方式
5. 先在同域部署,尽量不碰 CORS
## 阶段 2把编辑器接口接入正式权限
动作:
1. editor API 独立命名空间
2. 增加登录、角色、审计
3. 把“写源码文件”改成“写业务数据目录或数据库”
## 阶段 3接入异步任务与对象存储
动作:
1. 图片生成改为 job queue
2. 结果回写数据库与对象存储
3. API 只返回任务状态与资源地址
## 阶段 4补齐平台化能力
动作:
1. 用户系统
2. 云存档
3. 限流配额
4. 后台审计
5. 监控告警
---
## 19. 最终推荐结论
针对这个项目,最稳、最适合当前现状、也兼顾未来的方案是:
1. **不要尝试让浏览器直接跨域访问第三方模型服务**
2. **新增独立 Node API 服务,替代当前生产环境对 Vite middleware 的依赖**
3. **通过 Nginx 把前端和 `/api/*` 统一到同一域名下,优先从架构层消灭大部分 CORS**
4. **把 API 分成 runtime / editor / internal 三层**
5. **把图片生成、资源发布逐步迁移到对象存储 + 异步任务**
6. **把未来的鉴权、限流、审计、日志、数据库能力预留在 BFF 层**
一句话总结:
**真正解决 CORS 的最佳方式,不是给浏览器更多跨域权限,而是让浏览器只访问你自己的服务;同时把当前开发期代理能力升级成正式的生产 API 架构。**
---
## 20. 适合立即执行的落地清单
如果现在就要开始做,建议按下面顺序推进:
1. 新建 `server/` 目录,先迁出 LLM 代理与场景图片代理
2. 把线上部署改成 `Nginx -> dist + Node API`
3. 保持前端仍然请求 `/api/llm``/api/custom-world/scene-image`
4. 先使用同域部署,不主动引入跨子域 CORS
5. editor 写接口上线前先加鉴权,不要裸开放
6. 生成文件先写挂载目录,下一步再迁对象存储
7. 为流式接口补 `proxy_buffering off`
8. 为 API 层补 `requestId + latency + upstream error` 日志
如果后续需要,我可以继续把这份文档下一步直接细化成:
- `server/` 目录结构草案
- `Express/Fastify` 的接口骨架
- `Nginx` 正式可用配置
- `Dockerfile + docker-compose` 部署方案
- 当前仓库对应的迁移 checklist