整体架构

图模工坊由 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 入库 ─────────────────────────┘

组件职责矩阵

组件 角色 进程/技术 主要协议 默认端口
pf-app 终端用户 H5:选模板、传图、编辑、提交 Quasar 2.23 / Vue 3.5 + MD3 HTTP 8000
pf-editor 模板编辑器:拖拽出版、编辑 sceneJson Quasar 2.23 + Leafer 2.x HTTP 8100
pf-manage 管理后台:用户/角色/订单/模板/相册/商品 Naive UI 2.44 / soybean-admin HTTP 9527
pf-download 下载客户端:批量下载成品、排队重试 Wails + Go 1.25 + Quasar HTTP 桌面 .exe
pf-service 后端中台:路由、鉴权、合成调度、下载 API Bun + Elysia 1.4 + Drizzle HTTP / JWT 3000
pf-compose-service 图片合成:Leafer 离屏渲染 + 入库 Bun + Puppeteer 24 HTTP 3030
pf-face-service 人脸框检测(避脸裁剪) Rust + Axum 0.8 + ORT 2 HTTP 3010
PostgreSQL 业务数据持久化 PostgreSQL 16+ TCP 5432
Redis 缓存、会话 Redis 6.x TCP 6379
MinIO 原始素材 / 模板预览 / 合成成品 S3 兼容 HTTP 1280

开发期网关(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) 负责拼接。详见 传图与裁剪管线 与 部署。

资源解耦

各组件之间无强耦合,可按需替换:

想换什么 怎么做
对象存储 MinIO 换 S3 / OSS / COS,改 pf-service 与 pf-compose-service 的 s3 配置段
数据库 Drizzle 抽象层支持 Postgres / MySQL / SQLite,改 db/postgres.ts 与 drizzle.config.ts
人脸模型 替换 apps/pf-face-service/models/*.onnx,或换实现(保留 /api/face/detect 协议)
合成引擎 合成已是独立服务,可整体替换为 sharp / canvas / 远程 GPU,只要实现 POST /api/compose 契约
渲染引擎 Leafer 可换 Konva / Fabric,只要前后端共用同一份 sceneJson 序列化协议
下载端 pf-download 是纯消费 /api/download/* 的客户端,可换 Web 或脚本实现

下一步

站内搜索

没有找到内容!