定位

pf-compose-service 是图模工坊的独立图片合成微服务——把 sceneJson 用 Puppeteer 无头浏览器渲染成图片,再自行上传 MinIO,只回传 URL。它从 pf-service 解耦而来(2809bf4),让主服务不必背 Chromium、可轻量水平扩展。

  • 运行时:Bun(import.meta.main 入口)
  • HTTP:裸 Bun.serve(无 Elysia,手写路由 if 链)
  • 渲染:Puppeteer ^24.4.0 + Leafer / leafer-ui 2.2.9
  • 条码/二维码:@sa/code-renderer|日志:winston + daily-rotate
  • 端口:3030(生产 Docker 内同 3030,宿主映射 3031)

源码结构

1
2
3
4
5
6
7
8
9
10
11
12
apps/pf-compose-service/
├── config.json # 端口 / puppeteer / s3 / logging
├── .puppeteerrc.cjs # Chromium 下载/缓存配置
├── public/renderer/ # 内嵌 Leafer 渲染页(index.html + runner.js)
└── src/
├── index.ts # Bun.serve 入口 + 路由 + 优雅关闭
├── config/index.ts # 配置加载(CONFIG_PATH 可覆盖)
├── db/s3.ts # Bun S3Client
├── routes/ # composeRoutes / healthRoutes / storageRoutes
├── services/ # compositionService(渲染+上传编排)/ ossStorageService
├── utils/ # puppeteerLauncher / puppeteerPagePool / http / baseLogger
└── renderer/ # runner.ts(页内 renderScene)/ build.ts / fontPreload / imagePreload

HTTP 接口

所有 JSON 响应统一 { code, message, data, timestamp }(src/utils/http.ts),成功码 "0000",CORS *。

方法/路径 说明
GET / 存活信息
GET /health 健康 + Worker 池统计
POST /api/compose 合成入口(注意是 /api/compose)
GET|HEAD /storage/* S3 代理,流式回传 MinIO 对象
GET /renderer/* 内嵌 Leafer 渲染页/打包产物
GET|HEAD /img/*、/editor/img/* public/ 静态图

POST /api/compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 请求
{
"sceneJson": { "children": [ ... ] }, // 必填,非对象则 code=4000
"storage": { "purpose": "composition", "tenantId": 1, "fileName": "...", "extension": "png" },
"options": { "format": "png", "pixelRatio": 1, "timeoutMs": 30000 }
}

// 成功
{
"code": "0000", "message": "...",
"data": {
"fileKey": "composition/2026/09/27/xxx.png",
"publicUrl": "/storage/composition/2026/09/27/xxx.png",
"renderTimeMs": 380, "uploadTimeMs": 120, "totalTimeMs": 510
},
"timestamp": 1700000000000
}
  • 渲染产物由本服务自行 PutObject 入库 MinIO(ossStorageService.uploadBufferToS3,Bun S3Client),对象键 [tenant-<id>/]<purpose>/<yyyy/mm/dd>/<baseName>.<ext>。
  • publicUrl 由 config.s3.publicUrl(/storage)拼出——成品图仍通过本服务的 GET /storage/* 代理回读。
  • 合成失败以 HTTP 200 + code="5001" 返回(调用方按 code 判成败)。

Worker 进程池

核心在 src/utils/puppeteerPagePool.ts。它不是 Bun Worker 线程,也不是子进程,而是一池独立的完整 Chromium 浏览器实例(Puppeteer 启动),每个实例独占一个 Page:

  • BrowserWorker(别名 PageWorker):持有一个 Browser + 一个 Page + 独立 userDataDir,跟踪 usesCount / maxUses / isHealthy / isBusy。render(sceneJson, options) 调页内 window.renderScene(json, {pixelRatio, format}),与 timeoutMs(默认 30000)竞速,拿到 data:image/... base64 后解码为 Buffer 返回;出错即标记不健康。
  • PuppeteerBrowserPool(别名 PuppeteerPagePool):维护 idleWorkers[]、activeWorkers、pendingWaiters FIFO 队列。
    • warmUp() 并行预热到 workers 个(默认 3)
    • acquire(timeoutMs=60000):复用空闲 → 未达上限则新建 → 否则排队(超时 reject)
    • release() 归还或交给等待者,低于目标时触发再预热
    • withWorker(fn) acquire/release 包裹(抛错标不健康)
    • 每 Worker 一个唯一 userDataDir(<cwd>/.runtime/puppeteer/worker-...),视口 1920×1080,导航到 renderUrl(networkidle0)后等待 window.renderScene 与 window.__LEAFER_READY__ === true

src/utils/puppeteerLauncher.ts 负责 headless 启动参数(--no-sandbox、--disable-dev-shm-usage、--disable-gpu 等),并有相当完善的僵尸进程恢复:按 service-<port>-<workerId> 的运行锁、browser-state.json 记录 pid/wsEndpoint/userDataDir、杀进程前校验命令行、隔离不安全状态。

内嵌 Leafer 渲染页

public/renderer/index.html 极简(一个全屏 #leafer-view + runner.js);runner.js 是 src/renderer/runner.ts 的 Bun 打包产物(IIFE、browser target),在页内建一个 preview 模式的 Leafer App,暴露 window.renderScene / resetScene / initData / app / __LEAFER_READY__。

renderScene 的处理:把 data.children(或 [data])当作 Leafer IUIInputData[],重置画布 → 预加载所有图片 URL(collectImageUrls 递归 fill/url/children)→ 注入节点树 → 用 @sa/code-renderer 刷新条码/二维码节点 → 等字体/图片/视图完成 → app.export(format, {pixelRatio}) 返回 data URL。

配置(config.json)

1
2
3
4
5
6
7
{
"server": { "port": 3030, "workerId": 1 },
"logging": { "dir": "./logs", "level": "info", "maxSize": "20m", "maxFiles": "14d" },
"puppeteer":{ "workers": 3, "maxUsesPerWorker": 100, "renderTimeoutMs": 30000 },
"s3": { "accessKeyId": "minio", "secretAccessKey": "passwdminio", "bucket": "pf-bucket",
"endpoint": "http://localhost:1280", "publicUrl": "/storage" }
}

CONFIG_PATH / PF_COMPOSE_SERVICE_PORT 环境变量可覆盖;puppeteer.renderUrl 为可选键(默认 http://127.0.0.1:<port>/renderer/index.html)。

⚠️ S3 凭据在 config.json 内明文提交,属本地/开发默认值,生产请用配置注入覆盖。

健康检查

1
2
3
4
5
6
GET /health →
{ "code": "0000", "message": "服务正常",
"data": { "status": "ready",
"workers": { "idle": 3, "active": 0, "creating": 0, "total": 3, "waiting": 0 },
"uptimeSeconds": 1234 },
"timestamp": 1700000000000 }

pf-service 侧 checkComposeHealth() 以 code==="0000" && data.status==="ready" 判就绪。

开发与部署

1
2
3
bun dev:compose                        # 根目录:bun run --filter @wangijun/pf-compose-service dev
bun run --filter @wangijun/pf-compose-service build # 打包渲染产物
bun compose:install-browser # 安装 Puppeteer 用 Chromium

生产镜像基于 ghcr.io/puppeteer/puppeteer:24.4.0,额外装中日韩字体(noto-cjk / wqy-zenhei / wqy-microhei)并指向系统 Chrome,详见 部署。

下一步

站内搜索

没有找到内容!