Files
ai-showcase-platform/lib/database-sync-fixed.js

627 lines
22 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// =====================================================
// 修復的資料庫雙寫同步機制 (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 };