- Implement 5 core API endpoints (create task, get progress, get results, update decision, export Excel) - Add FulltextScreeningController with Zod validation (652 lines) - Implement ExcelExporter service with 4-sheet report generation (352 lines) - Register routes under /api/v1/asl/fulltext-screening - Create 31 REST Client test cases - Add automated integration test script - Fix PDF extraction fallback mechanism in LLM12FieldsService - Update API design documentation to v3.0 - Update development plan to v1.2 - Create Day 5 development record - Clean up temporary test files
22 KiB
22 KiB
平台基础设施规划(Platform Infrastructure Plan)
文档版本: V1.0
创建日期: 2025-11-16
适用对象: 架构师、后端开发、运维
文档状态: 实施规划
维护者: 架构团队
📋 文档说明
本文档是壹证循AI科研平台的平台基础设施规划文档,定义了:
- 核心需求:平台基础设施需要解决的问题
- 设计方案:技术架构和实现方案
- 实施计划:分阶段的实施路线图
- 验收标准:每个模块的验收标准
核心目标:
- ✅ 支持本地开发和云端部署无缝切换
- ✅ 支持PRD定义的4种部署形态(云端SaaS、私有化、单机版、混合)
- ✅ 支持模块化组合售卖(专业版、高级版、旗舰版)
- ✅ 提供通用能力,所有业务模块直接复用
🎯 需求背景
1. 业务需求(来自PRD)
根据 09-总体需求文档(PRD).md,平台必须支持:
| 需求ID | 需求描述 | 技术挑战 |
|---|---|---|
| NFR-1.1 | 云端SaaS版(多租户、高可用) | Serverless架构、自动扩缩容 |
| NFR-1.2 | 私有化部署(数据不出内网) | 本地存储、本地数据库 |
| NFR-1.3 | 单机版(100%本地化) | 离线运行、本地文件系统 |
| NFR-1.4 | 混合部署(部分本地+部分云端) | 灵活的配置切换 |
| NFR-2.1 | SaaS多版本(专业版、高级版、旗舰版) | Feature Flag、模块化 |
| NFR-2.2 | 模块化售卖(任何模块可独立售卖) | 松耦合架构 |
| NFR-2.3 | AI成本可控(动态切换LLM) | 适配器模式 |
2. 技术需求(来自云原生架构)
根据云原生部署架构(阿里云 Serverless + RDS + OSS),平台必须:
| 技术需求 | 说明 | 优先级 |
|---|---|---|
| 无状态应用 | 不依赖本地文件系统或内存状态 | P0 |
| 存储抽象 | 支持本地存储和OSS无缝切换 | P0 |
| 数据库连接池 | 防止Serverless扩容导致连接数超限 | P0 |
| 标准化日志 | 输出到stdout,支持集中收集 | P0 |
| 异步任务 | 长时间任务必须异步处理(避免超时) | P0 |
| 分布式缓存 | 多实例共享缓存 | P1 |
| 健康检查 | SAE存活和就绪检查 | P1 |
| 监控指标 | 数据库连接数、任务队列等 | P1 |
🏗️ 设计方案
核心设计原则
平台基础设施通过适配器模式(Adapter Pattern)实现多环境支持
┌─────────────────────────────────────────────────────────┐
│ 业务模块层 │
│ ASL | AIA | PKB | DC | SSA | ST | UAM │
│ 只关注业务逻辑,复用平台能力 │
└─────────────────────────────────────────────────────────┘
↓ import from '@/common/'
┌─────────────────────────────────────────────────────────┐
│ 平台基础设施层(Adapter Pattern) │
├─────────────────────────────────────────────────────────┤
│ 存储:LocalAdapter ←→ OSSAdapter │
│ 缓存:MemoryCacheAdapter ←→ RedisCacheAdapter │
│ 任务:MemoryQueueAdapter ←→ DatabaseQueueAdapter │
│ 日志:ConsoleLogger ←→ 阿里云SLS │
│ 数据库:本地PostgreSQL ←→ 阿里云RDS(连接池) │
└─────────────────────────────────────────────────────────┘
↓ 环境变量切换
┌─────────────────────────────────────────────────────────┐
│ 部署环境(零代码改动) │
│ 本地开发 | 云端SaaS | 私有化部署 | 单机版 │
└─────────────────────────────────────────────────────────┘
📦 平台基础设施模块清单
模块总览
| 模块 | 路径 | 优先级 | 说明 |
|---|---|---|---|
| 存储服务 | common/storage/ |
P0 | 文件上传下载(本地/OSS) |
| 数据库连接池 | config/database.ts |
P0 | Prisma连接池配置 |
| 日志系统 | common/logging/ |
P0 | 标准化日志输出 |
| 环境配置 | config/env.ts |
P0 | 环境变量管理 |
| 异步任务 | common/jobs/ |
P0 | 长时间任务异步处理 |
| 缓存服务 | common/cache/ |
P1 | 分布式缓存 |
| 健康检查 | common/health/ |
P1 | SAE健康检查端点 |
| 监控指标 | common/monitoring/ |
P1 | 关键指标监控 |
📐 详细设计
1. 存储服务(Storage Service)
设计目标
- ✅ 支持本地开发(LocalAdapter)
- ✅ 支持云端部署(OSSAdapter)
- ✅ 业务代码零改动切换
目录结构
backend/src/common/storage/
├── StorageAdapter.ts # 接口定义
├── LocalAdapter.ts # 本地实现
├── OSSAdapter.ts # OSS实现
├── StorageFactory.ts # 工厂类
└── index.ts # 统一导出
接口定义
// backend/src/common/storage/StorageAdapter.ts
export interface StorageAdapter {
upload(key: string, buffer: Buffer): Promise<string>
download(key: string): Promise<Buffer>
delete(key: string): Promise<void>
getUrl(key: string): string
}
环境切换
# 本地开发
STORAGE_TYPE=local
# 生产环境
STORAGE_TYPE=oss
OSS_REGION=oss-cn-hangzhou
OSS_BUCKET=aiclinical-prod
业务模块使用
import { storage } from '@/common/storage'
// 使用(不关心本地还是OSS)
const url = await storage.upload('literature/123.pdf', buffer)
2. 数据库连接池(Database Connection Pool)
设计目标
- ✅ 防止Serverless扩容导致连接数超限
- ✅ 优雅关闭连接
- ✅ 连接数监控
文件位置
backend/src/config/database.ts
连接池配置
// 计算公式:每实例连接数 = RDS最大连接数 / SAE最大实例数
// 示例:400连接 / 20实例 = 20连接/实例
import { PrismaClient } from '@prisma/client'
const connectionLimit = Math.floor(
Number(process.env.DB_MAX_CONNECTIONS || 400) /
Number(process.env.MAX_INSTANCES || 20)
)
export const prisma = new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
})
// 优雅关闭
process.on('SIGTERM', async () => {
await prisma.$disconnect()
process.exit(0)
})
环境变量
DB_MAX_CONNECTIONS=400 # RDS最大连接数
MAX_INSTANCES=20 # SAE最大实例数
DATABASE_URL=postgresql://...
3. 日志系统(Logging)
设计目标
- ✅ 云原生:只输出到stdout(不写本地文件)
- ✅ JSON格式(便于阿里云SLS解析)
- ✅ 统一的日志格式
目录结构
backend/src/common/logging/
├── logger.ts # 日志工具
└── index.ts # 导出
实现
// backend/src/common/logging/logger.ts
import winston from 'winston'
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: {
service: 'aiclinical-backend',
env: process.env.NODE_ENV,
instance: process.env.HOSTNAME
},
transports: [
new winston.transports.Console({
format: winston.format.json() // ⭐ JSON格式
})
]
})
业务模块使用
import { logger } from '@/common/logging'
logger.info('Screening started', { projectId, count: 100 })
logger.error('LLM API failed', { error: err.message })
4. 环境配置(Environment Config)
设计目标
- ✅ 统一的配置管理
- ✅ 本地开发:.env文件
- ✅ 生产环境:SAE环境变量
- ✅ 启动时验证必需配置
文件位置
backend/src/config/env.ts
实现
// backend/src/config/env.ts
import { config } from 'dotenv'
// 只在本地开发加载 .env 文件
if (process.env.NODE_ENV !== 'production') {
config()
}
export const env = {
// 应用配置
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: Number(process.env.PORT) || 3001,
// 数据库配置
DATABASE_URL: process.env.DATABASE_URL!,
DB_MAX_CONNECTIONS: Number(process.env.DB_MAX_CONNECTIONS) || 400,
MAX_INSTANCES: Number(process.env.MAX_INSTANCES) || 20,
// 存储配置
STORAGE_TYPE: process.env.STORAGE_TYPE || 'local',
OSS_REGION: process.env.OSS_REGION,
OSS_BUCKET: process.env.OSS_BUCKET,
// 缓存配置
CACHE_TYPE: process.env.CACHE_TYPE || 'memory',
REDIS_HOST: process.env.REDIS_HOST,
// LLM配置
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
QWEN_API_KEY: process.env.QWEN_API_KEY,
// 功能开关
ENABLED_MODULES: process.env.ENABLED_MODULES?.split(',') || [],
}
// 启动时验证
export function validateEnv() {
const required = ['DATABASE_URL']
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing required env var: ${key}`)
}
}
}
5. 异步任务(Async Jobs)
设计目标
- ✅ 长时间任务(>10秒)异步处理
- ✅ 避免Serverless超时(30秒)
- ✅ 支持进度查询
目录结构
backend/src/common/jobs/
├── JobQueue.ts # 任务队列接口
├── MemoryQueue.ts # 本地开发(内存队列)
├── DatabaseQueue.ts # 生产环境(数据库队列)
├── JobProcessor.ts # 任务处理器
└── index.ts
接口定义
// backend/src/common/jobs/JobQueue.ts
export interface Job<T = any> {
id: string
type: string
data: T
status: 'pending' | 'processing' | 'completed' | 'failed'
progress: number
result?: any
error?: string
createdAt: Date
updatedAt: Date
}
export interface JobQueue {
push<T>(type: string, data: T): Promise<Job<T>>
process<T>(type: string, handler: (job: Job<T>) => Promise<void>): void
getJob(id: string): Promise<Job | null>
updateProgress(id: string, progress: number): Promise<void>
}
业务模块使用(ASL模块示例)
import { jobQueue } from '@/common/jobs'
// 1. 创建任务(立即返回)
app.post('/screening/start', async (req, res) => {
const job = await jobQueue.push('asl:screening', {
projectId: req.body.projectId,
literatureIds: req.body.literatureIds
})
res.send({ jobId: job.id }) // ⭐ 立即返回,不等待完成
})
// 2. 查询进度
app.get('/screening/jobs/:id', async (req, res) => {
const job = await jobQueue.getJob(req.params.id)
res.send({
status: job.status,
progress: job.progress, // 0-100
result: job.result
})
})
// 3. 处理任务(后台)
jobQueue.process('asl:screening', async (job) => {
const { projectId, literatureIds } = job.data
for (let i = 0; i < literatureIds.length; i++) {
await screenLiterature(literatureIds[i])
await jobQueue.updateProgress(job.id, (i + 1) / literatureIds.length * 100)
}
})
6. 缓存服务(Cache Service)
设计目标
- ✅ 支持本地开发(MemoryCacheAdapter)
- ✅ 支持生产环境(RedisCacheAdapter)
- ✅ 多实例共享缓存
目录结构
backend/src/common/cache/
├── CacheAdapter.ts # 接口定义
├── MemoryCacheAdapter.ts # 本地实现
├── RedisCacheAdapter.ts # Redis实现
├── CacheFactory.ts # 工厂类
└── index.ts
接口定义
export interface CacheAdapter {
get<T>(key: string): Promise<T | null>
set(key: string, value: any, ttl?: number): Promise<void>
delete(key: string): Promise<void>
clear(): Promise<void>
}
环境切换
# 本地开发
CACHE_TYPE=memory
# 生产环境
CACHE_TYPE=redis
REDIS_HOST=r-***.redis.aliyuncs.com
7. 健康检查(Health Check)
设计目标
- ✅ SAE存活检查(/health/liveness)
- ✅ SAE就绪检查(/health/readiness)
- ✅ 检查依赖服务(数据库)
目录结构
backend/src/common/health/
├── healthCheck.ts
└── index.ts
实现
import { FastifyInstance } from 'fastify'
import { prisma } from '@/config/database'
export async function registerHealthRoutes(app: FastifyInstance) {
// 存活检查
app.get('/health/liveness', async () => {
return { status: 'ok', timestamp: Date.now() }
})
// 就绪检查
app.get('/health/readiness', async () => {
try {
await prisma.$queryRaw`SELECT 1`
return {
status: 'ready',
checks: { database: 'ok' }
}
} catch (error) {
return {
status: 'not_ready',
checks: { database: 'failed' }
}
}
})
}
8. 监控指标(Monitoring)
设计目标
- ✅ 数据库连接数监控
- ✅ 关键指标告警
目录结构
backend/src/common/monitoring/
├── metrics.ts
└── index.ts
实现
import { prisma } from '@/config/database'
import { logger } from '@/common/logging'
import { env } from '@/config/env'
export class Metrics {
static async recordDBConnectionCount() {
const result = await prisma.$queryRaw`
SELECT count(*) as count
FROM pg_stat_activity
WHERE datname = current_database()
`
const count = result[0].count
logger.info('DB connection count', { count })
// 告警:连接数超过80%
if (count > env.DB_MAX_CONNECTIONS * 0.8) {
logger.warn('DB connection pool near limit', {
current: count,
max: env.DB_MAX_CONNECTIONS
})
}
return count
}
}
📅 实施计划
总体时间规划
预计总耗时:2.5天(20小时)
Day 1: 核心基础设施(P0模块) 8小时
Day 2: 辅助基础设施(P1模块)+ 测试 8小时
Day 3: 文档更新 4小时
Day 1:核心基础设施(P0模块)
上午(4小时)
Task 1.1:存储服务(2小时)
- 创建
backend/src/common/storage/目录 - 实现
StorageAdapter.ts(接口定义) - 实现
LocalAdapter.ts(本地实现) - 实现
OSSAdapter.ts(OSS实现,预留) - 实现
StorageFactory.ts(工厂类) - 创建
index.ts(统一导出)
验收标准:
- ✅ LocalAdapter 可正常 upload/download
- ✅ 通过环境变量
STORAGE_TYPE切换 - ✅ 单元测试通过
Task 1.2:数据库连接池(2小时)
- 更新
backend/src/config/database.ts - 添加连接池配置
- 添加优雅关闭逻辑
- 添加环境变量验证
验收标准:
- ✅ Prisma Client 正确配置连接池
- ✅ 启动时验证环境变量
- ✅ SIGTERM信号优雅关闭
下午(4小时)
Task 1.3:日志系统(2小时)
- 创建
backend/src/common/logging/目录 - 实现
logger.ts(winston配置) - 配置JSON格式输出
- 创建
index.ts(导出)
验收标准:
- ✅ 日志输出到stdout(JSON格式)
- ✅ 包含timestamp、service、env等元信息
- ✅ 支持不同日志级别(info/warn/error)
Task 1.4:异步任务(2小时)
- 创建
backend/src/common/jobs/目录 - 实现
JobQueue.ts(接口定义) - 实现
MemoryQueue.ts(内存队列) - 实现
DatabaseQueue.ts(数据库队列,预留) - 实现
JobProcessor.ts(任务处理器)
验收标准:
- ✅ 可创建任务并立即返回
- ✅ 可查询任务进度
- ✅ 可在后台处理任务
Day 2:辅助基础设施(P1模块)+ 测试
上午(4小时)
Task 2.1:环境配置(1小时)
- 更新
backend/src/config/env.ts - 统一环境变量定义
- 添加启动验证函数
验收标准:
- ✅ 所有环境变量集中管理
- ✅ 启动时自动验证必需配置
- ✅ 支持本地开发和生产环境
Task 2.2:缓存服务(2小时)
- 创建
backend/src/common/cache/目录 - 实现
CacheAdapter.ts(接口定义) - 实现
MemoryCacheAdapter.ts(内存实现) - 实现
RedisCacheAdapter.ts(Redis实现,预留) - 实现
CacheFactory.ts(工厂类)
验收标准:
- ✅ MemoryCacheAdapter 可正常 get/set
- ✅ 通过环境变量切换
- ✅ 支持TTL过期
Task 2.3:健康检查(1小时)
- 创建
backend/src/common/health/目录 - 实现
healthCheck.ts - 注册
/health/liveness端点 - 注册
/health/readiness端点
验收标准:
- ✅ liveness端点正常返回
- ✅ readiness端点检查数据库连接
- ✅ 错误时返回正确状态
下午(4小时)
Task 2.4:监控指标(1小时)
- 创建
backend/src/common/monitoring/目录 - 实现
metrics.ts - 实现数据库连接数监控
- 实现告警逻辑
验收标准:
- ✅ 可查询数据库连接数
- ✅ 连接数超过80%时告警
- ✅ 日志输出正确
Task 2.5:集成测试(3小时)
- 编写存储服务单元测试
- 编写日志系统单元测试
- 编写缓存服务单元测试
- 编写异步任务单元测试
- 集成测试(所有模块)
验收标准:
- ✅ 所有单元测试通过
- ✅ 集成测试通过
- ✅ 代码覆盖率 > 80%
Day 3:文档更新
上午(4小时)
Task 3.1:更新架构文档(2小时)
- 更新
01-系统架构分层设计.md- 在"平台基础层"章节中详细化各服务
- 添加适配器模式说明
- 更新
前后端模块化架构设计-V2.md- 更新 backend 目录结构
- 体现新增的 common/ 子模块
Task 3.2:更新实施文档(1小时)
- 更新
09-架构实施/03-云原生部署架构指南.md- 添加平台基础设施章节
- 更新环境变量配置示例
Task 3.3:简化ASL文档(1小时)
- 更新
03-业务模块/ASL-AI智能文献/04-开发计划/02-标题摘要初筛开发计划.md- 移除"创建存储抽象层"任务
- 添加"前置条件:平台已提供"
- 更新
03-业务模块/ASL-AI智能文献/04-开发计划/03-任务分解.md- 移除存储抽象层任务
- 更新验收标准
🎯 验收标准
总体验收标准
- 功能完整性:所有P0模块实现完成
- 测试覆盖:单元测试覆盖率 > 80%
- 文档完整性:所有架构文档更新完成
- 多环境支持:本地开发和云端部署验证通过
- 业务模块验证:ASL模块可正常使用平台能力
环境验证矩阵
| 验证项 | 本地开发 | 云端SaaS | 私有化部署 | 单机版 |
|---|---|---|---|---|
| 存储服务 | ✅ LocalAdapter | ✅ OSSAdapter | ✅ LocalAdapter | ✅ LocalAdapter |
| 数据库连接 | ✅ 本地PostgreSQL | ✅ 阿里云RDS | ✅ 内网PostgreSQL | ✅ SQLite |
| 缓存服务 | ✅ Memory | ✅ Redis | ✅ 内网Redis | ✅ Memory |
| 日志系统 | ✅ Console | ✅ 阿里云SLS | ✅ Console | ✅ Console |
| 异步任务 | ✅ MemoryQueue | ✅ DatabaseQueue | ✅ DatabaseQueue | ✅ MemoryQueue |
| 健康检查 | ✅ OK | ✅ OK | ✅ OK | N/A |
🚀 实施后的收益
1. 开发效率提升
| 指标 | 改造前 | 改造后 | 提升 |
|---|---|---|---|
| 业务模块开发时间 | 需要实现存储等基础设施 | 直接使用平台能力 | 节省30% |
| 新模块上手时间 | 需要学习基础设施 | 只需关注业务逻辑 | 节省50% |
| 代码复用率 | 每个模块重复实现 | 所有模块复用 | 提升80% |
2. 部署灵活性
| 部署形态 | 支持情况 | 切换成本 |
|---|---|---|
| 云端SaaS | ✅ 完全支持 | 修改环境变量 |
| 私有化部署 | ✅ 完全支持 | 修改环境变量 |
| 单机版 | ✅ 完全支持 | 修改环境变量 |
| 混合部署 | ✅ 完全支持 | 按模块配置 |
3. 商业模式灵活性
| 商业模式 | 支持情况 |
|---|---|
| 专业版(部分模块) | ✅ Feature Flag控制 |
| 高级版(组合模块) | ✅ Feature Flag控制 |
| 旗舰版(全部模块) | ✅ Feature Flag控制 |
| 单模块售卖 | ✅ Docker镜像分层 |
4. 技术债务降低
- ✅ 避免重复代码
- ✅ 统一的架构风格
- ✅ 易于维护和升级
- ✅ 新人快速上手
📚 相关文档
- 09-总体需求文档(PRD).md - 业务需求来源
- 01-系统架构分层设计.md - 总体架构设计
- 前后端模块化架构设计-V2.md - 代码组织架构
- 03-云原生部署架构指南.md - 云原生部署详细指南
- 08-云原生开发规范.md - 开发规范
📝 变更记录
| 日期 | 版本 | 变更内容 | 变更人 |
|---|---|---|---|
| 2025-11-16 | V1.0 | 初始版本,定义平台基础设施规划 | 架构团队 |
文档结束