实际生产拓扑(本仓库采用)

deploy/README.md 记录的真实架构是:前端静态托管在阿里云 Nginx + 后端跑在本地 WSL 的 Docker + FRP 内网穿透双端联通。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
            公网用户
│ https://pf.wangijun.com
▼
┌──────────────────────────────────────────┐
│ 阿里云 ECS 123.57.205.60 · Nginx (TLS) │
│ /manage/ /editor/ /app/ → 静态 SPA │
│ /api/ → FRP :12016 → WSL pf-service │
│ /storage/ → FRP :12002 → WSL MinIO │
│ /imgproxy/→ FRP :12020 → WSL imgproxy │
│ 边缘磁盘缓存 pf_img_cache / pf_raw_cache │
└───────────────────┬────────────────────────┘
│ FRP 隧道 (:7000)
▼
┌──────────────────────────────────────────┐
│ 本地 WSL Ubuntu-24.04 /data/pf-factory │
│ docker compose (pf-network): │
│ pf-service :3001 │
│ pf-compose-service :3031→3030 │
│ pf-face-service :3011 │
│ pf-imgproxy :3020→8080 │
│ pf-frpc (host 网络) │
│ 宿主机服务(非容器,host.docker.internal):│
│ PostgreSQL :5432 · Redis :6379 · MinIO :1280 │
└────────────────────────────────────────────┘

要点:

  • 数据库是 PostgreSQL(库名 printing-factory),不是 MySQL;且 PostgreSQL / Redis / MinIO 跑在 WSL 宿主机、不在 compose 里,容器通过 host.docker.internal(extra_hosts: host.docker.internal:host-gateway)访问。
  • 所有容器 TZ=Asia/Shanghai;pf-service/compose/face 各自 bind-mount ./<svc>/logs → /app/logs 落盘到 WSL 宿主。

docker-compose 服务矩阵

deploy/docker-compose.yml(单一文件,无 override/env 变体):

服务 (container) 镜像 / 构建 端口 (host:container) 说明
pf-service build ./pf-service 3001:3001 主 API,depends_on face+compose
pf-compose-service build ./pf-compose-service 3031:3030 Bun + Puppeteer/Chromium
pf-face-service build ./pf-face-service 3011:3011 Rust 单二进制
pf-imgproxy darthsim/imgproxy:latest 3020:8080 动态 WebP 缩略图
pf-frpc fatedier/frpc:v0.61.1 host 网络 内网穿透客户端

各后端镜像基础层:pf-service / pf-face-service 用 debian:trixie-slim(拷预编译二进制);pf-compose-service 用 ghcr.io/puppeteer/puppeteer:24.4.0 + 中日韩字体。

pf_img_cache / pf_raw_cache 不是 Docker 卷,而是 Nginx 的 proxy_cache_path 磁盘缓存区(见下)。

imgproxy + Nginx 边缘缓存

imgproxy(darthsim/imgproxy:latest,host 3020→8080)关键 env:IMGPROXY_ENFORCE_WEBP=true、IMGPROXY_AUTO_ROTATE=true、IMGPROXY_QUALITY=80、IMGPROXY_MAX_SRC_RESOLUTION=50(MP)、IMGPROXY_READ_TIMEOUT=15、IMGPROXY_PROCESSING_TIMEOUT=10。

⚠️ imgproxy 当前未配置签名(无 IMGPROXY_KEY/SALT,Nginx 走 /imgproxy/unsafe/)——任何人可请求任意变换。若对外暴露,建议开启签名。

Nginx(源文件 deploy/aliyun-nginx.conf → 部署到 ECS /etc/nginx/conf.d/printing-factory.conf,域名 pf.wangijun.com,HTTP→HTTPS 301,client_max_body_size 50M):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 两块磁盘缓存区,均 levels=1:2, inactive=30d, use_temp_path=off
proxy_cache_path /var/cache/nginx/pf_img_cache keys_zone=pf_img_cache:20m max_size=10g; # 缩略图
proxy_cache_path /var/cache/nginx/pf_raw_cache keys_zone=pf_raw_cache:20m max_size=20g; # 原图

# 缩略图请求改写到 imgproxy 生成 WebP
# /storage/(.*)?w=<n> → /imgproxy/unsafe/resize:fit:<w>:0/plain/http://host.docker.internal:1280/pf-bucket/$1@webp

