新增 競賽建立、評審建立、團隊建立
This commit is contained in:
@@ -34,9 +34,12 @@ const masterConfig = {
|
||||
database: process.env.DB_NAME || 'db_AI_Platform',
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
idleTimeout: 300000,
|
||||
connectionLimit: 5, // 減少連接數,避免 Too many connections
|
||||
queueLimit: 10, // 允許排隊,避免立即失敗
|
||||
idleTimeout: 60000, // 1分鐘空閒超時,快速釋放連接
|
||||
acquireTimeout: 10000, // 10秒獲取連接超時
|
||||
timeout: 10000, // 10秒查詢超時
|
||||
reconnect: true, // 啟用重連
|
||||
ssl: false as any,
|
||||
};
|
||||
|
||||
@@ -49,9 +52,12 @@ const slaveConfig = {
|
||||
database: process.env.SLAVE_DB_NAME || 'db_AI_Platform', // 修正為 AI 平台資料庫
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
idleTimeout: 300000,
|
||||
connectionLimit: 5, // 減少連接數,避免 Too many connections
|
||||
queueLimit: 10, // 允許排隊,避免立即失敗
|
||||
idleTimeout: 60000, // 1分鐘空閒超時,快速釋放連接
|
||||
acquireTimeout: 10000, // 10秒獲取連接超時
|
||||
timeout: 10000, // 10秒查詢超時
|
||||
reconnect: true, // 啟用重連
|
||||
ssl: false as any,
|
||||
};
|
||||
|
||||
@@ -263,9 +269,15 @@ export class DatabaseFailover {
|
||||
await connection.ping();
|
||||
connection.release();
|
||||
this.status.masterHealthy = true;
|
||||
} catch (error) {
|
||||
console.log('✅ 主機資料庫健康檢查通過');
|
||||
} catch (error: any) {
|
||||
this.status.masterHealthy = false;
|
||||
console.error('主機資料庫健康檢查失敗:', error);
|
||||
console.error('❌ 主機資料庫健康檢查失敗:', error.message);
|
||||
|
||||
// 如果是 Too many connections 錯誤,強制標記為不健康
|
||||
if (error.code === 'ER_CON_COUNT_ERROR' || error.errno === 1040) {
|
||||
console.log('🚨 主機資料庫 Too many connections,強制切換到備機');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,9 +288,10 @@ export class DatabaseFailover {
|
||||
await connection.ping();
|
||||
connection.release();
|
||||
this.status.slaveHealthy = true;
|
||||
} catch (error) {
|
||||
console.log('✅ 備機資料庫健康檢查通過');
|
||||
} catch (error: any) {
|
||||
this.status.slaveHealthy = false;
|
||||
console.error('備機資料庫健康檢查失敗:', error);
|
||||
console.error('❌ 備機資料庫健康檢查失敗:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,9 +323,35 @@ export class DatabaseFailover {
|
||||
// 記錄狀態變化
|
||||
if (previousDatabase !== this.status.currentDatabase) {
|
||||
console.log(`📊 資料庫狀態變化: ${previousDatabase} → ${this.status.currentDatabase}`);
|
||||
console.log(`🔧 當前資料庫: ${this.status.currentDatabase}`);
|
||||
console.log(`📈 主機健康: ${this.status.masterHealthy ? '✅' : '❌'}`);
|
||||
console.log(`📈 備機健康: ${this.status.slaveHealthy ? '✅' : '❌'}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 強制切換到備機(緊急情況)
|
||||
public forceSwitchToSlave(): void {
|
||||
console.log('🚨 強制切換到備機資料庫');
|
||||
this.status.masterHealthy = false;
|
||||
this.status.currentDatabase = 'slave';
|
||||
this.status.consecutiveFailures++;
|
||||
console.log('✅ 已強制切換到備機');
|
||||
}
|
||||
|
||||
// 強制切換到主機
|
||||
public forceSwitchToMaster(): void {
|
||||
console.log('🔄 強制切換到主機資料庫');
|
||||
this.status.slaveHealthy = false;
|
||||
this.status.currentDatabase = 'master';
|
||||
this.status.consecutiveFailures = 0;
|
||||
console.log('✅ 已強制切換到主機');
|
||||
}
|
||||
|
||||
// 獲取當前資料庫
|
||||
public getCurrentDatabase(): 'master' | 'slave' {
|
||||
return this.status.currentDatabase;
|
||||
}
|
||||
|
||||
// 獲取當前連接池
|
||||
private getCurrentPool(): mysql.Pool | null {
|
||||
if (this.status.currentDatabase === 'master') {
|
||||
@@ -341,31 +380,68 @@ export class DatabaseFailover {
|
||||
|
||||
// 獲取連接
|
||||
public async getConnection(): Promise<mysql.PoolConnection> {
|
||||
const pool = this.getCurrentPool();
|
||||
if (!pool) {
|
||||
throw new Error('沒有可用的資料庫連接');
|
||||
}
|
||||
|
||||
let retries = 0;
|
||||
const maxRetries = parseInt(process.env.DB_RETRY_ATTEMPTS || '3');
|
||||
const retryDelay = parseInt(process.env.DB_RETRY_DELAY || '2000');
|
||||
|
||||
while (retries < maxRetries) {
|
||||
const pool = this.getCurrentPool();
|
||||
if (!pool) {
|
||||
throw new Error('沒有可用的資料庫連接');
|
||||
}
|
||||
|
||||
try {
|
||||
return await pool.getConnection();
|
||||
} catch (error: any) {
|
||||
console.error(`資料庫連接失敗 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
|
||||
console.error('錯誤詳情:', {
|
||||
code: error.code,
|
||||
errno: error.errno,
|
||||
sqlState: error.sqlState,
|
||||
sqlMessage: error.sqlMessage
|
||||
});
|
||||
|
||||
if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST') {
|
||||
// 嚴格處理 Too many connections 錯誤
|
||||
if (error.code === 'ER_CON_COUNT_ERROR' || error.errno === 1040) {
|
||||
console.log('🚨 檢測到 Too many connections,立即強制切換到備機');
|
||||
this.forceSwitchToSlave();
|
||||
|
||||
// 立即嘗試使用備機連接
|
||||
const slavePool = this.getSlavePool();
|
||||
if (slavePool) {
|
||||
try {
|
||||
console.log('🔄 嘗試使用備機連接...');
|
||||
return await slavePool.getConnection();
|
||||
} catch (slaveError: any) {
|
||||
console.error('❌ 備機連接也失敗:', slaveError.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 處理其他連接錯誤
|
||||
if (error.code === 'ECONNRESET' ||
|
||||
error.code === 'PROTOCOL_CONNECTION_LOST') {
|
||||
|
||||
console.log(`🔄 檢測到連接問題,觸發健康檢查並嘗試切換資料庫...`);
|
||||
|
||||
// 標記當前資料庫為不健康
|
||||
if (this.status.currentDatabase === 'master') {
|
||||
this.status.masterHealthy = false;
|
||||
console.log('❌ 主機資料庫標記為不健康');
|
||||
} else {
|
||||
this.status.slaveHealthy = false;
|
||||
console.log('❌ 備機資料庫標記為不健康');
|
||||
}
|
||||
|
||||
// 觸發健康檢查
|
||||
await this.performHealthCheck();
|
||||
|
||||
retries++;
|
||||
if (retries < maxRetries) {
|
||||
console.log(`等待 ${retryDelay}ms 後重試...`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
retries++;
|
||||
if (retries < maxRetries) {
|
||||
console.log(`等待 ${retryDelay}ms 後重試...`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
@@ -377,13 +453,80 @@ export class DatabaseFailover {
|
||||
|
||||
// 執行查詢
|
||||
public async query<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||
const connection = await this.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
return rows as T[];
|
||||
} finally {
|
||||
connection.release();
|
||||
let retries = 0;
|
||||
const maxRetries = parseInt(process.env.DB_RETRY_ATTEMPTS || '3');
|
||||
const retryDelay = parseInt(process.env.DB_RETRY_DELAY || '2000');
|
||||
|
||||
while (retries < maxRetries) {
|
||||
let connection;
|
||||
try {
|
||||
connection = await this.getConnection();
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
return rows as T[];
|
||||
} catch (error: any) {
|
||||
console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
|
||||
console.error('查詢錯誤詳情:', {
|
||||
code: error.code,
|
||||
errno: error.errno,
|
||||
sqlState: error.sqlState,
|
||||
sqlMessage: error.sqlMessage
|
||||
});
|
||||
|
||||
// 嚴格處理 Too many connections 錯誤
|
||||
if (error.code === 'ER_CON_COUNT_ERROR' || error.errno === 1040) {
|
||||
console.log('🚨 查詢時檢測到 Too many connections,立即強制切換到備機');
|
||||
this.forceSwitchToSlave();
|
||||
|
||||
// 立即嘗試使用備機連接
|
||||
const slavePool = this.getSlavePool();
|
||||
if (slavePool) {
|
||||
try {
|
||||
console.log('🔄 查詢時嘗試使用備機連接...');
|
||||
const slaveConnection = await slavePool.getConnection();
|
||||
const [rows] = await slaveConnection.execute(sql, params);
|
||||
slaveConnection.release();
|
||||
return rows as T[];
|
||||
} catch (slaveError: any) {
|
||||
console.error('❌ 備機查詢也失敗:', slaveError.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 處理其他連接錯誤
|
||||
if (error.code === 'ECONNRESET' ||
|
||||
error.code === 'PROTOCOL_CONNECTION_LOST') {
|
||||
|
||||
console.log(`🔄 查詢時檢測到連接問題,觸發健康檢查並嘗試切換資料庫...`);
|
||||
|
||||
// 標記當前資料庫為不健康
|
||||
if (this.status.currentDatabase === 'master') {
|
||||
this.status.masterHealthy = false;
|
||||
console.log('❌ 主機資料庫標記為不健康');
|
||||
} else {
|
||||
this.status.slaveHealthy = false;
|
||||
console.log('❌ 備機資料庫標記為不健康');
|
||||
}
|
||||
|
||||
// 觸發健康檢查
|
||||
await this.performHealthCheck();
|
||||
}
|
||||
|
||||
retries++;
|
||||
if (retries < maxRetries) {
|
||||
console.log(`等待 ${retryDelay}ms 後重試查詢...`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('資料庫查詢失敗,已達到最大重試次數');
|
||||
}
|
||||
|
||||
// 執行單一查詢
|
||||
|
Reference in New Issue
Block a user