import {drizzle} from 'drizzle-orm/postgres-js'; import {and, asc, count, desc, eq, exists, gt, gte, lt, lte, or, sql} from 'drizzle-orm'; import postgres from 'postgres'; import * as schema from './schema.ts'; import {config} from 'dotenv'; import path from 'path'; import {fileURLToPath} from 'url'; // 加载环境变量(优先使用工作目录的 .env,确保构建后运行时能正确加载) config({ path: path.resolve(process.cwd(), '.env') }); // 检查环境变量 // 注意:在 CI/Nuxt prerender 构建阶段,可能没有 DATABASE_URL const connectionString = process.env.DATABASE_URL || 'postgresql://postgres:dummy@localhost:5432/dummy'; if (!process.env.DATABASE_URL && !process.env.npm_lifecycle_script?.includes('build')) { console.warn('Warning: DATABASE_URL environment variable is not set. Using dummy connection string for build/prerender.'); } // 检测数据库类型 const isNeonDatabase = connectionString.includes('neon.tech') || connectionString.includes('neon.database.com'); // 检测 Serverless 部署环境 const isServerlessEnv = process.env.NETLIFY === 'true' || !!process.env.VERCEL; // 根据数据库类型和部署环境选择配置 const getDatabaseConfig = () => { if (isNeonDatabase || isServerlessEnv) { // Neon Database / Serverless 环境 (Netlify/Vercel) 优化配置 return { max: 1, // Serverless 环境下每个实例保持最小连接数,利用平台自身的连接池 idle_timeout: 1, // 尽快释放空闲连接,适应 Serverless 的快速冻结特性 connect_timeout: 10, // 减少连接超时时间 max_lifetime: 3600, // 连接最大生命周期(1小时) ssl: isNeonDatabase ? 'require' : (connectionString.includes('sslmode=require') || connectionString.includes('ssl=true') ? 'require' : false), prepare: false, // 禁用预处理语句以提高兼容性 keep_alive: false, // 禁用 TCP keep-alive,避免 Serverless 环境下连接残留 transform: { undefined: null, // 将undefined转换为null }, connection: { application_name: 'voicehub-app' }, onnotice: process.env.NODE_ENV === 'development' ? console.log : undefined, debug: process.env.NODE_ENV === 'development' && process.env.DEBUG_SQL === 'true' }; } else { // 标准 PostgreSQL 数据库配置(本地开发 / 常规 Node Server) return { max: 10, // 普通PostgreSQL可以支持更多连接 idle_timeout: 20, // 空闲超时时间 connect_timeout: 30, // 连接超时时间以适应网络延迟 max_lifetime: 3600, // 连接最大生命周期(1小时) ssl: connectionString.includes('sslmode=require') || connectionString.includes('ssl=true') ? 'require' : false, prepare: false, // 禁用预处理语句以提高兼容性 transform: { undefined: null, // 将undefined转换为null }, connection: { application_name: 'voicehub-app' }, onnotice: process.env.NODE_ENV === 'development' ? console.log : undefined, debug: process.env.NODE_ENV === 'development' && process.env.DEBUG_SQL === 'true' }; } }; const client = postgres(connectionString, getDatabaseConfig()); // 创建Drizzle数据库实例 export const db = drizzle(client, { schema }); // 导出连接客户端(用于手动查询或关闭连接) export { client }; // 导出schema以便在其他地方使用 export * from './schema.ts'; // 导出drizzle-orm函数 export {eq, and, gt, gte, lt, lte, count, exists, desc, asc, or, sql}; // 数据库连接测试函数 export async function testConnection() { try { await client`SELECT 1`; console.log('✅ Database connection successful'); return true; } catch (error) { console.error('❌ Database connection failed:', error); return false; } } // 获取数据库连接状态 export function getConnectionStatus() { return { isConnected: !client.ended, maxConnections: client.options.max, idleTimeout: client.options.idle_timeout, connectTimeout: client.options.connect_timeout }; } // 连接管理 - 根据数据库类型自适应 let idleTimer: NodeJS.Timeout | null = null; // Neon 数据库使用更短的空闲时间以支持自动启停,普通 PostgreSQL 使用更长的空闲时间 const IDLE_TIMEOUT = isNeonDatabase ? 5 * 60 * 1000 : 10 * 60 * 1000; // Neon: 5分钟,PostgreSQL: 10分钟 // 重置空闲计时器 function resetIdleTimer() { if (idleTimer) { clearTimeout(idleTimer); } // 只在生产环境启用自动断开 if (process.env.NODE_ENV === 'production') { idleTimer = setTimeout(async () => { try { if (!client.ended) { const dbType = isNeonDatabase ? 'Neon' : 'PostgreSQL'; console.log(`🔄 Auto-closing idle ${dbType} database connections${isNeonDatabase ? ' for Serverless optimization' : ''}`); await client.end({ timeout: isNeonDatabase ? 5 : 10 }); } } catch (error) { console.error('❌ Error during auto-close:', error); } }, IDLE_TIMEOUT); } } // 包装数据库操作以支持自动启停 export function withAutoReconnect( operation: (...args: T) => Promise ) { return async (...args: T): Promise => { resetIdleTimer(); try { return await operation(...args); } catch (error: any) { // 如果连接已关闭,记录信息 if (error?.code === 'CONNECTION_ENDED' || client.ended) { const dbType = isNeonDatabase ? 'Neon' : 'PostgreSQL'; console.log(`🔄 ${dbType} database connection ended${isNeonDatabase ? ', Neon will auto-reconnect on next query' : ', will reconnect on next query'}`); } throw error; } }; } // 优雅关闭数据库连接 export async function closeConnection() { try { if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; } if (!client.ended) { await client.end({ timeout: 10 }); console.log('✅ Database connection closed gracefully'); } } catch (error) { console.error('❌ Error closing database connection:', error); } } // 设置优雅关闭处理 if (typeof process !== 'undefined') { const gracefulShutdown = async () => { console.log('🔄 Shutting down database connections...'); await closeConnection(); }; process.on('SIGINT', gracefulShutdown); process.on('SIGTERM', gracefulShutdown); process.on('beforeExit', gracefulShutdown); }