269 lines
8.0 KiB
TypeScript
269 lines
8.0 KiB
TypeScript
// =====================================================
|
||
// 資料庫連接配置 (整合備援功能)
|
||
// =====================================================
|
||
|
||
import mysql from 'mysql2/promise';
|
||
import { dbFailover } from './database-failover';
|
||
import { dbSync } from './database-sync';
|
||
|
||
// 資料庫配置
|
||
const dbConfig = {
|
||
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',
|
||
timezone: '+08:00',
|
||
acquireTimeout: 10000, // 10秒獲取連接超時
|
||
timeout: 10000, // 10秒查詢超時
|
||
reconnect: true,
|
||
connectionLimit: 5, // 減少連接數,避免 Too many connections
|
||
queueLimit: 10, // 允許排隊,避免立即失敗
|
||
// 添加連接重試和錯誤處理配置
|
||
retryDelay: 2000,
|
||
maxRetries: 3,
|
||
// 添加連接池配置
|
||
idleTimeout: 60000, // 1分鐘空閒超時,快速釋放連接
|
||
maxIdle: 5, // 最大空閒連接數
|
||
// 添加 SSL 配置(如果需要)
|
||
ssl: false as any,
|
||
};
|
||
|
||
// 創建連接池
|
||
const pool = mysql.createPool(dbConfig);
|
||
|
||
// 資料庫連接類 (整合備援功能)
|
||
export class Database {
|
||
private static instance: Database;
|
||
private pool: mysql.Pool;
|
||
private useFailover: boolean;
|
||
|
||
private constructor() {
|
||
this.pool = pool;
|
||
// 強制啟用備援功能,確保系統穩定性
|
||
this.useFailover = true;
|
||
console.log('🔄 資料庫備援功能已啟用');
|
||
}
|
||
|
||
public static getInstance(): Database {
|
||
if (!Database.instance) {
|
||
Database.instance = new Database();
|
||
}
|
||
return Database.instance;
|
||
}
|
||
|
||
// 獲取連接
|
||
public async getConnection(): Promise<mysql.PoolConnection> {
|
||
if (this.useFailover) {
|
||
return await dbFailover.getConnection();
|
||
}
|
||
return await this.pool.getConnection();
|
||
}
|
||
|
||
// 執行查詢
|
||
public async query<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||
if (this.useFailover) {
|
||
return await dbFailover.query<T>(sql, params);
|
||
}
|
||
|
||
let connection;
|
||
let retries = 0;
|
||
const maxRetries = 3;
|
||
|
||
while (retries < maxRetries) {
|
||
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);
|
||
|
||
if (connection) {
|
||
connection.release();
|
||
}
|
||
|
||
if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST') {
|
||
retries++;
|
||
if (retries < maxRetries) {
|
||
console.log(`等待 ${2000 * retries}ms 後重試...`);
|
||
await new Promise(resolve => setTimeout(resolve, 2000 * retries));
|
||
continue;
|
||
}
|
||
}
|
||
|
||
throw error;
|
||
} finally {
|
||
if (connection) {
|
||
connection.release();
|
||
}
|
||
}
|
||
}
|
||
|
||
throw new Error('資料庫連接失敗,已達到最大重試次數');
|
||
}
|
||
|
||
// 執行單一查詢
|
||
public async queryOne<T = any>(sql: string, params?: any[]): Promise<T | null> {
|
||
if (this.useFailover) {
|
||
return await dbFailover.queryOne<T>(sql, params);
|
||
}
|
||
|
||
try {
|
||
const results = await this.query<T>(sql, params);
|
||
return results.length > 0 ? results[0] : null;
|
||
} catch (error) {
|
||
console.error('資料庫單一查詢錯誤:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 執行插入(支援雙寫)
|
||
public async insert(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||
if (this.useFailover) {
|
||
// 檢查是否啟用雙寫
|
||
const syncStatus = await dbSync.getSyncStatus();
|
||
if (syncStatus.enabled) {
|
||
const result = await dbSync.dualInsert(sql, params);
|
||
if (result.success) {
|
||
// 雙寫成功,直接返回成功結果
|
||
// 不需要重新執行查詢,因為雙寫已經完成
|
||
return { insertId: 0, affectedRows: 1 } as mysql.ResultSetHeader;
|
||
} else {
|
||
// 雙寫失敗,拋出錯誤
|
||
throw new Error(`雙寫失敗: 主機${result.masterError || '成功'}, 備機${result.slaveError || '成功'}`);
|
||
}
|
||
} else {
|
||
return await dbFailover.insert(sql, params);
|
||
}
|
||
}
|
||
|
||
const connection = await this.getConnection();
|
||
try {
|
||
const [result] = await connection.execute(sql, params);
|
||
return result as mysql.ResultSetHeader;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
// 執行更新(支援雙寫)
|
||
public async update(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||
if (this.useFailover) {
|
||
// 檢查是否啟用雙寫
|
||
const syncStatus = await dbSync.getSyncStatus();
|
||
if (syncStatus.enabled) {
|
||
const result = await dbSync.dualUpdate(sql, params);
|
||
if (result.success) {
|
||
// 雙寫成功,直接返回成功結果
|
||
// 不需要重新執行查詢,因為雙寫已經完成
|
||
return { insertId: 0, affectedRows: 1 } as mysql.ResultSetHeader;
|
||
} else {
|
||
// 雙寫失敗,拋出錯誤
|
||
throw new Error(`雙寫失敗: 主機${result.masterError || '成功'}, 備機${result.slaveError || '成功'}`);
|
||
}
|
||
} else {
|
||
return await dbFailover.update(sql, params);
|
||
}
|
||
}
|
||
|
||
const connection = await this.getConnection();
|
||
try {
|
||
const [result] = await connection.execute(sql, params);
|
||
return result as mysql.ResultSetHeader;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
// 執行刪除(支援雙寫)
|
||
public async delete(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||
if (this.useFailover) {
|
||
// 檢查是否啟用雙寫
|
||
const syncStatus = await dbSync.getSyncStatus();
|
||
if (syncStatus.enabled) {
|
||
const result = await dbSync.dualDelete(sql, params);
|
||
if (result.success) {
|
||
// 雙寫成功,直接返回成功結果
|
||
// 不需要重新執行查詢,因為雙寫已經完成
|
||
return { insertId: 0, affectedRows: 1 } as mysql.ResultSetHeader;
|
||
} else {
|
||
// 雙寫失敗,拋出錯誤
|
||
throw new Error(`雙寫失敗: 主機${result.masterError || '成功'}, 備機${result.slaveError || '成功'}`);
|
||
}
|
||
} else {
|
||
return await dbFailover.delete(sql, params);
|
||
}
|
||
}
|
||
|
||
const connection = await this.getConnection();
|
||
try {
|
||
const [result] = await connection.execute(sql, params);
|
||
return result as mysql.ResultSetHeader;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
// 開始事務
|
||
public async beginTransaction(): Promise<mysql.PoolConnection> {
|
||
if (this.useFailover) {
|
||
return await dbFailover.beginTransaction();
|
||
}
|
||
|
||
const connection = await this.getConnection();
|
||
await connection.beginTransaction();
|
||
return connection;
|
||
}
|
||
|
||
// 提交事務
|
||
public async commit(connection: mysql.PoolConnection): Promise<void> {
|
||
if (this.useFailover) {
|
||
return await dbFailover.commit(connection);
|
||
}
|
||
|
||
await connection.commit();
|
||
connection.release();
|
||
}
|
||
|
||
// 回滾事務
|
||
public async rollback(connection: mysql.PoolConnection): Promise<void> {
|
||
if (this.useFailover) {
|
||
return await dbFailover.rollback(connection);
|
||
}
|
||
|
||
await connection.rollback();
|
||
connection.release();
|
||
}
|
||
|
||
// 獲取備援狀態
|
||
public getFailoverStatus() {
|
||
if (this.useFailover) {
|
||
return dbFailover.getStatus();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// 切換資料庫
|
||
public async switchDatabase(database: 'master' | 'slave'): Promise<boolean> {
|
||
if (this.useFailover) {
|
||
return await dbFailover.switchToDatabase(database);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 關閉連接池
|
||
public async close(): Promise<void> {
|
||
if (this.useFailover) {
|
||
await dbFailover.close();
|
||
}
|
||
await this.pool.end();
|
||
}
|
||
}
|
||
|
||
// 導出單例實例
|
||
export const db = Database.getInstance();
|
||
|
||
// 導出類型
|
||
export type { PoolConnection } from 'mysql2/promise';
|