新增 競賽建立、評審建立、團隊建立

This commit is contained in:
2025-09-15 13:32:30 +08:00
parent b85a9ce95e
commit 31ffaa1974
31 changed files with 5163 additions and 455 deletions

View File

@@ -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('資料庫查詢失敗,已達到最大重試次數');
}
// 執行單一查詢