BioF3 在线绘图服务 — 架构设计文档
本文档定义 BioF3 在线绘图工具(plot.biof3.com)的技术架构,作为下一阶段独立项目的启动参考。
1. 产品定位
| 维度 | 描述 |
|---|---|
| 目标用户 | 生物信息学研究者、研究生、临床生信分析师 |
| 核心价值 | 上传数据 → 选择图类型 → 调参数 → 一键出 SCI 级图 |
| 所属项目 | OmicsHub(已有项目,单细胞云平台),绘图工具作为新增模块 |
| 与 BioF3 的关系 | FigCode 页面的"在线绘图"按钮跳转到 OmicsHub 绘图模块 |
| 访问方式 | http://<服务器IP>:<端口>/bindplot(OmicsHub 内的路由) |
2. 功能规划
Phase 1(MVP)
- 支持 10 种核心图类型(火山图、热图、PCA、富集 dotplot、生存曲线等)
- 用户上传 CSV/TSV → 选择图类型 → 调参数 → 预览 → 下载 PNG/PDF
- 无需注册即可使用(匿名)
- 单次请求,无状态
Phase 2
- 用户注册 + 历史记录
- 更多图类型(30+)
- 批量出图(一次上传多个文件)
- 自定义配色方案保存
Phase 3
- 流程性工具(差异分析 pipeline、富集分析 pipeline)
- 多步骤 workflow(上传 → QC → 分析 → 出图)
- API 接口(供第三方调用)
- 团队协作功能
3. 技术架构
整体架构:前后端分离
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ plot.biof3.com (React / Next.js 前端) │
└────────────────────────┬────────────────────────────────┘
│ HTTPS API
▼
┌─────────────────────────────────────────────────────────┐
│ API Gateway (Nginx) │
│ - 限流 / 认证 / CORS / 文件大小限制 │
└────────────┬───────────────────────────────┬────────────┘
│ │
▼ ▼
┌────────────────────────┐ ┌──────────────────────────┐
│ 计算服务 (R Worker) │ │ 计算服务 (Python Worker) │
│ - Plumber API │ │ - FastAPI │
│ - ggplot2 / pheatmap │ │ - matplotlib / seaborn │
│ - 容器化 (Docker) │ │ - 容器化 (Docker) │
└────────────────────────┘ └──────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ 共享存储 / 缓存 │
│ - Redis(任务队列 + 结果缓存) │
│ - MinIO / OSS(文件存储:上传数据 + 生成图片) │
└─────────────────────────────────────────────────────────┘
前端
| 选择 | 理由 |
|---|---|
| Next.js 14+ (App Router) | SSR/SSG 混合、API Routes 可做 BFF、React 生态 |
| TypeScript | 类型安全 |
| Tailwind CSS | 快速 UI 开发,和 BioF3 配色统一 |
| Zustand | 轻量状态管理 |
| React Query | 异步请求管理 + 缓存 |
后端(API 层 + 计算层分离)
核心思路:API 层用专业后端框架,R 只作为计算引擎被调用。
前端 → API 层(Node.js / Go / Python)→ 任务队列 → R 执行器(子进程)
API 层(选一)
| 方案 | 语言 | 优势 | 适用 |
|---|---|---|---|
| NestJS | TypeScript | 和前端同语言 、装饰器路由、模块化、生态丰富 | 团队熟 TS |
| Go (Gin / Fiber) | Go | 性能极高、并发原生、二进制部署简单 | 追求性能 |
| FastAPI | Python | 自动文档、类型校验、async 原生 | 团队熟 Python |
推荐:NestJS(和 Next.js 前端同语言栈,减少上下文切换;TypeORM 做数据库;Bull 做任务队列都是 Node 生态原生)
R 执行器(计算层)
R 不再暴露 HTTP 接口,而是作为子进程被 API 层调用:
API 收到请求 → 参数校验 → 写入临时 JSON 参数文件
→ spawn("Rscript", ["plots/volcano.R", "--params", "/tmp/xxx.json"])
→ R 读参数 → ggplot2 出图 → 写 PNG/PDF 到指定路径
→ API 读取结果文件 → 返回给前端
或者用 队列模式(高并发时):
API → Redis Bull Queue → Worker 进程 → Rscript 子进程 → 结果回写
这样 R 的职责非常纯粹:
- 接收一个 JSON 参数文件
- 读取用户数据文件
- 执行 ggplot2 代码
- 输出图片到指定路径
- 退出
不需要 Plumber、不需要 R 做 HTTP、不需要 R 处理认证/限流/错误。
架构图(更新版)
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ plot.biof3.com (Next.js 前端) │
└────────────────────────┬────────────────────────────────┘
│ HTTPS
▼
┌─────────────────────────────────────────────────────────┐
│ API 服务 (NestJS / Go / FastAPI) │
│ - 路由 / 认证 / 限流 / 参数校验 / 文件管理 │
│ - 任务调度(Bull Queue → Redis) │
│ - 结果轮询 / WebSocket 推送 │
└────────────────────────┬────────────────────────────────┘
│ 任务队列
▼
┌─────────────────────────────────────────────────────────┐
│ Worker 进程池 │
│ - 从队列取任务 │
│ - spawn Rscript 子进程执行绘图 │
│ - 超时 kill / 错误捕获 / 结果上传 │
│ - 每个 Worker 可并行跑多个 R 子进程(受 CPU 限制) │
└────────────────────────┬────────────────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ R 执行环境 │ │ Python 执行环境 │
│ (Docker 容器) │ │ (Docker 容器,备选) │
│ - ggplot2 │ │ - matplotlib │
│ - pheatmap │ │ - seaborn │
│ - 只读数据文件 │ │ - 只读数据文件 │
│ - 只写输出目录 │ │ - 只写输出目录 │
└──────────────────┘ └──────────────────────┘
R 脚本的标准接口
每个图类型对应一个 R 脚本,统一接口:
#!/usr/bin/env Rscript
# plots/volcano.R
# 标准接口:读 JSON 参数 → 出图 → 退出
library(jsonlite)
library(ggplot2)
library(ggrepel)
# 1. 读取参数
args <- commandArgs(trailingOnly = TRUE)
params <- fromJSON(args[1])
# 2. 读取用户数据
data <- read.csv(params$data_path)
# 3. 绑定代码(参数化)
p <- ggplot(data, aes(x = .data[[params$x_col]],
y = -log10(.data[[params$p_col]]),
color = sig)) +
geom_point(size = params$point_size, alpha = params$alpha) +
# ... 其余参数化代码
# 4. 输出
ggsave(params$output_path, p, width = params$width, height = params$height, dpi = 300)
# 5. 返回状态
cat(toJSON(list(status = "success", output = params$output_path)))
API 层只需要:
// NestJS Worker
const result = await execRscript('plots/volcano.R', paramsJsonPath);
// result = { status: "success", output: "/tmp/xxx/volcano.png" }
基础设施
| 组件 | 选择 | 说明 |
|---|---|---|
| 容器编排 | Docker Compose(初期)→ K8s(后期) | 初期简单,后期按需扩展 |
| 反向代理 | Nginx | 限流、SSL、静态文件 |
| 消息队列 | Redis | 任务队列 + 结果缓存 |
| 文件存储 | 阿里云 OSS 或 MinIO | 用户上传文件 + 生成图片 |
| 数据库 | PostgreSQL | 用户、历史记录、配置 |
| 监控 | Prometheus + Grafana | 请求量、响应时间、错误率 |
4. 核心流程
单次绘图请求
1. 用户在前端选择图类型(如 Volcano Plot)
2. 上传数据文件(CSV/TSV,< 50MB)
3. 前端校验格式(列名、数据类型)
4. 调整参数(阈值、颜色、标注数量等)
5. 点击"生成"→ POST /api/plot/volcano
6. API Gateway 验证 + 限流
7. 任务入队(Redis Queue)
8. R Worker 从队列取任务:
a. 读取数据文件
b. 执行 ggplot2 代码(参数化)
c. ggsave 输出 PNG + PDF
d. 上传到 OSS
e. 返回图片 URL
9. 前端轮询/WebSocket 获取结果
10. 用户预览 + 下载
并发处理
- 每个 R Worker 是一个 Docker 容器,单线程处理一个任务
- 通过 Redis Queue 实现多 Worker 并行消费
- 初期 4 个 Worker(4 核服务器),后期按负载自动扩缩容
- 单个任务超时 30s,超时自动 kill
5. 安全与限制
| 维度 | 策略 |
|---|---|
| 文件大小 | 单文件 < 50MB |
| 请求频率 | 匿名用户 10 次/分钟,注册用户 60 次/分钟 |
| 数据隔离 | 每个任务在独立临时目录,完成后清理 |
| 代码注入 | 不执行用户代码,只接受参数化的预定义图类型 |
| 数据保留 | 匿名用户数据 1 小时后删除;注册用户 7 天 |
| HTTPS | 全站强制 HTTPS |
6. 目录结构(建议)
biof3-plot/
├── frontend/ # Next.js 前端
│ ├── app/
│ │ ├── page.tsx # 首页(图类型选择)
│ │ ├── plot/[type]/ # 每种图的参数页
│ │ └── api/ # BFF API Routes
│ ├── components/
│ │ ├── FileUpload.tsx
│ │ ├── ParamPanel.tsx
│ │ └── PlotPreview.tsx
│ └── lib/
│ ├── plotTypes.ts # 图类型定义(复用 FigCode 的 _data.ts)
│ └── api.ts # 后端 API 调用
│
├── backend/
│ ├── r-worker/ # R 绘图服务
│ │ ├── Dockerfile
│ │ ├── plumber.R # API 入口
│ │ └── plots/ # 每种图的 R 函数
│ │ ├── volcano.R
│ │ ├── heatmap.R
│ │ └── ...
│ ├── python-worker/ # Python 绘图服务(备选)
│ │ ├── Dockerfile
│ │ ├── main.py
│ │ └── plots/
│ └── queue/ # 任务队列配置
│ └── worker.ts
│
├── infra/
│ ├── docker-compose.yml # 本地开发
│ ├── docker-compose.prod.yml
│ ├── nginx.conf
│ └── deploy.sh
│
├── shared/
│ └── plot-types.json # 图类型定义(前后端共享)
│
└── README.md
7. 与 BioF3 的集成点
| 集成方式 | 说明 |
|---|---|
| FigCode "在线绘图" 按钮 | URL 指向 http://<IP>:<PORT>/plot/volcano 等 |
| 图类型定义共享 | _data.ts 里的 onlineToolUrl 字段指向对应页面 |
| 用户体系 | 可选:共享 BioF3 的 Waline 用户系统,或独立注册 |
| 样式统一 | 使用相同的配色(biof3_colors)和字体 |
8. 开发路线
| 阶段 | 时间 | 目标 |
|---|---|---|
| Week 1-2 | 搭建骨架 | Next.js 前端 + R Plumber 后端 + Docker Compose |
| Week 3-4 | MVP 3 种图 | Volcano + Heatmap + PCA,端到端跑通 |
| Week 5-6 | 扩展到 10 种图 | 覆盖 FigCode 里最常 用的 10 种 |
| Week 7-8 | 上线 + 优化 | 部署到阿里云、性能优化、限流 |
| 后续 | 持续迭代 | 更多图类型、用户系统、pipeline 工具 |
9. 服务器资源估算(初期)
| 资源 | 规格 | 用途 |
|---|---|---|
| 计算服务器 | 4C 8G | API + R Worker × 4 + Redis + Nginx |
| 前端 | 同一台服务器或 Vercel | Next.js(初期和 API 同机部署) |
| 存储 | 阿里云 OSS 100GB 或本地磁盘 | 用户文件 + 生成图片 |
| 端口 | 如 8080(前端)+ 3001(API) | Nginx 统一代理 |
| SSL | 后期按需配置 | 初期 HTTP 即可 |
如果用户量增长,计算服务器可以横向扩展(加 Worker 容器)。
10. 技术风险与应对
| 风险 | 应对 |
|---|---|
| R 包版本冲突 | Docker 镜像锁定版本,每种图类型可以用不同镜像 |
| 大文件处理慢 | 前端预处理(采样/截断)+ 后端流式读取 |
| 并发高峰 | Redis 队列 + Worker 自动扩缩容 |
| 恶意请求 | 限流 + 文件类型白名单 + 沙箱执行 |
| R 进程崩溃 | 每个任务独立进程,超时 kill,不影响其他任务 |
11. 下一步行动
- 在 OmicsHub 项目中新增绘图模块(前端路由 + 后端 Worker)
- 复用 OmicsHub 已有的用户系统、文件存储、API 框架
- 新增 R Worker 容器(Docker),实现第一个图类型(Volcano Plot)
- 本地验证端到端流程
- 把 BioF3 FigCode 的
onlineToolUrl从#改成 OmicsHub 的真实 URL
文档版本:v1.1 | 2026-05-12 | 作者:BioF3 团队 目标项目:OmicsHub(绘图模块)
静态文件
离线资料下载
手册 HTML / PDF 已在后台预生成,点击后直接下载网站静态资源。