整体架构
图模工坊由 7 个相互独立的服务/应用 组成:三个前端 + 三个后端服务 + 一个桌面客户端,按业务边界拆分,可单机起,也可分布式部署。合成渲染已从主服务解耦为独立的 pf-compose-service。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ┌──────────────────────────────────────────────────────┐ │ 终端用户(H5) │ 设计师(编辑器) │ 运营(后台/客户端) │ └────┬───────────────┬──────────────────┬───────────────┘ ▼ ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │ pf-app │ │ pf-editor │ │ pf-manage │ │ pf-download │ │ Quasar H5 │ │Quasar+Leafer│ │ Naive UI │ │ Wails 桌面端 │ │ :8000 │ │ :8100 │ │ :9527 │ │ (.exe) │ └────┬───────┘ └────┬───────┘ └────┬───────┘ └──────┬───────┘ │ HTTP/JWT/令牌 │ │ │ HTTP/JWT └────────────────┴────────────────┴────────────────┘ ▼ ┌────────────────────────────┐ │ pf-service │ │ Bun + Elysia (TS) :3000 │ │ /api/manage /api/editor │ │ /api/app /api/download │ │ Job: orderTemplate- │ │ CompositionJob │ └──┬────────┬────────┬───────┘ POST /api/compose│ detect│ │ ▼ ▼ ▼ ▼ ▼ ┌────────────────────┐ ┌───────────────┐ ┌────────┐ ┌───────┐ ┌────────┐ │ pf-compose-service │ │ pf-face-service│ │Postgres│ │ Redis │ │ MinIO │ │ Bun+Puppeteer :3030│ │ Rust/Axum :3010│ │ 16+ │ │ 6.x │ │ S3 兼容│ │ 多 Worker 进程池 │ │ SCRFD/ONNX │ │Drizzle │ │ │ │ :1280 │ └─────────┬──────────┘ └───────────────┘ └────────┘ └───────┘ └───┬────┘ └──── 渲染成品自行 PutObject 入库 ─────────────────────────┘
|
组件职责矩阵
开发期网关(Caddy)
本地开发用 tools/caddy/Caddyfile 起一个 Caddy 网关(PF_GATEWAY_PORT,默认 :9800)把三端 + API + 对象存储收敛到同源路径,避免跨域与端口记忆负担:
1 2 3 4 5 6 7
| :9800 ├── / → 302 重定向到 /manage/ ├── /manage/* → pf-manage 127.0.0.1:9527 ├── /editor/* → pf-editor 127.0.0.1:8100 ├── /app/* → pf-app 127.0.0.1:8000 ├── /api/* → pf-service 127.0.0.1:3000 └── /storage/* → MinIO 127.0.0.1:1280 (隐式映射 /pf-bucket)
|
前端各自的 publicPath / VITE_BASE_URL 已对齐为 /manage/、/editor/、/app/。生产环境由 Aliyun Nginx 承担同样的路径分发(见 部署)。
后端路由分区
pf-service 按”调用方”做前缀分区,避免不同端点鉴权策略与字段命名约定混在一起(注意:现有前缀是 /api/*,历史文档里的 /pf-manage、/shared 已不再使用):
1 2 3 4 5 6
| src/routes/ ├── pf-app/ # /api/app —— 用户端接口(订单提交、模板、资源;订单访问令牌鉴权) ├── pf-editor/ # /api/editor —— 编辑器接口(模板、合成预览 + 复用 shared) ├── pf-manage/ # /api/manage —— 管理后台接口(auth/user/role/menu/template/album/order/product...,JWT 鉴权) ├── pf-download/ # /api/download —— 下载客户端接口(订单/提交分页、成品下载、下载回执,JWT 鉴权) └── shared/ # OSS 预签名、字体库、背景库、人脸检测代理(被 editor / app 复用)
|
后端入口在 apps/pf-service/src/index.ts,按顺序挂载 cors → logger → 各端路由 → swagger(/docs)。JWT + Bearer 鉴权封装在 plugins/auth.ts 内,由需要鉴权的路由组各自 use;全局 onError 统一输出 { code, data, message, timestamp } 响应体。
六条主数据流
1. 模板配置流(管理员/设计师)
1 2 3 4 5 6
| pf-editor 拖拽出版 → 序列化为 sceneJson(Leafer 节点树,剔除瞬态节点) → POST /api/editor/templates → templates 表落库(含 tenantId 租户归属) → 关联 album_templates / templateType / group → 上架后用户端 pf-app 可见
|
2. 素材库流(管理员/运营)
1 2 3 4 5 6
| pf-manage / pf-editor 上传背景 / 字体 → GET /api/.../oss/upload-url ← 申请 S3 预签名(purpose 白名单校验) → PUT 直传 MinIO → POST /api/.../background-assets 或 /font-variants → 全局 (userId=0) 或个人/租户素材入库 → 用户端 / 编辑器侧均可拉取;缩略图走 imgproxy
|
3. 用户传图与提交流(终端用户)
1 2 3 4 5 6 7 8
| pf-app 选相册/模板 → 乐观占位 + 并发 3 直传 MinIO(前端裁剪/旋转后再传,失败单项重试) → 后台队列并发调 /api/app/face/detect 取人脸框 → 自动避脸裁剪 → 用户编辑文字、调整位置(Leafer 实时预览) → POST /api/app/order-templates/submission body: { orderNo, items: [{ templateId, userUploads, userTexts, sceneJson }] } → 入 order_template_submissions / order_template_submission_items(status=pending) → 订单 uploadStatus=submitted, composeStatus=pending
|
4. 自动合成流(后台 Job + 合成微服务)
1 2 3 4 5 6 7 8 9
| orderTemplateCompositionJob (每 10s 轮询) → FOR UPDATE SKIP LOCKED 抢占最多 3 条 pending/可重试/僵尸明细 → processing → 并发 3 处理明细: buildPreviewSceneJson(sceneJson + uploads + texts + 订单变量) → POST http://pf-compose-service/api/compose { sceneJson, storage, options } → 合成服务:Puppeteer Worker 渲染 Leafer 页面 → 导出图 → 自行上传 MinIO → 返回 { fileKey, publicUrl, 各阶段耗时 } → 写回 item.composedImageUrls;失败 → attemptCount++, nextRetryAt=now+2^n s(≥3 次永久失败) → 聚合:全部 completed → submission/order 状态 completed;否则 processing/failed
|
5. 成品下载流(运营 / 桌面客户端)
1 2 3 4 5 6 7
| pf-download(Windows 桌面)登录 pf-service → GET /api/download/orders/page · /submissions/page(可按 composeStatus / hasDownloaded 过滤) → GET /api/download/submissions/:id/images 列出可下载成品 → GET /api/download/images/:sid/:iid/:idx/file 流式下载(attachment) → POST /api/download/submissions/:id/receipt 标记首次下载 → 回写 orders.hasDownloaded / firstDownloadedAt、submissions.downloadedAt (下载状态与合成状态解耦:时间戳/布尔位,非 Job 维护)
|
6. 审核与交付流(管理员)
1 2 3 4
| pf-manage 列表查看 composeStatus=completed 的提交 → 预览 composedImageUrls(缩略图 imgproxy 懒加载) → 审核通过 / 触发重合成(composeStatus 回退 pending) → 驳回 → 通知用户端 → 用户重新提交
|
图片管道(imgproxy + 边缘缓存)
所有列表/网格/瀑布流缩略图统一走同源 /storage/<key>?w=<宽>:Nginx 把带 ?w= 的请求改写到 imgproxy 生成动态 WebP,并落两块磁盘缓存区(pf_img_cache 缩略图 / pf_raw_cache 原图,均 30 天)。前端三端各有一份 resolveOssAssetUrl(url, width) 负责拼接。详见 传图与裁剪管线 与 部署。
资源解耦
各组件之间无强耦合,可按需替换:
下一步