Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
File diff suppressed because it is too large
Load Diff
744
docs/technical/PIXELMOTION_TECHNICAL_BREAKDOWN_2026-04-04.md
Normal file
744
docs/technical/PIXELMOTION_TECHNICAL_BREAKDOWN_2026-04-04.md
Normal 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
14
docs/technical/README.md
Normal 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/` 一起看,更容易判断先后顺序。
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user