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

311 lines
9.3 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.

// =====================================================
// 資料庫連接配置 (整合備援功能)
// =====================================================
import mysql from 'mysql2/promise';
import { dbFailover } from './database-failover';
import { dbSync } from './database-sync';
// 資料庫配置
const dbConfig = {
host: process.env.DB_HOST || '122.100.99.161',
port: parseInt(process.env.DB_PORT || '43306'),
user: process.env.DB_USER || 'A999',
password: process.env.DB_PASSWORD || '1023',
database: process.env.DB_NAME || 'db_AI_Platform',
charset: 'utf8mb4',
timezone: '+08:00',
acquireTimeout: 5000, // 5秒獲取連接超時
timeout: 8000, // 8秒查詢超時
reconnect: true,
connectionLimit: 3, // 進一步減少連接數,避免 Too many connections
queueLimit: 5, // 減少排隊數量
// 添加連接重試和錯誤處理配置
retryDelay: 1000,
maxRetries: 2,
// 添加連接池配置
idleTimeout: 30000, // 30秒空閒超時快速釋放連接
maxIdle: 2, // 最大空閒連接數
// 添加連接生命週期管理
maxReconnects: 3,
reconnectDelay: 2000,
// 添加 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: mysql.PoolConnection | null = null;
let retries = 0;
const maxRetries = 2;
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) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
}
connection = null;
}
if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST' || error.code === 'ER_CON_COUNT_ERROR') {
retries++;
if (retries < maxRetries) {
console.log(`等待 ${1000 * retries}ms 後重試...`);
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
continue;
}
}
throw error;
} finally {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
}
}
}
}
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);
}
}
let connection: mysql.PoolConnection | null = null;
try {
connection = await this.getConnection();
const [result] = await connection.execute(sql, params);
return result as mysql.ResultSetHeader;
} catch (error) {
console.error('資料庫插入錯誤:', error);
throw error;
} finally {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
}
}
}
}
// 執行更新(支援雙寫)
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);
}
}
let connection: mysql.PoolConnection | null = null;
try {
connection = await this.getConnection();
const [result] = await connection.execute(sql, params);
return result as mysql.ResultSetHeader;
} catch (error) {
console.error('資料庫更新錯誤:', error);
throw error;
} finally {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
}
}
}
}
// 執行刪除(支援雙寫)
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);
}
}
let connection: mysql.PoolConnection | null = null;
try {
connection = await this.getConnection();
const [result] = await connection.execute(sql, params);
return result as mysql.ResultSetHeader;
} catch (error) {
console.error('資料庫刪除錯誤:', error);
throw error;
} finally {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
}
}
}
}
// 開始事務
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';