"use strict"; // ===================================================== // 資料庫備援連線服務 // ===================================================== var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.dbFailover = exports.DatabaseFailover = void 0; const promise_1 = __importDefault(require("mysql2/promise")); // 主機資料庫配置 const masterConfig = { host: process.env.DB_HOST || 'mysql.theaken.com', port: parseInt(process.env.DB_PORT || '33306'), user: process.env.DB_USER || 'AI_Platform', password: process.env.DB_PASSWORD || 'Aa123456', database: process.env.DB_NAME || 'db_AI_Platform', charset: 'utf8mb4', timezone: '+08:00', connectionLimit: 10, queueLimit: 0, idleTimeout: 300000, ssl: false, }; // 備機資料庫配置 const slaveConfig = { host: process.env.SLAVE_DB_HOST || '122.100.99.161', port: parseInt(process.env.SLAVE_DB_PORT || '43306'), user: process.env.SLAVE_DB_USER || 'A999', password: process.env.SLAVE_DB_PASSWORD || '1023', database: process.env.SLAVE_DB_NAME || 'db_AI_Platform', // 修正為 AI 平台資料庫 charset: 'utf8mb4', timezone: '+08:00', connectionLimit: 10, queueLimit: 0, idleTimeout: 300000, ssl: false, }; // 備援資料庫管理類 class DatabaseFailover { constructor() { this.masterPool = null; this.slavePool = null; this.healthCheckInterval = null; this.status = { isEnabled: process.env.DB_FAILOVER_ENABLED === 'true', currentDatabase: 'master', masterHealthy: false, slaveHealthy: false, lastHealthCheck: 0, consecutiveFailures: 0, }; // 同步初始化連接池 this.initializePoolsSync(); this.startHealthCheck(); } static getInstance() { if (!DatabaseFailover.instance) { DatabaseFailover.instance = new DatabaseFailover(); } return DatabaseFailover.instance; } // 同步初始化連接池 initializePoolsSync() { try { console.log('🚀 開始同步初始化資料庫備援系統...'); // 直接創建連接池,不等待測試 this.masterPool = promise_1.default.createPool(masterConfig); console.log('✅ 主機資料庫連接池創建成功'); this.slavePool = promise_1.default.createPool(slaveConfig); console.log('✅ 備機資料庫連接池創建成功'); // 設置默認使用主機 this.status.currentDatabase = 'master'; this.status.masterHealthy = true; // 假設主機健康,後續健康檢查會驗證 console.log('🎯 當前使用資料庫: 主機'); // 異步測試連接 this.testConnectionsAsync(); } catch (error) { console.error('❌ 資料庫連接池同步初始化失敗:', error); } } // 異步測試連接 async testConnectionsAsync() { try { await this.testConnections(); } catch (error) { console.error('❌ 異步連接測試失敗:', error); } } // 初始化連接池 async initializePools() { try { console.log('🚀 開始初始化資料庫備援系統...'); // 先測試主機連接 console.log('📡 測試主機資料庫連接...'); const masterHealthy = await this.testMasterConnection(); if (masterHealthy) { console.log('✅ 主機資料庫連接正常,使用主機'); this.status.currentDatabase = 'master'; this.status.masterHealthy = true; // 初始化主機連接池 this.masterPool = promise_1.default.createPool(masterConfig); console.log('✅ 主機資料庫連接池初始化成功'); } else { console.log('❌ 主機資料庫連接失敗,測試備機...'); // 測試備機連接 const slaveHealthy = await this.testSlaveConnection(); if (slaveHealthy) { console.log('✅ 備機資料庫連接正常,切換到備機'); this.status.currentDatabase = 'slave'; this.status.slaveHealthy = true; // 初始化備機連接池 this.slavePool = promise_1.default.createPool(slaveConfig); console.log('✅ 備機資料庫連接池初始化成功'); } else { console.log('❌ 主機和備機都無法連接,嘗試初始化主機連接池'); this.masterPool = promise_1.default.createPool(masterConfig); this.status.currentDatabase = 'master'; } } // 初始化另一個連接池(用於健康檢查) if (this.status.currentDatabase === 'master' && !this.slavePool) { this.slavePool = promise_1.default.createPool(slaveConfig); console.log('✅ 備機資料庫連接池初始化成功(用於健康檢查)'); } else if (this.status.currentDatabase === 'slave' && !this.masterPool) { this.masterPool = promise_1.default.createPool(masterConfig); console.log('✅ 主機資料庫連接池初始化成功(用於健康檢查)'); } console.log(`🎯 當前使用資料庫: ${this.status.currentDatabase === 'master' ? '主機' : '備機'}`); } catch (error) { console.error('❌ 資料庫連接池初始化失敗:', error); } } // 測試主機連接 async testMasterConnection() { try { const connection = await promise_1.default.createConnection(masterConfig); await connection.ping(); await connection.end(); return true; } catch (error) { console.error('主機資料庫連接失敗:', error.message); return false; } } // 測試備機連接 async testSlaveConnection() { try { const connection = await promise_1.default.createConnection(slaveConfig); await connection.ping(); await connection.end(); return true; } catch (error) { console.error('備機資料庫連接失敗:', error.message); return false; } } // 測試資料庫連接 async testConnections() { // 測試主機 try { if (this.masterPool) { const connection = await this.masterPool.getConnection(); await connection.ping(); connection.release(); this.status.masterHealthy = true; console.log('主機資料庫連接正常'); } } catch (error) { this.status.masterHealthy = false; console.error('主機資料庫連接失敗:', error); } // 測試備機 try { if (this.slavePool) { const connection = await this.slavePool.getConnection(); await connection.ping(); connection.release(); this.status.slaveHealthy = true; console.log('備機資料庫連接正常'); } } catch (error) { this.status.slaveHealthy = false; console.error('備機資料庫連接失敗:', error); } } // 開始健康檢查 startHealthCheck() { if (!this.status.isEnabled) return; const interval = parseInt(process.env.DB_HEALTH_CHECK_INTERVAL || '30000'); this.healthCheckInterval = setInterval(async () => { await this.performHealthCheck(); }, interval); } // 執行健康檢查 async performHealthCheck() { const now = Date.now(); this.status.lastHealthCheck = now; // 檢查主機 if (this.masterPool) { try { const connection = await this.masterPool.getConnection(); await connection.ping(); connection.release(); this.status.masterHealthy = true; } catch (error) { this.status.masterHealthy = false; console.error('主機資料庫健康檢查失敗:', error); } } // 檢查備機 if (this.slavePool) { try { const connection = await this.slavePool.getConnection(); await connection.ping(); connection.release(); this.status.slaveHealthy = true; } catch (error) { this.status.slaveHealthy = false; console.error('備機資料庫健康檢查失敗:', error); } } // 決定當前使用的資料庫 this.determineCurrentDatabase(); } // 決定當前使用的資料庫 determineCurrentDatabase() { const previousDatabase = this.status.currentDatabase; if (this.status.masterHealthy) { if (this.status.currentDatabase !== 'master') { console.log('🔄 主機資料庫恢復,切換回主機'); this.status.currentDatabase = 'master'; this.status.consecutiveFailures = 0; } } else if (this.status.slaveHealthy) { if (this.status.currentDatabase !== 'slave') { console.log('🔄 主機資料庫故障,切換到備機'); this.status.currentDatabase = 'slave'; this.status.consecutiveFailures++; } } else { this.status.consecutiveFailures++; console.error('❌ 主機和備機資料庫都無法連接'); } // 記錄狀態變化 if (previousDatabase !== this.status.currentDatabase) { console.log(`📊 資料庫狀態變化: ${previousDatabase} → ${this.status.currentDatabase}`); } } // 獲取當前連接池 getCurrentPool() { if (this.status.currentDatabase === 'master') { if (this.masterPool) { return this.masterPool; } else if (this.slavePool) { // 主機不可用,嘗試使用備機 console.log('⚠️ 主機連接池不可用,嘗試使用備機'); this.status.currentDatabase = 'slave'; return this.slavePool; } } else if (this.status.currentDatabase === 'slave') { if (this.slavePool) { return this.slavePool; } else if (this.masterPool) { // 備機不可用,嘗試使用主機 console.log('⚠️ 備機連接池不可用,嘗試使用主機'); this.status.currentDatabase = 'master'; return this.masterPool; } } console.error('❌ 沒有可用的資料庫連接池'); return null; } // 獲取連接 async getConnection() { const pool = this.getCurrentPool(); if (!pool) { throw new Error('沒有可用的資料庫連接'); } let retries = 0; const maxRetries = parseInt(process.env.DB_RETRY_ATTEMPTS || '3'); const retryDelay = parseInt(process.env.DB_RETRY_DELAY || '2000'); while (retries < maxRetries) { try { return await pool.getConnection(); } catch (error) { console.error(`資料庫連接失敗 (嘗試 ${retries + 1}/${maxRetries}):`, error.message); if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST') { // 觸發健康檢查 await this.performHealthCheck(); retries++; if (retries < maxRetries) { console.log(`等待 ${retryDelay}ms 後重試...`); await new Promise(resolve => setTimeout(resolve, retryDelay)); continue; } } throw error; } } throw new Error('資料庫連接失敗,已達到最大重試次數'); } // 執行查詢 async query(sql, params) { const connection = await this.getConnection(); try { const [rows] = await connection.execute(sql, params); return rows; } finally { connection.release(); } } // 執行單一查詢 async queryOne(sql, params) { try { const results = await this.query(sql, params); return results.length > 0 ? results[0] : null; } catch (error) { console.error('資料庫單一查詢錯誤:', error); throw error; } } // 執行插入 async insert(sql, params) { const connection = await this.getConnection(); try { const [result] = await connection.execute(sql, params); return result; } finally { connection.release(); } } // 執行更新 async update(sql, params) { const connection = await this.getConnection(); try { const [result] = await connection.execute(sql, params); return result; } finally { connection.release(); } } // 執行刪除 async delete(sql, params) { const connection = await this.getConnection(); try { const [result] = await connection.execute(sql, params); return result; } finally { connection.release(); } } // 開始事務 async beginTransaction() { const connection = await this.getConnection(); await connection.beginTransaction(); return connection; } // 提交事務 async commit(connection) { await connection.commit(); connection.release(); } // 回滾事務 async rollback(connection) { await connection.rollback(); connection.release(); } // 獲取備援狀態 getStatus() { return { ...this.status }; } // 強制切換到指定資料庫 async switchToDatabase(database) { if (database === 'master' && this.status.masterHealthy) { this.status.currentDatabase = 'master'; return true; } else if (database === 'slave' && this.status.slaveHealthy) { this.status.currentDatabase = 'slave'; return true; } return false; } // 關閉所有連接池 async close() { if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); } if (this.masterPool) { await this.masterPool.end(); } if (this.slavePool) { await this.slavePool.end(); } } } exports.DatabaseFailover = DatabaseFailover; // 導出單例實例 exports.dbFailover = DatabaseFailover.getInstance();