// ===================================================== // 修復的資料庫雙寫同步機制 // 確保主機和備機使用各自的 ID 序列 // ===================================================== import mysql from 'mysql2/promise'; import { DatabaseFailover } from './database-failover'; export interface WriteResult { success: boolean; masterSuccess: boolean; slaveSuccess: boolean; masterError?: string; slaveError?: string; masterId?: string; slaveId?: string; } export class DatabaseSyncFixed { private masterPool: mysql.Pool | null = null; private slavePool: mysql.Pool | null = null; private dbFailover: DatabaseFailover; constructor() { this.dbFailover = new DatabaseFailover(); this.initializePools(); } // 初始化連接池 private initializePools(): void { try { // 主機連接池 this.masterPool = mysql.createPool({ 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', connectionLimit: 10, acquireTimeout: 60000, timeout: 60000, reconnect: true }); // 備機連接池 this.slavePool = mysql.createPool({ 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', charset: 'utf8mb4', connectionLimit: 10, acquireTimeout: 60000, timeout: 60000, reconnect: true }); console.log('✅ 修復的雙寫連接池初始化成功'); } catch (error) { console.error('❌ 修復的雙寫連接池初始化失敗:', error); } } // 智能雙寫插入 - 每個資料庫使用自己的 ID async smartDualInsert(tableName: string, data: Record): Promise { const result: WriteResult = { success: false, masterSuccess: false, slaveSuccess: false }; try { // 同時寫入主機和備機,各自生成 ID const masterPromise = this.insertToMaster(tableName, data); const slavePromise = this.insertToSlave(tableName, data); const [masterResult, slaveResult] = await Promise.allSettled([masterPromise, slavePromise]); result.masterSuccess = masterResult.status === 'fulfilled'; result.slaveSuccess = slaveResult.status === 'fulfilled'; result.success = result.masterSuccess || result.slaveSuccess; if (masterResult.status === 'fulfilled') { result.masterId = masterResult.value; } else { result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機寫入失敗'; } if (slaveResult.status === 'fulfilled') { result.slaveId = slaveResult.value; } else { result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機寫入失敗'; } console.log(`📝 智能雙寫結果: 主機${result.masterSuccess ? '✅' : '❌'} 備機${result.slaveSuccess ? '✅' : '❌'}`); } catch (error) { result.masterError = error instanceof Error ? error.message : '智能雙寫執行失敗'; } return result; } // 寫入主機 - 使用主機的 ID 生成 private async insertToMaster(tableName: string, data: Record): Promise { if (!this.masterPool) { throw new Error('主機連接池不可用'); } const connection = await this.masterPool.getConnection(); try { // 生成主機的 UUID const [uuidResult] = await connection.execute('SELECT UUID() as id'); const masterId = (uuidResult as any)[0].id; // 構建插入 SQL const columns = Object.keys(data).join(', '); const placeholders = Object.keys(data).map(() => '?').join(', '); const values = Object.values(data); const sql = `INSERT INTO ${tableName} (id, ${columns}) VALUES (?, ${placeholders})`; await connection.execute(sql, [masterId, ...values]); console.log(`✅ 主機寫入成功: ${masterId}`); return masterId; } finally { connection.release(); } } // 寫入備機 - 使用備機的 ID 生成 private async insertToSlave(tableName: string, data: Record): Promise { if (!this.slavePool) { throw new Error('備機連接池不可用'); } const connection = await this.slavePool.getConnection(); try { // 生成備機的 UUID const [uuidResult] = await connection.execute('SELECT UUID() as id'); const slaveId = (uuidResult as any)[0].id; // 構建插入 SQL const columns = Object.keys(data).join(', '); const placeholders = Object.keys(data).map(() => '?').join(', '); const values = Object.values(data); const sql = `INSERT INTO ${tableName} (id, ${columns}) VALUES (?, ${placeholders})`; await connection.execute(sql, [slaveId, ...values]); console.log(`✅ 備機寫入成功: ${slaveId}`); return slaveId; } finally { connection.release(); } } // 智能雙寫關聯表 - 使用對應的競賽 ID async smartDualInsertRelation( relationTable: string, competitionId: string, slaveCompetitionId: string, relationData: any[], relationIdField: string ): Promise { const result: WriteResult = { success: false, masterSuccess: false, slaveSuccess: false }; try { console.log(`🔍 smartDualInsertRelation 開始執行`); console.log(` 表名: ${relationTable}`); console.log(` 主機競賽 ID: ${competitionId}`); console.log(` 備機競賽 ID: ${slaveCompetitionId}`); console.log(` 關聯數據:`, relationData); console.log(` 關聯字段: ${relationIdField}`); // 先獲取主機的競賽 ID const masterCompetitionId = await this.getMasterCompetitionId(competitionId); if (!masterCompetitionId || !slaveCompetitionId) { throw new Error('找不到對應的競賽 ID'); } console.log(`🔍 關聯雙寫開始: ${relationTable}`); console.log(` 主機競賽 ID: ${masterCompetitionId}`); console.log(` 備機競賽 ID: ${slaveCompetitionId}`); console.log(` 關聯數據數量: ${relationData.length}`); console.log(` 主機競賽存在: ${!!masterCompetitionId}`); console.log(` 備機競賽存在: ${!!slaveCompetitionId}`); // 同時寫入關聯數據 const masterPromise = this.insertRelationsToMaster(relationTable, masterCompetitionId, relationData, relationIdField); const slavePromise = this.insertRelationsToSlave(relationTable, slaveCompetitionId, relationData, relationIdField); const [masterResult, slaveResult] = await Promise.allSettled([masterPromise, slavePromise]); result.masterSuccess = masterResult.status === 'fulfilled'; result.slaveSuccess = slaveResult.status === 'fulfilled'; result.success = result.masterSuccess || result.slaveSuccess; if (masterResult.status === 'rejected') { result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機關聯寫入失敗'; console.error(`❌ 主機關聯寫入失敗:`, masterResult.reason); } if (slaveResult.status === 'rejected') { result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機關聯寫入失敗'; console.error(`❌ 備機關聯寫入失敗:`, slaveResult.reason); } console.log(`📝 關聯雙寫結果: 主機${result.masterSuccess ? '✅' : '❌'} 備機${result.slaveSuccess ? '✅' : '❌'}`); } catch (error) { result.masterError = error instanceof Error ? error.message : '關聯雙寫執行失敗'; } return result; } // 獲取主機競賽 ID private async getMasterCompetitionId(competitionId: string): Promise { if (!this.masterPool) return null; const connection = await this.masterPool.getConnection(); try { const [rows] = await connection.execute('SELECT id FROM competitions WHERE id = ?', [competitionId]); return (rows as any[])[0]?.id || null; } finally { connection.release(); } } // 獲取備機競賽 ID private async getSlaveCompetitionId(competitionId: string): Promise { if (!this.slavePool) return null; const connection = await this.slavePool.getConnection(); try { const [rows] = await connection.execute('SELECT id FROM competitions WHERE id = ?', [competitionId]); return (rows as any[])[0]?.id || null; } finally { connection.release(); } } // 獲取競賽信息 private async getCompetitionById(competitionId: string): Promise { if (!this.masterPool) return null; const connection = await this.masterPool.getConnection(); try { const [rows] = await connection.execute('SELECT * FROM competitions WHERE id = ?', [competitionId]); return (rows as any[])[0] || null; } finally { connection.release(); } } // 根據名稱獲取備機競賽 ID private async getSlaveCompetitionIdByName(name: string): Promise { if (!this.slavePool) return null; console.log(`🔍 getSlaveCompetitionIdByName 調用,名稱:`, name); const connection = await this.slavePool.getConnection(); try { const [rows] = await connection.execute('SELECT id FROM competitions WHERE name = ? ORDER BY created_at DESC LIMIT 1', [name]); console.log(`🔍 備機查詢結果:`, rows); const result = (rows as any[])[0]?.id || null; console.log(`🔍 備機競賽 ID 結果:`, result, typeof result); // 確保返回的是字符串 if (result && typeof result !== 'string') { console.log(`⚠️ 備機競賽 ID 不是字符串,轉換為字符串:`, String(result)); return String(result); } return result; } finally { connection.release(); } } // 寫入主機關聯表 private async insertRelationsToMaster( relationTable: string, competitionId: string, relationData: any[], relationIdField: string ): Promise { if (!this.masterPool) return; const connection = await this.masterPool.getConnection(); try { for (const data of relationData) { const [uuidResult] = await connection.execute('SELECT UUID() as id'); const relationId = (uuidResult as any)[0].id; const sql = `INSERT INTO ${relationTable} (id, competition_id, ${relationIdField}) VALUES (?, ?, ?)`; await connection.execute(sql, [relationId, competitionId, data[relationIdField]]); } } finally { connection.release(); } } // 寫入備機關聯表 private async insertRelationsToSlave( relationTable: string, competitionId: string, relationData: any[], relationIdField: string ): Promise { if (!this.slavePool) return; console.log(`🔍 備機關聯寫入開始: ${relationTable}`); console.log(` 備機競賽 ID: ${competitionId}`); console.log(` 關聯數據:`, relationData); console.log(` 關聯字段: ${relationIdField}`); const connection = await this.slavePool.getConnection(); try { for (const data of relationData) { const [uuidResult] = await connection.execute('SELECT UUID() as id'); const relationId = (uuidResult as any)[0].id; console.log(`🔍 準備插入關聯數據:`, { relationId, competitionId, relationField: relationIdField, relationValue: data[relationIdField] }); const sql = `INSERT INTO ${relationTable} (id, competition_id, ${relationIdField}) VALUES (?, ?, ?)`; console.log(`🔍 執行 SQL:`, sql); console.log(`🔍 參數:`, [relationId, competitionId, data[relationIdField]]); await connection.execute(sql, [relationId, competitionId, data[relationIdField]]); console.log(`✅ 備機關聯數據插入成功`); } } catch (error) { console.error(`❌ 備機關聯寫入失敗:`, error); throw error; } finally { connection.release(); } } // 清理資源 async close(): Promise { if (this.masterPool) { await this.masterPool.end(); } if (this.slavePool) { await this.slavePool.end(); } } } // 導出實例 export const dbSyncFixed = new DatabaseSyncFixed();