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

383 lines
14 KiB
JavaScript
Raw 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();
}
}
// 智能雙寫關聯表 - 使用對應的競賽 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 {
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 close() {
if (this.masterPool) {
await this.masterPool.end();
}
if (this.slavePool) {
await this.slavePool.end();
}
}
}
// 導出實例
module.exports = { DatabaseSyncFixed };