# 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 验证和生成。