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

607 lines
21 KiB
TypeScript
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.

// =====================================================
// 修復的資料庫雙寫同步機制
// 確保主機和備機使用各自的 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<string, any>): Promise<WriteResult> {
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<string, any>): Promise<string> {
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<string, any>): Promise<string> {
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();
}
}
// 智能雙寫更新
async smartDualUpdate(tableName: string, id: string, updates: Record<string, any>): Promise<WriteResult> {
const result: WriteResult = {
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: string,
competitionId: string,
slaveCompetitionId: string,
relationData: any[],
relationIdField: string
): Promise<WriteResult> {
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<string | null> {
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<string | null> {
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<any> {
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();
}
}
// 獲取主機記錄
private async getMasterRecord(tableName: string, id: string): Promise<any> {
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 as any[])[0] || null;
} finally {
connection.release();
}
}
// 根據名稱獲取備機記錄 ID
private async getSlaveRecordIdByName(tableName: string, name: string): Promise<string | null> {
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 as any[])[0]?.id || null;
return result && typeof result !== 'string' ? String(result) : result;
} finally {
connection.release();
}
}
// 更新主機記錄
private async updateMasterRecord(tableName: string, id: string, updates: Record<string, any>): Promise<void> {
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();
}
}
// 更新備機記錄
private async updateSlaveRecord(tableName: string, id: string, updates: Record<string, any>): Promise<void> {
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();
}
}
// 根據名稱獲取備機競賽 ID
private async getSlaveCompetitionIdByName(name: string): Promise<string | null> {
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<void> {
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<void> {
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 clearAllCurrentCompetitions(): Promise<WriteResult> {
const result: WriteResult = {
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;
}
// 清除主機的所有當前競賽狀態
private async clearMasterCurrentCompetitions(): Promise<void> {
if (!this.masterPool) return;
const connection = await this.masterPool.getConnection();
try {
await connection.execute('UPDATE competitions SET is_current = FALSE');
} finally {
connection.release();
}
}
// 清除備機的所有當前競賽狀態
private async clearSlaveCurrentCompetitions(): Promise<void> {
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: string, id: string, idField: string = 'id'): Promise<WriteResult> {
const result: WriteResult = {
success: false,
masterSuccess: false,
slaveSuccess: false
};
try {
// 獲取備機對應的 ID
let slaveId: string | null = 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;
}
// 從主機刪除記錄
private async deleteFromMaster(tableName: string, id: string, idField: string = 'id'): Promise<void> {
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();
}
}
// 從備機刪除記錄
private async deleteFromSlave(tableName: string, id: string, idField: string = 'id'): Promise<void> {
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(): Promise<void> {
if (this.masterPool) {
await this.masterPool.end();
}
if (this.slavePool) {
await this.slavePool.end();
}
}
}
// 導出實例
export const dbSyncFixed = new DatabaseSyncFixed();