import Fastify from 'fastify'; import cors from '@fastify/cors'; import multipart from '@fastify/multipart'; import { config, validateEnv } from './config/env.js'; import { testDatabaseConnection, prisma } from './config/database.js'; import { projectRoutes } from './legacy/routes/projects.js'; import { agentRoutes } from './legacy/routes/agents.js'; import { conversationRoutes } from './legacy/routes/conversations.js'; import knowledgeBaseRoutes from './legacy/routes/knowledgeBases.js'; import { chatRoutes } from './legacy/routes/chatRoutes.js'; import { batchRoutes } from './legacy/routes/batchRoutes.js'; import reviewRoutes from './legacy/routes/reviewRoutes.js'; import { rvwRoutes } from './modules/rvw/index.js'; import { aslRoutes } from './modules/asl/routes/index.js'; import { registerDCRoutes, initDCModule } from './modules/dc/index.js'; import pkbRoutes from './modules/pkb/routes/index.js'; import { aiaRoutes } from './modules/aia/index.js'; import { registerHealthRoutes, markShuttingDown } from './common/health/index.js'; import { logger } from './common/logging/index.js'; import { authRoutes, registerAuthPlugin } from './common/auth/index.js'; import { promptRoutes } from './common/prompt/index.js'; import { registerTestRoutes } from './test-platform-api.js'; import { registerScreeningWorkers } from './modules/asl/services/screeningWorker.js'; import { registerResearchWorker } from './modules/asl/workers/researchWorker.js'; import { registerExtractionWorkers } from './modules/dc/tool-b/workers/extractionWorker.js'; import { registerExtractionWorkers as registerAslExtractionWorkers } from './modules/asl/extraction/workers/index.js'; import { registerParseExcelWorker } from './modules/dc/tool-c/workers/parseExcelWorker.js'; import { registerReviewWorker } from './modules/rvw/workers/reviewWorker.js'; import { jobQueue } from './common/jobs/index.js'; // 全局处理BigInt序列化 (BigInt.prototype as any).toJSON = function() { return Number(this); }; // 生产环境使用JSON格式日志(性能更好),开发环境使用pino-pretty(易读) const fastify = Fastify({ logger: config.nodeEnv === 'production' ? { level: config.logLevel, // 生产环境:简单的JSON日志,适合日志收集系统 } : { level: config.logLevel, // 开发环境:使用pino-pretty美化输出 transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'HH:MM:ss Z', ignore: 'pid,hostname', }, }, }, }); // 注册CORS插件 - 完整配置 await fastify.register(cors, { origin: true, // 开发环境允许所有来源 credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'], // 明确允许的HTTP方法 allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'Origin'], // 允许的请求头 exposedHeaders: ['Content-Range', 'X-Content-Range'], // 暴露的响应头 maxAge: 600, // preflight请求缓存时间(秒) preflightContinue: false, // Fastify处理preflight请求 }); console.log('✅ CORS已配置: 允许所有HTTP方法 (GET, POST, PUT, DELETE, PATCH, OPTIONS)'); // 注册文件上传插件 await fastify.register(multipart, { limits: { fileSize: 50 * 1024 * 1024, // 50MB(与前端提示一致) }, }); console.log('✅ 文件上传插件已配置: 最大文件大小 10MB'); // ============================================ // 【平台基础设施】健康检查路由 // ============================================ // 注册健康检查路由(/health, /health/liveness, /health/readiness) await registerHealthRoutes(fastify); logger.info('✅ 健康检查路由已注册'); // ============================================ // 【公开API】无需认证的公共接口 // ============================================ import { getTenantLoginConfig } from './modules/admin/controllers/tenantController.js'; fastify.get('/api/v1/public/tenant-config/:tenantCode', getTenantLoginConfig); logger.info('✅ 公开API已注册: /api/v1/public/tenant-config/:tenantCode'); // ============================================ // 【平台基础设施】认证模块 // ============================================ await registerAuthPlugin(fastify); await fastify.register(authRoutes, { prefix: '/api/v1/auth' }); logger.info('✅ 认证路由已注册: /api/v1/auth'); // ============================================ // 【运营管理】Prompt管理模块 // ============================================ await fastify.register(promptRoutes, { prefix: '/api/admin/prompts' }); logger.info('✅ Prompt管理路由已注册: /api/admin/prompts'); // ============================================ // 【运营管理】租户管理模块 // ============================================ import { tenantRoutes, moduleRoutes } from './modules/admin/routes/tenantRoutes.js'; import { userRoutes } from './modules/admin/routes/userRoutes.js'; import { statsRoutes, userOverviewRoute } from './modules/admin/routes/statsRoutes.js'; import { systemKbRoutes } from './modules/admin/system-kb/index.js'; import { iitProjectRoutes, iitQcRuleRoutes, iitUserMappingRoutes, iitBatchRoutes, iitQcCockpitRoutes, iitEqueryRoutes } from './modules/admin/iit-projects/index.js'; import { authenticate, requireRoles } from './common/auth/auth.middleware.js'; await fastify.register(tenantRoutes, { prefix: '/api/admin/tenants' }); await fastify.register(moduleRoutes, { prefix: '/api/admin/modules' }); await fastify.register(userRoutes, { prefix: '/api/admin/users' }); await fastify.register(statsRoutes, { prefix: '/api/admin/stats' }); await fastify.register(userOverviewRoute, { prefix: '/api/admin/users' }); await fastify.register(systemKbRoutes, { prefix: '/api/v1/admin/system-kb' }); // IIT 项目管理路由 — 认证 + 角色守卫(SUPER_ADMIN, PROMPT_ENGINEER, IIT_OPERATOR, PHARMA_ADMIN, HOSPITAL_ADMIN 可访问) await fastify.register(async (scope) => { scope.addHook('preHandler', authenticate); scope.addHook('preHandler', requireRoles('SUPER_ADMIN', 'PROMPT_ENGINEER', 'IIT_OPERATOR', 'PHARMA_ADMIN', 'HOSPITAL_ADMIN')); await scope.register(iitProjectRoutes); await scope.register(iitQcRuleRoutes); await scope.register(iitUserMappingRoutes); await scope.register(iitBatchRoutes); await scope.register(iitQcCockpitRoutes); await scope.register(iitEqueryRoutes); }, { prefix: '/api/v1/admin/iit-projects' }); logger.info('✅ 运营管理路由已注册: /api/admin/tenants, /api/admin/modules, /api/admin/users, /api/admin/stats, /api/v1/admin/system-kb, /api/v1/admin/iit-projects (authenticated)'); // ============================================ // 【临时】平台基础设施测试API // ============================================ await registerTestRoutes(fastify); logger.info('✅ 测试API已注册: /test/platform'); // API路由前缀 fastify.get('/api/v1', async () => { return { message: 'AI Clinical Research Platform API', version: '1.0.0', environment: config.nodeEnv, }; }); // 注册项目管理路由 await fastify.register(projectRoutes, { prefix: '/api/v1' }); // 注册智能体管理路由 await fastify.register(agentRoutes, { prefix: '/api/v1' }); // 注册对话管理路由 await fastify.register(conversationRoutes, { prefix: '/api/v1' }); // 注册知识库管理路由 await fastify.register(knowledgeBaseRoutes, { prefix: '/api/v1' }); // 注册通用对话路由 await fastify.register(chatRoutes, { prefix: '/api/v1' }); // Phase 3: 注册批处理路由 await fastify.register(batchRoutes, { prefix: '/api/v1' }); // 注册稿件审查路由(旧版,保留兼容) await fastify.register(reviewRoutes, { prefix: '/api/v1' }); // ============================================ // 【业务模块】RVW - 稿件审查系统 // ============================================ await fastify.register(rvwRoutes, { prefix: '/api/v1/rvw' }); logger.info('✅ RVW稿件审查路由已注册: /api/v1/rvw'); // ============================================ // 【业务模块】PKB - 个人知识库 // ============================================ await fastify.register(pkbRoutes, { prefix: '/api/v1/pkb' }); logger.info('✅ PKB个人知识库路由已注册: /api/v1/pkb'); // ============================================ // 【业务模块】AIA - AI智能问答 // ============================================ await fastify.register(aiaRoutes, { prefix: '/api/v1/aia' }); logger.info('✅ AIA智能问答路由已注册: /api/v1/aia'); // ============================================ // 【业务模块】Protocol Agent - 研究方案制定Agent // ============================================ import { protocolAgentRoutes } from './modules/agent/protocol/index.js'; await fastify.register((instance, opts, done) => { protocolAgentRoutes(instance, { prisma, ...opts }).then(() => done()).catch(done); }, { prefix: '/api/v1/aia/protocol-agent' }); logger.info('✅ Protocol Agent路由已注册: /api/v1/aia/protocol-agent'); // ============================================ // 【业务模块】ASL - AI智能文献筛选 // ============================================ await fastify.register(aslRoutes, { prefix: '/api/v1/asl' }); logger.info('✅ ASL智能文献筛选路由已注册: /api/v1/asl'); // ============================================ // 【业务模块】DC - 数据清洗整理 // ============================================ await registerDCRoutes(fastify); logger.info('✅ DC数据清洗模块路由已注册: /api/v1/dc/tool-b'); // ============================================ // 【业务模块】IIT Manager Agent - IIT研究智能助手 // ============================================ import { registerIitRoutes, initIitManager } from './modules/iit-manager/index.js'; await registerIitRoutes(fastify); logger.info('✅ IIT Manager Agent路由已注册: /api/v1/iit'); // ============================================ // 【业务模块】SSA - 智能统计分析 // ============================================ import { ssaRoutes } from './modules/ssa/index.js'; await fastify.register(ssaRoutes, { prefix: '/api/v1/ssa' }); logger.info('✅ SSA智能统计分析路由已注册: /api/v1/ssa'); // ============================================ // 【集成模块】Legacy Bridge - 旧系统集成桥接 // ============================================ import { legacyBridgeRoutes } from './modules/legacy-bridge/routes.js'; await fastify.register(legacyBridgeRoutes, { prefix: '/api/v1/legacy' }); logger.info('✅ Legacy Bridge路由已注册: /api/v1/legacy'); // 启动服务器 const start = async () => { try { // 验证环境变量 validateEnv(); // 测试数据库连接 console.log('🔍 正在测试数据库连接...'); const dbConnected = await testDatabaseConnection(); if (!dbConnected) { console.error('❌ 数据库连接失败,无法启动服务器'); process.exit(1); } // ============================================ // 【Postgres-Only】启动队列和注册Workers // ============================================ try { logger.info('🚀 Starting Postgres-Only queue and workers...'); // 启动队列(pg-boss) await jobQueue.start(); logger.info('✅ Queue started (pg-boss)'); // 注册ASL筛选Workers registerScreeningWorkers(); logger.info('✅ ASL screening workers registered'); // 注册ASL智能文献检索Worker registerResearchWorker(); logger.info('✅ ASL research worker registered'); // 注册DC提取Workers registerExtractionWorkers(); logger.info('✅ DC extraction workers registered'); // 注册DC Tool C Excel解析Worker registerParseExcelWorker(); logger.info('✅ DC Tool C parse excel worker registered'); // 注册ASL工具3全文提取Workers(散装派发 + Aggregator) await registerAslExtractionWorkers(); logger.info('✅ ASL extraction workers registered (Tool 3)'); // 注册RVW审稿Worker(包含启动时清理卡住任务) await registerReviewWorker(); logger.info('✅ RVW review worker registered'); // 注册IIT Manager Workers await initIitManager(); logger.info('✅ IIT Manager workers registered'); // ⚠️ 等待3秒,确保所有 Worker 异步注册到 pg-boss 完成 console.log('\n⏳ 等待 Workers 异步注册完成...'); await new Promise(resolve => setTimeout(resolve, 3000)); logger.info('✅ All workers registration completed (waited 3s)'); console.log('\n' + '='.repeat(60)); console.log('✅ Postgres-Only 架构已启动'); console.log('='.repeat(60)); console.log('📦 队列类型: pg-boss'); console.log('📦 缓存类型: PostgreSQL'); console.log('📦 注册的Workers:'); console.log(' - asl_screening_batch (文献筛选批次处理)'); console.log(' - dc_extraction_batch (数据提取批次处理)'); console.log(' - dc_toolc_parse_excel (Tool C Excel解析)'); console.log(' - rvw_review_task (稿件审查任务)'); console.log(' - iit_quality_check (IIT质控+企微推送)'); console.log(' - iit_redcap_poll (IIT REDCap轮询)'); console.log('='.repeat(60) + '\n'); } catch (error) { logger.error('❌ Failed to start Postgres-Only architecture', { error }); console.error('❌ Postgres-Only 架构启动失败:', error); process.exit(1); } // 初始化DC模块(Seed预设模板) try { await initDCModule(); logger.info('✅ DC模块初始化成功'); } catch (error) { logger.warn('⚠️ DC模块初始化失败,但不影响启动', { error }); } // 启动Fastify服务器 await fastify.listen({ port: config.port, host: config.host, }); console.log('\n' + '='.repeat(60)); console.log('🚀 AI临床研究平台 - 后端服务器启动成功!'); console.log('='.repeat(60)); console.log(`📍 服务地址: http://${config.host === '0.0.0.0' ? 'localhost' : config.host}:${config.port}`); console.log(`🔍 健康检查: http://localhost:${config.port}/health`); console.log(`📡 API入口: http://localhost:${config.port}/api/v1`); console.log(`🌍 运行环境: ${config.nodeEnv}`); console.log('='.repeat(60) + '\n'); } catch (err) { fastify.log.error(err); process.exit(1); } }; start(); // ============================================ // 🛡️ 优雅关闭处理(Graceful Shutdown) // ============================================ const SHUTDOWN_TIMEOUT_MS = 30_000; const gracefulShutdown = async (signal: string) => { console.log(`\n⚠️ 收到 ${signal} 信号,开始优雅关闭...`); // 立即标记停机,健康检查返回 503,CLB 不再派发新请求 markShuttingDown(); console.log('🚫 健康检查已切换为 503,CLB 将停止路由新流量'); // 强制超时兜底:防止 SSE 长连接或死循环任务阻塞退出 const forceTimer = setTimeout(() => { console.error(`❌ 优雅关闭超时 (${SHUTDOWN_TIMEOUT_MS / 1000}s),强制退出`); process.exit(1); }, SHUTDOWN_TIMEOUT_MS); forceTimer.unref(); try { // 1. 停止接收新请求(已有 SSE 连接继续跑完) await fastify.close(); console.log('✅ HTTP 服务已停止'); // 2. 停止队列(等待当前任务完成) await jobQueue.stop(); console.log('✅ 任务队列已停止'); // 3. 关闭数据库连接 await prisma.$disconnect(); console.log('✅ 数据库连接已关闭'); // 4. 关闭 Legacy MySQL 连接池 const { closeLegacyMysqlPool } = await import('./modules/legacy-bridge/mysql-pool.js'); await closeLegacyMysqlPool(); console.log('✅ Legacy MySQL连接池已关闭'); console.log('👋 优雅关闭完成,再见!'); process.exit(0); } catch (error) { console.error('❌ 优雅关闭失败:', error); process.exit(1); } }; process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGINT', () => gracefulShutdown('SIGINT'));