// ===================================================== // 修復的資料庫雙寫同步機制 (JavaScript 版本) // 確保主機和備機使用各自的 ID 序列 // ===================================================== const mysql = require('mysql2/promise'); class DatabaseSyncFixed { constructor() { this.masterPool = null; this.slavePool = null; this.initializePools(); } // 初始化連接池 initializePools() { 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 }); // 備機連接池 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 }); console.log('✅ 修復的雙寫連接池初始化成功'); } catch (error) { console.error('❌ 修復的雙寫連接池初始化失敗:', error); } } // 智能雙寫插入 - 每個資料庫使用自己的 ID async smartDualInsert(tableName, data) { const result = { 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 生成 async insertToMaster(tableName, data) { 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[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 生成 async insertToSlave(tableName, data) { 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[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(); } } // 智能雙寫更新 async smartDualUpdate(tableName, id, updates) { const result = { success: false, masterSuccess: false, slaveSuccess: false }; try { // 先獲取主機的記錄 const masterRecord = await this.getMasterRecord(tableName, id); if (!masterRecord) { throw new Error('主機記錄不存在'); } // 獲取備機的記錄 ID(通過名稱匹配) const slaveId = await this.getSlaveRecordIdByName(tableName, masterRecord.name); if (!slaveId) { throw new Error('備機記錄不存在'); } // 同時更新主機和備機 const masterPromise = this.updateMasterRecord(tableName, id, updates); const slavePromise = this.updateSlaveRecord(tableName, slaveId, updates); 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 : '主機更新失敗'; } if (slaveResult.status === 'rejected') { result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機更新失敗'; } } catch (error) { result.masterError = error instanceof Error ? error.message : '雙寫更新執行失敗'; } return result; } // 智能雙寫關聯表 - 使用對應的競賽 ID async smartDualInsertRelation(relationTable, masterCompetitionId, slaveCompetitionId, relationData, relationIdField) { const result = { success: false, masterSuccess: false, slaveSuccess: false }; try { console.log(`🔍 關聯雙寫開始: ${relationTable}`); console.log(` 主機競賽 ID: ${masterCompetitionId}`); console.log(` 備機競賽 ID: ${slaveCompetitionId}`); console.log(` 關聯數據數量: ${relationData.length}`); // 先驗證競賽 ID 是否存在 const masterExists = await this.verifyCompetitionExists(masterCompetitionId, 'master'); const slaveExists = await this.verifyCompetitionExists(slaveCompetitionId, 'slave'); console.log(` 主機競賽存在: ${masterExists}`); console.log(` 備機競賽存在: ${slaveExists}`); if (!masterExists) { result.masterError = '主機競賽不存在'; result.slaveError = '主機競賽不存在,跳過備機寫入'; console.log(`📝 關聯雙寫結果: 主機❌ 備機❌`); return result; } if (!slaveExists) { result.masterError = '備機競賽不存在'; result.slaveError = '備機競賽不存在'; console.log(`📝 關聯雙寫結果: 主機❌ 備機❌`); return result; } // 同時寫入關聯數據 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 : '主機關聯寫入失敗'; } if (slaveResult.status === 'rejected') { 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; } // 驗證競賽是否存在 async verifyCompetitionExists(competitionId, database) { try { const pool = database === 'master' ? this.masterPool : this.slavePool; if (!pool) return false; const connection = await pool.getConnection(); try { const [rows] = await connection.execute('SELECT COUNT(*) as count FROM competitions WHERE id = ?', [competitionId]); return rows[0].count > 0; } finally { connection.release(); } } catch (error) { console.error(`驗證${database}競賽存在失敗:`, error.message); return false; } } // 獲取備機的評審 ID(通過名稱匹配) async getSlaveJudgeId(masterJudgeId) { try { if (!this.slavePool) return masterJudgeId; const connection = await this.slavePool.getConnection(); try { // 先獲取主機評審的名稱 const masterConn = await this.masterPool.getConnection(); const [masterJudge] = await masterConn.execute('SELECT name FROM judges WHERE id = ?', [masterJudgeId]); masterConn.release(); if (masterJudge.length === 0) return masterJudgeId; // 在備機中查找相同名稱的評審 const [slaveJudges] = await connection.execute('SELECT id FROM judges WHERE name = ? ORDER BY created_at ASC LIMIT 1', [masterJudge[0].name]); return slaveJudges.length > 0 ? slaveJudges[0].id : masterJudgeId; } finally { connection.release(); } } catch (error) { console.error('獲取備機評審 ID 失敗:', error); return masterJudgeId; } } // 獲取備機的團隊 ID(通過名稱匹配) async getSlaveTeamId(masterTeamId) { try { if (!this.slavePool) return masterTeamId; const connection = await this.slavePool.getConnection(); try { // 先獲取主機團隊的名稱 const masterConn = await this.masterPool.getConnection(); const [masterTeam] = await masterConn.execute('SELECT name FROM teams WHERE id = ?', [masterTeamId]); masterConn.release(); if (masterTeam.length === 0) return masterTeamId; // 在備機中查找相同名稱的團隊 const [slaveTeams] = await connection.execute('SELECT id FROM teams WHERE name = ? ORDER BY created_at ASC LIMIT 1', [masterTeam[0].name]); return slaveTeams.length > 0 ? slaveTeams[0].id : masterTeamId; } finally { connection.release(); } } catch (error) { console.error('獲取備機團隊 ID 失敗:', error); return masterTeamId; } } // 寫入主機關聯表 async insertRelationsToMaster(relationTable, competitionId, relationData, relationIdField) { 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[0].id; if (relationTable === 'competition_award_types') { // 特殊處理獎項類型 const sql = `INSERT INTO ${relationTable} (id, competition_id, name, description, icon, color, order_index) VALUES (?, ?, ?, ?, ?, ?, ?)`; await connection.execute(sql, [ relationId, competitionId, data.name, data.description || '', data.icon || '🏆', data.color || 'text-yellow-600', data.order_index || 0 ]); } else if (relationTable === 'competition_rules') { // 特殊處理評分規則 const sql = `INSERT INTO ${relationTable} (id, competition_id, name, description, weight, order_index) VALUES (?, ?, ?, ?, ?, ?)`; await connection.execute(sql, [ relationId, competitionId, data.name, data.description || '', data.weight || 0, data.order_index || 0 ]); } else { // 一般關聯表 const sql = `INSERT INTO ${relationTable} (id, competition_id, ${relationIdField}) VALUES (?, ?, ?)`; await connection.execute(sql, [relationId, competitionId, data[relationIdField]]); } } } finally { connection.release(); } } // 寫入備機關聯表 async insertRelationsToSlave(relationTable, competitionId, relationData, relationIdField) { if (!this.slavePool) return; const connection = await this.slavePool.getConnection(); try { // 先刪除現有的關聯數據 await connection.execute(`DELETE FROM ${relationTable} WHERE competition_id = ?`, [competitionId]); for (const data of relationData) { const [uuidResult] = await connection.execute('SELECT UUID() as id'); const relationId = uuidResult[0].id; if (relationTable === 'competition_award_types') { // 特殊處理獎項類型 const sql = `INSERT INTO ${relationTable} (id, competition_id, name, description, icon, color, order_index) VALUES (?, ?, ?, ?, ?, ?, ?)`; await connection.execute(sql, [ relationId, competitionId, data.name, data.description || '', data.icon || '🏆', data.color || 'text-yellow-600', data.order_index || 0 ]); } else if (relationTable === 'competition_rules') { // 特殊處理評分規則 const sql = `INSERT INTO ${relationTable} (id, competition_id, name, description, weight, order_index) VALUES (?, ?, ?, ?, ?, ?)`; await connection.execute(sql, [ relationId, competitionId, data.name, data.description || '', data.weight || 0, data.order_index || 0 ]); } else { // 一般關聯表 - 處理 ID 映射 let slaveId = data[relationIdField]; if (relationTable === 'competition_judges' && relationIdField === 'judge_id') { slaveId = await this.getSlaveJudgeId(data[relationIdField]); } else if (relationTable === 'competition_teams' && relationIdField === 'team_id') { slaveId = await this.getSlaveTeamId(data[relationIdField]); } const sql = `INSERT INTO ${relationTable} (id, competition_id, ${relationIdField}) VALUES (?, ?, ?)`; await connection.execute(sql, [relationId, competitionId, slaveId]); } } } finally { connection.release(); } } // 獲取主機記錄 async getMasterRecord(tableName, id) { if (!this.masterPool) return null; const connection = await this.masterPool.getConnection(); try { const [rows] = await connection.execute(`SELECT * FROM ${tableName} WHERE id = ?`, [id]); return rows[0] || null; } finally { connection.release(); } } // 根據名稱獲取備機記錄 ID async getSlaveRecordIdByName(tableName, name) { if (!this.slavePool) return null; const connection = await this.slavePool.getConnection(); try { const [rows] = await connection.execute(`SELECT id FROM ${tableName} WHERE name = ? ORDER BY created_at DESC LIMIT 1`, [name]); const result = rows[0]?.id || null; return result && typeof result !== 'string' ? String(result) : result; } finally { connection.release(); } } // 更新主機記錄 async updateMasterRecord(tableName, id, updates) { if (!this.masterPool) return; const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at'); if (fields.length === 0) return; const setClause = fields.map(field => `${field} = ?`).join(', '); const values = fields.map(field => updates[field]); const connection = await this.masterPool.getConnection(); try { const sql = `UPDATE ${tableName} SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; await connection.execute(sql, [...values, id]); } finally { connection.release(); } } // 更新備機記錄 async updateSlaveRecord(tableName, id, updates) { if (!this.slavePool) return; const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at'); if (fields.length === 0) return; const setClause = fields.map(field => `${field} = ?`).join(', '); const values = fields.map(field => updates[field]); const connection = await this.slavePool.getConnection(); try { const sql = `UPDATE ${tableName} SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; await connection.execute(sql, [...values, id]); } finally { connection.release(); } } // 清除所有競賽的當前狀態 async clearAllCurrentCompetitions() { const result = { success: false, masterSuccess: false, slaveSuccess: false }; try { // 同時清除主機和備機的所有當前競賽狀態 const masterPromise = this.clearMasterCurrentCompetitions(); const slavePromise = this.clearSlaveCurrentCompetitions(); 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 : '主機清除失敗'; } if (slaveResult.status === 'rejected') { result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機清除失敗'; } } catch (error) { result.masterError = error instanceof Error ? error.message : '清除所有當前競賽執行失敗'; } return result; } // 清除主機的所有當前競賽狀態 async clearMasterCurrentCompetitions() { if (!this.masterPool) return; const connection = await this.masterPool.getConnection(); try { await connection.execute('UPDATE competitions SET is_current = FALSE'); } finally { connection.release(); } } // 清除備機的所有當前競賽狀態 async clearSlaveCurrentCompetitions() { if (!this.slavePool) return; const connection = await this.slavePool.getConnection(); try { await connection.execute('UPDATE competitions SET is_current = FALSE'); } finally { connection.release(); } } // 智能雙寫刪除 async smartDualDelete(tableName, id, idField = 'id') { const result = { success: false, masterSuccess: false, slaveSuccess: false }; try { // 獲取備機對應的 ID let slaveId = null; if (tableName === 'competitions') { // 對於競賽表,先獲取競賽名稱 const masterRecord = await this.getMasterRecord(tableName, id); if (masterRecord) { slaveId = await this.getSlaveRecordIdByName(tableName, masterRecord.name); } } else { // 對於關聯表,直接使用主機 ID slaveId = id; } // 同時刪除主機和備機的記錄 const masterPromise = this.deleteFromMaster(tableName, id, idField); const slavePromise = slaveId ? this.deleteFromSlave(tableName, slaveId, idField) : Promise.resolve(); 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 : '主機刪除失敗'; } if (slaveResult.status === 'rejected') { result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機刪除失敗'; } } catch (error) { result.masterError = error instanceof Error ? error.message : '雙寫刪除執行失敗'; } return result; } // 從主機刪除記錄 async deleteFromMaster(tableName, id, idField = 'id') { if (!this.masterPool) return; const connection = await this.masterPool.getConnection(); try { const sql = `DELETE FROM ${tableName} WHERE ${idField} = ?`; await connection.execute(sql, [id]); } finally { connection.release(); } } // 從備機刪除記錄 async deleteFromSlave(tableName, id, idField = 'id') { if (!this.slavePool) return; const connection = await this.slavePool.getConnection(); try { const sql = `DELETE FROM ${tableName} WHERE ${idField} = ?`; await connection.execute(sql, [id]); } finally { connection.release(); } } // 清理資源 async close() { if (this.masterPool) { await this.masterPool.end(); } if (this.slavePool) { await this.slavePool.end(); } } } // 導出實例 module.exports = { DatabaseSyncFixed };