整体架构
图模工坊由 5 个相互独立的服务/应用 组成,前端三端 + 后端两端,按业务边界拆分,可单机起,也可分布式部署。
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 29 30 31 32 33 34 35 36
| ┌──────────────────────────────────────┐ │ 终端用户 / 管理员 / 运营 │ └──────┬──────────────┬────────────┬───┘ │ H5 │ PC Web │ PC Web ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ pf-app │ │ pf-editor │ │ pf-manage │ │ Quasar H5 │ │ Quasar PC │ │ Naive UI │ │ 端口 8000 │ │ 端口 9000 │ │ 端口 9527 │ └──────┬───────┘ └──────┬───────┘ └──────┬──────┘ │ HTTP / Token │ HTTP / Token │ HTTP / Token └────────────────┼────────────────┘ ▼ ┌────────────────────────────┐ │ pf-service │ │ Bun + Elysia (TS) │ │ 端口 3000 │ │ ┌──────────────────────┐ │ │ │ Routes: │ │ │ │ /pf-manage/* │ │ │ │ /pf-editor/* │ │ │ │ /pf-app/* │ │ │ │ /shared/* │ │ │ ├──────────────────────┤ │ │ │ Jobs: │ │ │ │ orderTemplate- │ │ │ │ CompositionJob │ │ │ └──────────────────────┘ │ └──┬──────┬──────┬────────┬──┘ HTTP /detect │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │ pf-face-service │ │ MySQL │ │ Redis │ │ MinIO │ │ FastAPI/SCRFD │ │ 8.0 + │ │ 6.x │ │ S3 兼容 │ │ 端口 3010 │ │ Drizzle │ │ │ │ 对象存储 │ └─────────────────┘ └──────────┘ └──────────┘ └─────────────┘
|
组件职责矩阵
| 组件 |
角色 |
进程/技术 |
主要协议 |
默认端口 |
pf-app |
终端用户 H5:选模板、传图、编辑、提交 |
Quasar / Vue 3.5 |
HTTP |
8000 |
pf-editor |
模板编辑器:拖拽出版、编辑 sceneJson |
Quasar / Vue 3.5 + Leafer |
HTTP |
9000 |
pf-manage |
管理后台:用户/角色/订单/模板/审核 |
Naive UI / soybean-admin |
HTTP |
9527 |
pf-service |
后端中台:路由、鉴权、合成任务调度 |
Bun + Elysia + Drizzle |
HTTP / JWT |
3000 |
pf-face-service |
人脸框检测(避脸裁剪) |
Python 3.12 + FastAPI + InsightFace |
HTTP |
3010 |
| MySQL |
业务数据持久化 |
MySQL 8.0 |
TCP |
3306 |
| Redis |
缓存、合成任务状态 |
Redis 6.x |
TCP |
6379 |
| MinIO |
原始素材 / 模板预览 / 合成成品 |
S3 兼容 |
HTTP |
1280 |
后端路由分区
pf-service 按”调用方”做命名分区,避免不同端点鉴权策略与字段命名约定混在一起:
1 2 3 4 5
| src/routes/projects/ ├── pf-app/ # 用户端接口(orderTemplate 提交、订单查询...) ├── pf-editor/ # 编辑器接口(模板创建、模板素材、素材库读取...) ├── pf-manage/ # 管理后台接口(auth/users/roles/menus/orders/审核/相册管理...) └── shared/ # 跨端共用(OSS 上传、字体库、背景库、人脸检测代理)
|
后端入口在 apps/pf-service/src/index.ts,按顺序注册各端路由 + Swagger 文档(/docs)。CORS、统一错误响应(SERVICE_ERROR_CODE)、JWT 鉴权(@elysiajs/jwt + @elysiajs/bearer)、日志插件均挂载在应用层。
五条主数据流
1. 模板配置流(管理员/设计师)
1 2 3 4 5 6
| pf-editor 拖拽出版 → 序列化为 sceneJson(Leafer 节点树) → POST /pf-editor/templates → templates 表落库 → 关联 album_templates / category / tags → 上架后用户端 pf-app 可见
|
2. 素材库流(管理员/运营)
1 2 3 4 5 6
| pf-manage 上传背景 / 字体 → POST /shared/oss/upload ← S3 预签名 / 直传 MinIO → POST /shared/background-library → POST /shared/font-library → 全局 (userId=0) 或个人素材入库 → 用户端 / 编辑器侧均可拉取
|
3. 用户传图与提交流(终端用户)
1 2 3 4 5 6 7 8
| pf-app 选相册/模板 → 分片上传素材到 MinIO(前端裁剪/旋转后再传) → 可选:POST /shared/face/detect 获取人脸框 → 自动避脸裁剪 → 用户编辑文字、调整位置 → POST /pf-app/order-template-submissions body: { orderNo, items: [{ templateId, userUploads, userTexts, sceneJson }] } → 入 order_template_submissions / order_template_submission_items → 状态 = pending
|
4. 自动合成流(后台 Job)
1 2 3 4 5 6 7 8 9 10 11
| orderTemplateCompositionJob (每 N 秒轮询) → 取一条 status=pending 的 submission → 标记 processing → 遍历明细 item: 合并 sceneJson + userUploads + userTexts → 启动/复用 Puppeteer 实例 → 把场景渲染到 headless Chrome 页面 → 截图 / 导出 PNG/PDF → 上传 MinIO 得到 composedImageUrls → 全部成功 → submission.status = completed → 任一失败 → item.failReason 记录 / submission.status = failed
|
5. 审核与交付流(管理员)
1 2 3 4
| pf-manage 列表查看 status=completed 的 submission → 预览 composedImageUrls → 审核通过:标记可下载 / 推送至打印队列 → 审核驳回:回写状态 → 通知用户端 → 用户重新提交
|
端口与协议矩阵
| 源 |
目标 |
协议 |
端口 |
用途 |
| 用户浏览器 |
pf-app |
HTTP |
8000 |
H5 静态资源 |
| 管理员浏览器 |
pf-editor |
HTTP |
9000 |
编辑器静态资源 |
| 管理员浏览器 |
pf-manage |
HTTP |
9527 |
后台静态资源 |
| 三端前端 |
pf-service |
HTTP/JWT |
3000 |
业务 API |
| 任意 |
pf-service |
HTTP |
3000 |
/docs Swagger、/health |
| pf-service |
pf-face-service |
HTTP |
3010 |
POST /api/face/detect |
| pf-service |
MySQL |
TCP |
3306 |
Drizzle ORM |
| pf-service |
Redis |
TCP |
6379 |
缓存 / 状态 |
| pf-service / 前端 |
MinIO |
HTTP |
1280 |
素材 / 成品对象存储 |
生产环境请将 pf-service 与 pf-face-service 置于内网;前端三端通过反向代理(Nginx / Caddy)统一对外,参见 部署。
前端分层(pf-app / pf-editor)
两个 Quasar 应用源码结构高度对称,便于复用:
1 2 3 4 5 6 7 8 9 10
| src/ ├── pages/ # 路由页面(IndexPage、AlbumTemplatesPage、TemplateListPage...) ├── layouts/ # 布局 ├── components/ # 业务组件(ImageCropper, ImagePreview, TemplateCard, workshop/*) ├── composables/ # 组合式函数(编辑器 useXxx, fontPreload) ├── stores/ # Pinia(pinia-plugin-persistedstate 持久化) ├── api/ # 与 pf-service 的 HTTP 调用 ├── boot/ # Quasar boot 文件(i18n、axios、router 守卫) ├── i18n/ # 国际化 └── utils/ # 通用工具
|
渲染引擎是 Leafer 2.x——同一份 sceneJson 在 pf-editor 用于交互编辑,在 pf-app 用于预览,在 pf-service 用 Puppeteer 启动一个最小的 Leafer 页面用于合成。
资源解耦
各组件之间无强耦合,可按需替换:
| 想换什么 |
怎么做 |
| 对象存储 |
MinIO 换 S3 / OSS / COS,改 apps/pf-service/config.json 的 s3 段 |
| 数据库 |
Drizzle 抽象层支持 MySQL / Postgres / SQLite,改 db/mysql.ts 与 drizzle.config.ts |
| 人脸模型 |
替换 apps/pf-face-service/models/*.onnx,或换实现(保留 /api/face/detect 协议) |
| 合成引擎 |
把 utils/jobProcessor.ts 的 Puppeteer 路径换成 sharp / canvas / 远程 GPU 服务 |
| 渲染引擎 |
Leafer 可换 Konva / Fabric,只要前后端共用同一份 sceneJson 序列化协议 |
| UI 框架 |
移除 Quasar,复用 packages/* 共享层接入任意 Vue 应用 |
下一步