定位

pf-service 是图模工坊的单体后端中台——所有前端(pf-app / pf-editor / pf-manage)的业务请求都经过它;它也负责后台合成 Job 的调度、对外暴露 OpenAPI 文档。

  • 运行时:Bun 1.x(启动比 Node 快、对 TS 零编译、原生 Web API)
  • 框架:Elysia 1.4(路由 + 校验 + 中间件,TypeBox 驱动)
  • ORM:Drizzle 0.45(类型安全 SQL builder)
  • 数据库:MySQL 8.0
  • 缓存:Redis 6.x
  • 对象存储:MinIO(S3 协议)
  • 鉴权:JWT (@elysiajs/jwt) + Bearer Token (@elysiajs/bearer)
  • 端口:开发环境 3000

源码结构

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
apps/pf-service/
├── config.json # 运行时配置(端口/DB/Redis/S3/face)
├── drizzle.config.ts # Drizzle Kit 配置
├── drizzle/ # 迁移 SQL(生成产物)
├── test/ # bun test 用例
└── src/
├── index.ts # 启动入口(Elysia 应用装配)
├── config/ # 配置加载
├── db/
│ ├── schema.ts # Drizzle Schema(20+ 张表,~300 行)
│ ├── mysql.ts # 连接池
│ ├── redis.ts # Redis 客户端
│ ├── s3.ts # S3/MinIO 客户端
│ └── seed.ts # 种子数据
├── plugins/
│ ├── auth.ts # JWT + 鉴权中间件
│ └── logger.ts # 日志中间件
├── routes/
│ └── projects/
│ ├── pf-app/ # 用户端接口
│ ├── pf-editor/ # 编辑器接口
│ ├── pf-manage/ # 后台接口
│ └── shared/ # 跨端共用
├── jobs/
│ └── orderTemplateCompositionJob.ts # 合成轮询 Job
└── utils/
├── exportImg.ts # 图片导出
├── jobProcessor.ts # 合成主流程
├── puppeteerEventBus.ts # Puppeteer 进程通讯
└── serviceResponse.ts # 统一响应封装

启动流程

src/index.ts 装配应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Elysia()
.use(cors()) # 跨域
.use(logger) # 日志
.use(pfManageRoutes) # /pf-manage/*
.use(pfEditorRoutes) # /pf-editor/*
.use(sharedRoutes) # /shared/*
.use(pfAppRoutes) # /pf-app/*
.use(swagger({ path: "/docs" }))
.get("/", () => "图模工坊管理系统 API")
.get("/health", ...)
.onError(...) # 全局错误兜底
.listen(config.server.port)

# 启动后
startOrderTemplateCompositionJob(intervalMs)

启动日志:

1
2
3
4
5
6
7
========================================
🚀 图模工坊管理系统 API 服务已启动
========================================
📍 服务地址: http://localhost:3000
📚 API 文档: http://localhost:3000/docs
❤️ 健康检查: http://localhost:3000/health
========================================

路由分区

后端路由按”调用方”分四区,避免不同前端的字段约定、鉴权策略串味:

前缀 调用方 文件位置
/pf-app/* 终端用户 H5 routes/projects/pf-app/
/pf-editor/* 模板编辑器 routes/projects/pf-editor/
/pf-manage/* 管理后台 routes/projects/pf-manage/
/shared/* 跨端共用(OSS / 字体 / 背景 / face) routes/projects/shared/
/docs Swagger UI 框架自带
/health 探活 入口

鉴权

JWT 中间件 plugins/auth.ts

  • 登录接口下发 accessToken(短 TTL)+ refreshToken(长 TTL)
  • Bearer Token 解析(@elysiajs/bearer
  • 角色 / 权限校验在路由层 derive,不通过则返回 401/403
  • 用户/角色/菜单表与 soybean-admin 标准结构对齐,方便后台直接接入

统一响应

utils/serviceResponse.ts 暴露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const SERVICE_SUCCESS_CODE = "0000";
export const SERVICE_ERROR_CODE = {
notFound: "0404",
internalError: "0500",
// ...按模块扩展
};

export const success = <T>(data: T, message = "OK") => ({
code: SERVICE_SUCCESS_CODE, data, message,
});

export const failure = (code: string, message: string) => ({
code, data: null, message,
});

全局 onError 把抛出的 Error 转成结构化响应;NOT_FOUND 是约定常量。所有响应都附 timestamp

数据访问

1
2
3
4
5
6
7
8
import { db } from "../db/mysql";
import { templates } from "../db/schema";
import { and, eq, isNull } from "drizzle-orm";

const rows = await db
.select()
.from(templates)
.where(and(eq(templates.status, "normal"), isNull(templates.deletedAt)));
  • 软删强约束:所有查询带 isNull(deletedAt)
  • 事务:await db.transaction(async (tx) => { ... })
  • 迁移:bun db:generate 生成 SQL,bun db:push 同步(dev)/ 手动审过迁移(prod)

后台 Job:自动合成

jobs/orderTemplateCompositionJob.ts 定时轮询 order_template_submissions

  • intervalMs(默认 10s,可在 config.server.jobs.composition.intervalMs 调整)跑一轮
  • 通过内部锁 isRunning 防止并发重入
  • 单条 submission 进入处理后整体加事务,明细级失败标记 failReason
  • 实际渲染走 utils/jobProcessor.ts → Puppeteer

完整机制见 自动合成流水线

对外集成

  • MinIOdb/s3.ts 暴露上传/预签名/读取;前端直传走预签名 URL,后端合成产物走服务端 PutObject
  • Redis:缓存热点配置(字体/分类)、Job 状态、Puppeteer 进程信号
  • face-serviceroutes/projects/shared/faceRoutes.ts 是个 thin proxy,统一前端入口;后端合成时也内部调用同一服务
  • OpenAPI@elysiajs/swagger 自动从 Elysia 路由 schema 生成 /docs

开发指令

1
2
3
4
5
6
7
8
9
10
cd apps/pf-service

bun dev # bun --watch src/index.ts
bun lint # oxlint
bun typecheck # tsc --noEmit
bun fmt # oxfmt
bun db:generate # Drizzle 生成迁移
bun db:push # Drizzle 同步 schema(dev)
bun db:studio # 可视化
bun test # bun 内置测试

性能与限制

维度 现状 后续
启动 < 1s(Bun + 单进程)
单机并发 取决于 MySQL / Puppeteer 实例数 多进程 + 任务队列
合成吞吐 ~ Puppeteer 实例数 × 张/秒 进程池 + GPU 渲染
文件上传 走 MinIO 预签名直传,绕开 Node 单点
鉴权 JWT 单点,refresh token 续期 引入 SSO

下一步