const mongoose = require('mongoose'); const redis = require('redis'); class DatabaseConnection { constructor() { this.mongooseConnection = null; this.redisClient = null; } // 连接MongoDB async connectMongoDB() { try { const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/pdf-tools'; this.mongooseConnection = await mongoose.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true, serverSelectionTimeoutMS: 5000, maxPoolSize: 10, socketTimeoutMS: 45000, }); console.log('✅ MongoDB连接成功'); // 监听连接事件 mongoose.connection.on('error', (err) => { console.error('❌ MongoDB连接错误:', err); }); mongoose.connection.on('disconnected', () => { console.log('⚠️ MongoDB连接断开'); }); mongoose.connection.on('reconnected', () => { console.log('✅ MongoDB重新连接成功'); }); return this.mongooseConnection; } catch (error) { console.error('❌ MongoDB连接失败:', error); throw error; } } // 连接Redis async connectRedis() { try { const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379'; this.redisClient = redis.createClient({ url: redisUrl, retry_strategy: (options) => { if (options.error && options.error.code === 'ECONNREFUSED') { console.error('❌ Redis服务器拒绝连接'); return new Error('Redis服务器拒绝连接'); } if (options.total_retry_time > 1000 * 60 * 60) { console.error('❌ Redis重连超时'); return new Error('Redis重连超时'); } if (options.attempt > 10) { console.error('❌ Redis重连次数超限'); return undefined; } // 重连间隔递增 return Math.min(options.attempt * 100, 3000); } }); this.redisClient.on('error', (err) => { console.error('❌ Redis连接错误:', err); }); this.redisClient.on('connect', () => { console.log('✅ Redis连接成功'); }); this.redisClient.on('reconnecting', () => { console.log('🔄 Redis重新连接中...'); }); this.redisClient.on('ready', () => { console.log('✅ Redis准备就绪'); }); await this.redisClient.connect(); return this.redisClient; } catch (error) { console.error('❌ Redis连接失败:', error); // Redis连接失败不应该阻止应用启动 return null; } } // 初始化所有数据库连接 async initialize() { try { // 并行连接数据库 const [mongoConnection, redisConnection] = await Promise.allSettled([ this.connectMongoDB(), this.connectRedis() ]); if (mongoConnection.status === 'rejected') { throw new Error(`MongoDB连接失败: ${mongoConnection.reason.message}`); } if (redisConnection.status === 'rejected') { console.warn('⚠️ Redis连接失败,将使用内存缓存'); } console.log('🎉 数据库初始化完成'); return { mongodb: mongoConnection.value, redis: redisConnection.status === 'fulfilled' ? redisConnection.value : null }; } catch (error) { console.error('❌ 数据库初始化失败:', error); throw error; } } // 关闭所有连接 async close() { try { const promises = []; if (this.mongooseConnection) { promises.push(mongoose.connection.close()); } if (this.redisClient) { promises.push(this.redisClient.quit()); } await Promise.all(promises); console.log('✅ 数据库连接已关闭'); } catch (error) { console.error('❌ 关闭数据库连接失败:', error); } } // 获取MongoDB连接状态 getMongoStatus() { return { status: mongoose.connection.readyState, host: mongoose.connection.host, port: mongoose.connection.port, name: mongoose.connection.name }; } // 获取Redis连接状态 getRedisStatus() { if (!this.redisClient) { return { status: 'disconnected', message: '未连接' }; } return { status: this.redisClient.isReady ? 'connected' : 'disconnected', message: this.redisClient.isReady ? '已连接' : '未连接' }; } // 健康检查 async healthCheck() { const health = { mongodb: { status: 'unknown', message: '' }, redis: { status: 'unknown', message: '' } }; try { // MongoDB健康检查 if (mongoose.connection.readyState === 1) { await mongoose.connection.db.admin().ping(); health.mongodb = { status: 'healthy', message: '连接正常' }; } else { health.mongodb = { status: 'unhealthy', message: '连接异常' }; } } catch (error) { health.mongodb = { status: 'unhealthy', message: error.message }; } try { // Redis健康检查 if (this.redisClient && this.redisClient.isReady) { await this.redisClient.ping(); health.redis = { status: 'healthy', message: '连接正常' }; } else { health.redis = { status: 'unhealthy', message: '连接异常' }; } } catch (error) { health.redis = { status: 'unhealthy', message: error.message }; } return health; } } // 创建单例实例 const databaseConnection = new DatabaseConnection(); module.exports = databaseConnection;