location /storage/ { # 原图(proxy_cache pf_raw_cache)
proxy_pass http://127.0.0.1:12002/pf-bucket/;
proxy_set_header Host host.docker.internal:1280; # 修 AWS4 签名 403
proxy_cache pf_raw_cache; proxy_cache_key $uri;
proxy_cache_valid 200 30d; proxy_cache_valid 404 1m;
add_header Cache-Control "public, max-age=2592000, immutable";
add_header X-Cache-Status $upstream_cache_status; # 仅缓存 200,不缓存 206
}
location /imgproxy/ { # 缩略图(proxy_cache pf_img_cache)
proxy_pass http://127.0.0.1:12020/;
proxy_cache pf_img_cache; proxy_cache_key $uri;
proxy_cache_valid 200 30d; proxy_cache_valid 404 1m;
}
location /api/ { proxy_pass http://127.0.0.1:12016/api/; } # websocket + 600s 超时
# SPA: /manage/ /editor/ /app/ alias /usr/app/webpage/pf-*;/ → 302 /manage/

原图与缩略图分两块缓存区,避免 2–10MB 原图挤掉缩略图。

FRP 内网穿透

deploy/frpc.toml:server 123.57.205.60:7000;隧道 pf-service-api 3001→12016、pf-imgproxy 3020→12020,MinIO 1280→12002。Nginx 通过这些 127.0.0.1 端口回源到 WSL 容器。

部署脚本

全部在 deploy/(PowerShell),配套沉淀了部署 Skill(.agents/skills/deploy-printing-factory/SKILL.md):

脚本 作用
deploy.ps1 统一入口,-Target all/frontend/backend/<svc>/nginx/frpc(可组合)+ -VerifyOnly;Run-HealthCheck 探 7 个端点
build-and-sync-wsl.ps1 后端编译 + 同步 WSL + docker compose up。pf-service:bun build --compile --target=bun-linux-x64;compose:bun run build + Bun 编译(含 public/);face(Rust):docker run rust:latest cargo build --release 后拷 target/release + models/
deploy-frontend.ps1 三前端 bun run build → tar|ssh 推到 ECS /usr/app/webpage(pf-manage 用 dist,pf-editor/pf-app 用 dist/spa)
aliyun-nginx.conf / frpc.toml / minio-cors.json Nginx / FRP / MinIO CORS 配置

后端二进制在 WSL 内用容器交叉编译,镜像本身不含编译链——启动快、镜像小。

通用部署要点(自建其它拓扑时)

若不用 WSL+FRP,也可把后端三服务 + imgproxy 全放一台机器 + 反向代理:

  • API 组 vs 合成 Worker:pf-service 可多实例并行(FOR UPDATE SKIP LOCKED 天然支持);把 jobs.composition.enabled=false 的实例做纯 API 组、true 的做调度组。渲染压力已在 pf-compose-service,按需扩其副本/Worker 数。
  • 合成服务:ghcr.io/puppeteer/puppeteer 基础镜像已带 Chromium 系统库;给它单独 cgroup 限内存,maxUsesPerWorker 控制 Browser 重启防泄漏。
  • 人脸服务:Rust 单二进制 + libgomp1/libstdc++6,/health 探活。
  • 对象存储:生产建议多节点 MinIO(Erasure Code)或直接 S3/OSS/COS,改各服务 s3 段。
  • 数据库:PostgreSQL 主从(读多写少);order_template_submission_items 增长最快,按 createdAt 归档。备份 pg_dump + WAL 归档。
  • Redis:Cluster / Sentinel,别用单点。

静态前端发布

1
2
3
bun run --filter @wangijun/pf-app build      # dist/spa
bun run --filter @wangijun/pf-editor build # dist/spa
bun run --filter @wangijun/pf-manage build # dist

注意 SPA base:pf-manage=/manage/、pf-editor=/editor/、pf-app=/app/,Nginx try_files $uri /index.html;后端地址通过各自 .env 注入。

监控与告警

指标 来源 参考阈值
HTTP 5xx 率 Nginx > 1% 持续 5min
API P95 延迟 pf-service > 1s
合成 pending 队列长度 DB query > 100 持续 10min
合成失败率 / 单条耗时 P95 DB / compose 日志 > 5% / > 30s
compose /health workers.waiting 探针 持续 > 0
face /health 探针 非 ok 任何时长
MinIO 可用空间 exporter < 20%
Nginx X-Cache-Status HIT 率 日志 缩略图应高命中

安全 checklist

  • pf-service / compose / face 只暴露给内部代理(FRP/内网)
  • imgproxy 生产开启签名(IMGPROXY_KEY/SALT)
  • MinIO 私有桶 + 预签名 URL,禁止公开列举
  • JWT/S3/FRP 密钥放 secret manager,不入仓(当前 config.json/frpc.toml 有明文默认值,仅供开发)
  • CORS 白名单生产收紧
  • 上传白名单:MIME + 文件头双重校验
  • 合成服务只渲染受信内嵌页,不渲染外网任意 URL

下一步

站内搜索

没有找到内容!