Files
MiragenFlow/docs/miragenflow-architecture-analysis.md

222 lines
13 KiB
Markdown
Raw Permalink 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.
# MiragenFlow 架构分析
## 1. 系统定位
MiragenFlow 是一个本地优先的 AI 创作工作台,并在服务端提供统一的任务网关、计费、渠道路由和管理能力。
- 用户端负责画布编排、图片/文本/音频创作、任务查看、资产管理和 WebDAV 同步。
- 管理台负责用户、渠道、模型产品、套餐、计费、审批、任务、存储、指标和审计。
- 服务端负责认证、目录、任务、余额、队列、供应商调用、对象落地、事件和后台作业。
- `packages/contracts` 维护浏览器和服务端共享的会话、任务、事件、上传对象等 TypeScript 合约。
当前系统不是“浏览器直接持有供应商 Key”的纯前端工具:计费任务默认通过 `/api/v1` 服务端网关执行。另一方面,画布项目和“我的资产”仍主要保存在浏览器本地,WebDAV 是可选同步层,不是平台自带云端项目库。
## 2. 运行时边界
```text
用户端 web/ ─┐
├─ shared contracts ─> server HTTP /api/v1
管理台 admin/ ┘ │
├─ Auth / CSRF / MFA / Scope
├─ Store + PersistenceRepository
├─ Task Dispatch Outbox + Queue
├─ Task Worker + Provider Adapter
├─ Staging / Object / Asset
├─ Task WebSocket Events
└─ Message / WebDAV / GC / Reconcile Workers
```
服务端启动时加载配置、迁移和持久化状态,恢复任务租约、消息 outbox、WebDAV 作业和排队任务,再启动 HTTP、任务 WebSocket 及周期性恢复/GC 作业。证据入口:`server/src/index.ts:13`。
## 3. 用户端架构
### 3.1 页面和启动
- `web/src/main.tsx:1` 组合 `AppProviders` 与 `RouterProvider`。
- `web/src/router.tsx:29` 集中注册公开页面、认证页面和受保护工作台路由。
- `AuthGuard` 在认证 store 完成 hydrate 前保持加载态,未登录则跳到登录页并保留 redirect。
- `ClientRootInit` 启动时并行恢复会话并加载公开模型目录。
### 3.2 状态与本地持久化
- `useAuthStore` 管理内存 access token、用户、余额、MFA challenge 和 refresh 恢复。
- `useCatalogStore` 缓存 `/api/v1/catalog/models` 的公开模型目录。
- `useCanvasStore` 通过 localForage 持久化项目,约 400ms 防抖写入。
- `useAssetStore` 持久化文本、图片和音频资产,并在删除时清理无引用媒体。
- `local-data-namespace` 以用户 ID 或 anonymous 区分本地命名空间,并用 epoch 防止切换竞态。
因此账号登录和服务端任务是云端边界,画布及本地资产不是。清理站点数据仍可能丢失未同步的本地内容。
### 3.3 API 与任务体验
`web/src/services/api/platform.ts:81` 是统一请求边界:
- 同源 `/api/v1` 请求带 `credentials: include`。
- access token 只放内存并以 Bearer 发送。
- Cookie 会话存在时,写请求附带 CSRF token。
- 401 时只刷新一次,失败后触发统一会话过期处理。
任务创建链路位于同文件 `runPlatformTask`:创建任务后优先订阅 `/api/v1/ws/tasks`;WebSocket 失败时以 250ms 轮询任务详情。任务页另以 5 秒可见刷新维护列表。
### 3.4 上传、对象和 WebDAV
- 小于等于 50MB 的图片可走 multipart;其他对象走预签名/complete 链路。
- 任务结果通过带认证的 `/api/v1/objects/*` 读取。
- `app-sync.ts` 在画布/资产 hydration 后合并远端 manifest,补齐媒体并上传变化。
- WebDAV 同步是用户主动配置的跨设备同步,不是多人协作或实时云存储。
## 4. 管理台架构
### 4.1 入口、状态和路由
- `admin/src/main.tsx:3` 组合 Redux `Provider`、BrowserRouter 和应用入口。
- Redux 只维护 `global` 与 `user` 两个 slice;业务列表不进入大型全局 store。
- `admin/src/router/index.ts:36` 为路由声明 `requiredScope`。
- 用户安全、财务、审计等页面分别需要 `admin:security`、`admin:finance`、`admin:audit`;其他页面默认 `admin:read`。
- `PermissionGate` 读取角色和 scope,`super_admin` 直接通过,其余不足权限时进入 `/403`。
### 4.2 请求与业务页
`admin/src/services/platform.ts:65` 的 `adminRequest` 统一处理:
- Bearer access token、管理员 CSRF Cookie、`credentials: include`。
- 非 GET 请求自动生成 Idempotency-Key。
- 401 只触发一次并发去重的 refresh。
- 错误对象保留 status、code、details 和 requestId。
当前大多数业务路由懒加载 `admin/src/pages/Business/index.tsx`。该页面根据 section 映射读取 `/api/v1/admin/*`,写操作统一处理 `If-Match`、幂等、刷新列表及 409/422/503。
管理台的“实时指标”并非 WebSocket 客户端:页面每 10 秒 GET `/api/v1/admin/ws/metrics`,服务端即时聚合订阅数、队列和事件积压。审计大板和详细日志都读取 `/api/v1/admin/audit-logs`。
## 5. HTTP、安全与权限
HTTP 入口在 `server/src/app/http.ts:1174`:
- 每个请求分配 requestId,并统一处理 CORS、body limit、健康和就绪检查。
- `/api/v1` 写请求先经过 CSRF 检查;纯 Bearer 且没有 refresh Cookie 的请求不强制双重提交。认证引导端点可以恢复缺少 CSRF Cookie 的旧 refresh 会话并补发缺失 Cookie,普通业务写请求仍受双提交保护。
- 用户 token 需要校验 JWT、session family、撤销状态和账号状态。
- 管理 token 使用独立 audience、角色和 scopes。
- 管理资源 scope 由 `adminRequiredScope` 统一映射,不依赖前端菜单作为安全边界。
- 非读取管理请求必须带合法 Idempotency-Key,并以 body fingerprint 防止同键异体重放。
密码使用 scrypt;JWT 使用 HS256 并包含 aud、scope、jti、sid、roles/scopes 等声明。登录策略还支持 CAPTCHA、TOTP 和 recovery code。
生产配置会强制检查 JWT secret、管理员密码、渠道加密密钥、PostgreSQL 和 Redis 配置;本地开发允许 memory/file persistence 与 memory queue。
## 6. 任务主链
### 6.1 创建与预留
`server/src/app/http.ts:751` 的任务创建先完成:
1. 校验公开模型、能力、输入引用、mask、数量、分辨率和参数。
2. 解析产品、价格、渠道组和套餐权益快照。
3. 计算预计金币并检查并发限制。
4. 在账本中把 available 移到 reserved。
5. 原子写入 task、幂等记录、task event、dispatch outbox 和队列信息。
6. 返回 `202` 与公开任务投影。
### 6.2 队列和租约
`server/src/infra/queue.ts` 定义统一队列接口。开发可使用内存队列;生产 Redis 适配器使用 sorted set、hash 和 Lua 原子 claim/renew/nack,并维护 dead-letter 集合。
PostgreSQL 模式使用 task dispatch outbox:事务提交后再由 dispatcher 以 `SKIP LOCKED` claim 并投递唯一 taskId,避免“数据库已提交但队列未写入”的双写问题。
### 6.3 Worker、重试和未知结果
`server/src/jobs/task-worker.ts:400`:
- Worker claim durable lease 后把 queued 改为 running,并周期性续租。
- 路由快照生成按渠道优先级、重试预算和断路器状态排列的 attempt plan。
- 每个 attempt 带平台幂等键,调用 provider adapter 后分类为 success、retryable、permanent 或 unknown。
- 可重试错误切换渠道;预算耗尽才失败并释放剩余预留。
- 网络超时、租约丢失或结果不确定不会直接重复请求,而是进入 unknown/reconciliation,防止供应商已完成但平台重复扣费。
- 续租失败会 fence 后续副作用,避免两个 Worker 同时结算同一任务。
### 6.4 输出与结算
成功输出先写 staging/object,再创建用户 asset 和 output 记录;结算按实际成功输出和价格快照扣除 reserved,释放多余预留。结算失败会进入 unknown,等待一致性恢复。
## 7. 供应商适配
`server/src/adapters/provider.ts` 是供应商边界:
- 平台显示模型 ID 与供应商请求模型 ID 分开解析,模型映射只在出站时生效。
- OpenAI 兼容图片适配器支持 `/images/generations`、`/images/edits`、多参考图、mask、参数和 idempotency header。
- 出站 URL 在生产要求 HTTPS 与公网 DNS;请求固定解析地址、禁用重定向,并限制超时和响应字节数。
- transport/timeout 归类为 unknown,而不是轻率判为 failed。
- admin 模型探测会明确请求 `/v1/models` 并返回 requestAttempted 与分类诊断。
设计文档中的 `@图片X` 文本重写、固定 1K/2K/4K 模型后缀和比例矩阵属于产品设计要求;当前源码已确认参考图数组、mask、分辨率模型映射和 OpenAI 兼容调用,但本次没有在服务端调用链中确认独立的 `@图片X -> 第X张图` 文本重写器。
## 8. 一致性、持久化与数据
### 8.1 Store 与事务
Store 聚合用户、会话、模型、渠道、任务、attempt、事件、余额 bucket、ledger、订单、审批、审计和 WebDAV 状态。
- 内存事务保存 snapshot,失败时回滚,提交后才发布缓冲事件。
- 文件适配器使用临时文件 + rename 原子替换,并以 0600 权限写入。
- PostgreSQL Repository 使用 revision/advisory lock、乐观冲突重试和领域行投影。
- task lease、dispatch outbox、message outbox 和 WebDAV job 都有独立 durable claim/renew/complete 接口。
### 8.2 余额与审批
账本使用 bucket slice 记录 reserve、settle、release、refund 和 adjustment。人工充值、退款及高风险调账先创建审批单;默认禁止申请人自批,执行时可要求管理员 TOTP,并把账本、订单、审批状态和审计放入同一事务。
支付 webhook 校验签名、金额、币种和唯一 eventId;订单、payment event、余额 bucket 和 ledger 原子提交。非生产 mock 支付与真实 adapter 路径明确分开。
### 8.3 审计
审计 helper 会脱敏凭证、provider 内部标识和路由快照,并用 previousHash/hash 形成链式校验。管理读审计列表本身也会写入 `audit.read`,因此审计读取是可追踪操作。
## 9. 事件、指标与后台作业
### 9.1 用户任务事件
`server/src/app/ws.ts` 只接受 `/api/v1/ws/tasks`:
- 校验同源与用户会话;生产拒绝 query access_token。
- 每连接最多 8 个任务订阅,20 秒 heartbeat。
- 订阅时先按 cursor 回放,再接收实时事件。
- 以 eventId 和每任务 sequence 去重。
- HTTP 事件与 WS 事件都经过 `publicTaskEvent` 白名单投影,隐藏 channel/provider 等敏感字段。
### 9.2 后台作业
服务端还运行消息 outbox、WebDAV sync/retention、staging GC、租约恢复、reservation 恢复和 unknown reconciliation。WebDAV 适配器校验公网 HTTPS、清理路径穿越,并使用 ETag、租约、指数退避和冲突副本。
## 10. 部署形态
| 环境 | 持久化 | 队列 | 外部依赖 |
| --- | --- | --- | --- |
| 本地开发 | memory/file | memory | 可使用本地 fixture,但不会自动创建展示渠道 |
| 生产 | PostgreSQL | Redis | 公网 HTTPS provider、真实支付/消息/WebDAV 配置 |
已确认的生产硬要求来自 `server/src/config.ts:76`。`system-diagrams.md` 提到可选 S3,但当前源码中实际确认的是本地 staging/object 处理;不能把外部 S3 视为已经实现。
## 11. 已确认事实与未确认边界
### 已确认
- 用户端、管理台、共享合约和服务端是四个明确代码边界。
- 任务采用余额预留、dispatch outbox、队列 lease、attempt、重试/断路器和 unknown 对账。
- 用户任务事件有 WS + cursor replay + HTTP fallback。
- 管理台实时指标为 HTTP 轮询。
- 画布与本地资产以 localForage/IndexedDB 为主,WebDAV 可选同步。
- 生产要求 PostgreSQL 与 Redis。
### 未确认或不应过度承诺
- 没有确认外部 S3 对象存储实现;当前应按本地 staging/object 理解。
- 没有确认设计文档中的 `@图片X` 服务端重写器已经落地。
- 没有确认真正的 3D/视频生成;现有范围以图片、文本、音频和 2D DOM/SVG 画布为主。
- AGENTS.md 提到的 Agent 对话 `threadId/turnId/itemId` 协议未在当前 `server/` 与 `packages/contracts/` 中找到实现。
- 多实例扩展仍依赖 PostgreSQL/Redis、租约 fencing 和外部服务的生产验证,不能以本地 fixture 代替。
## 12. 图示产物
- `docs/miragenflow-architecture.html`:全平台组件、边界、请求和异步主链。
- `docs/miragenflow-task-lifecycle.html`:任务状态主线、失败重试、unknown 保护和取消出口。
- 对应 JSON 规格位于同目录,可继续用 Archify 验证和生成。