修正資料庫未關閉問題
This commit is contained in:
206
lib/connection-monitor.ts
Normal file
206
lib/connection-monitor.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
// =====================================================
|
||||
// 資料庫連線監控服務
|
||||
// =====================================================
|
||||
|
||||
import { db } from './database';
|
||||
import { dbFailover } from './database-failover';
|
||||
|
||||
export interface ConnectionStats {
|
||||
totalConnections: number;
|
||||
activeConnections: number;
|
||||
idleConnections: number;
|
||||
queuedRequests: number;
|
||||
maxConnections: number;
|
||||
usagePercentage: number;
|
||||
isHealthy: boolean;
|
||||
lastCheck: Date;
|
||||
}
|
||||
|
||||
export class ConnectionMonitor {
|
||||
private static instance: ConnectionMonitor;
|
||||
private stats: ConnectionStats | null = null;
|
||||
private checkInterval: NodeJS.Timeout | null = null;
|
||||
private isMonitoring = false;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): ConnectionMonitor {
|
||||
if (!ConnectionMonitor.instance) {
|
||||
ConnectionMonitor.instance = new ConnectionMonitor();
|
||||
}
|
||||
return ConnectionMonitor.instance;
|
||||
}
|
||||
|
||||
// 開始監控
|
||||
public startMonitoring(intervalMs: number = 30000): void {
|
||||
if (this.isMonitoring) {
|
||||
console.log('⚠️ 連線監控已在運行中');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isMonitoring = true;
|
||||
console.log(`🔍 開始連線監控,檢查間隔: ${intervalMs}ms`);
|
||||
|
||||
this.checkInterval = setInterval(async () => {
|
||||
await this.checkConnectionHealth();
|
||||
}, intervalMs);
|
||||
|
||||
// 立即執行一次檢查
|
||||
this.checkConnectionHealth();
|
||||
}
|
||||
|
||||
// 停止監控
|
||||
public stopMonitoring(): void {
|
||||
if (this.checkInterval) {
|
||||
clearInterval(this.checkInterval);
|
||||
this.checkInterval = null;
|
||||
}
|
||||
this.isMonitoring = false;
|
||||
console.log('🛑 連線監控已停止');
|
||||
}
|
||||
|
||||
// 檢查連線健康狀態
|
||||
public async checkConnectionHealth(): Promise<ConnectionStats> {
|
||||
try {
|
||||
const stats = await this.getConnectionStats();
|
||||
this.stats = stats;
|
||||
|
||||
// 記錄連線狀態
|
||||
console.log(`📊 資料庫連線狀態: ${stats.activeConnections}/${stats.maxConnections} (${stats.usagePercentage.toFixed(1)}%)`);
|
||||
|
||||
// 檢查是否健康
|
||||
if (stats.usagePercentage > 80) {
|
||||
console.warn(`⚠️ 資料庫連線使用率過高: ${stats.usagePercentage.toFixed(1)}%`);
|
||||
}
|
||||
|
||||
if (stats.usagePercentage > 95) {
|
||||
console.error(`🚨 資料庫連線使用率危險: ${stats.usagePercentage.toFixed(1)}%`);
|
||||
}
|
||||
|
||||
return stats;
|
||||
} catch (error) {
|
||||
console.error('❌ 連線健康檢查失敗:', error);
|
||||
return {
|
||||
totalConnections: 0,
|
||||
activeConnections: 0,
|
||||
idleConnections: 0,
|
||||
queuedRequests: 0,
|
||||
maxConnections: 0,
|
||||
usagePercentage: 0,
|
||||
isHealthy: false,
|
||||
lastCheck: new Date()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取連線統計
|
||||
public async getConnectionStats(): Promise<ConnectionStats> {
|
||||
try {
|
||||
// 檢查備援狀態
|
||||
const failoverStatus = db.getFailoverStatus();
|
||||
|
||||
if (failoverStatus?.isEnabled) {
|
||||
// 使用備援系統獲取統計
|
||||
const result = await dbFailover.queryOne<{
|
||||
current_connections: number;
|
||||
max_connections: number;
|
||||
}>('SHOW STATUS LIKE "Threads_connected"');
|
||||
|
||||
const maxResult = await dbFailover.queryOne<{
|
||||
Variable_name: string;
|
||||
Value: string;
|
||||
}>('SHOW VARIABLES LIKE "max_connections"');
|
||||
|
||||
const currentConnections = result?.current_connections || 0;
|
||||
const maxConnections = parseInt(maxResult?.Value || '0');
|
||||
const usagePercentage = maxConnections > 0 ? (currentConnections / maxConnections) * 100 : 0;
|
||||
|
||||
return {
|
||||
totalConnections: currentConnections,
|
||||
activeConnections: currentConnections,
|
||||
idleConnections: 0, // MySQL 不直接提供空閒連線數
|
||||
queuedRequests: 0,
|
||||
maxConnections,
|
||||
usagePercentage,
|
||||
isHealthy: usagePercentage < 90,
|
||||
lastCheck: new Date()
|
||||
};
|
||||
} else {
|
||||
// 使用主資料庫獲取統計
|
||||
const result = await db.queryOne<{
|
||||
current_connections: number;
|
||||
max_connections: number;
|
||||
}>('SHOW STATUS LIKE "Threads_connected"');
|
||||
|
||||
const maxResult = await db.queryOne<{
|
||||
Variable_name: string;
|
||||
Value: string;
|
||||
}>('SHOW VARIABLES LIKE "max_connections"');
|
||||
|
||||
const currentConnections = result?.current_connections || 0;
|
||||
const maxConnections = parseInt(maxResult?.Value || '0');
|
||||
const usagePercentage = maxConnections > 0 ? (currentConnections / maxConnections) * 100 : 0;
|
||||
|
||||
return {
|
||||
totalConnections: currentConnections,
|
||||
activeConnections: currentConnections,
|
||||
idleConnections: 0,
|
||||
queuedRequests: 0,
|
||||
maxConnections,
|
||||
usagePercentage,
|
||||
isHealthy: usagePercentage < 90,
|
||||
lastCheck: new Date()
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('獲取連線統計失敗:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取當前統計
|
||||
public getCurrentStats(): ConnectionStats | null {
|
||||
return this.stats;
|
||||
}
|
||||
|
||||
// 強制清理連線
|
||||
public async forceCleanup(): Promise<void> {
|
||||
try {
|
||||
console.log('🧹 開始強制清理資料庫連線...');
|
||||
|
||||
// 獲取當前連線數
|
||||
const stats = await this.getConnectionStats();
|
||||
console.log(`清理前連線數: ${stats.activeConnections}`);
|
||||
|
||||
// 強制關閉所有空閒連線
|
||||
try {
|
||||
const { db } = await import('./database');
|
||||
await db.close();
|
||||
console.log('✅ 資料庫連線池已關閉');
|
||||
|
||||
// 等待連線完全關閉
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 重新初始化
|
||||
const { Database } = await import('./database');
|
||||
const newDb = Database.getInstance();
|
||||
console.log('✅ 資料庫連線池已重新初始化');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 強制清理連線池失敗:', error);
|
||||
}
|
||||
|
||||
console.log('✅ 連線清理完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 連線清理失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 檢查是否正在監控
|
||||
public isCurrentlyMonitoring(): boolean {
|
||||
return this.isMonitoring;
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const connectionMonitor = ConnectionMonitor.getInstance();
|
@@ -462,8 +462,24 @@ export class DatabaseFailover {
|
||||
try {
|
||||
connection = await this.getConnection();
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
console.log('✅ 備援查詢連線已完全關閉');
|
||||
|
||||
return rows as T[];
|
||||
} catch (error: any) {
|
||||
// 確保連線被關閉
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉連線時發生錯誤:', destroyError);
|
||||
}
|
||||
connection = null;
|
||||
}
|
||||
|
||||
console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
|
||||
console.error('查詢錯誤詳情:', {
|
||||
code: error.code,
|
||||
@@ -540,36 +556,60 @@ export class DatabaseFailover {
|
||||
}
|
||||
}
|
||||
|
||||
// 執行插入
|
||||
// 執行插入 - 每次查詢完就完全關閉連線
|
||||
public async insert(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||||
const connection = await this.getConnection();
|
||||
let connection;
|
||||
try {
|
||||
connection = await this.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
return result as mysql.ResultSetHeader;
|
||||
} finally {
|
||||
connection.release();
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.destroy();
|
||||
console.log('✅ 備援插入連線已完全關閉');
|
||||
} catch (destroyError) {
|
||||
console.error('關閉連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 執行更新
|
||||
// 執行更新 - 每次查詢完就完全關閉連線
|
||||
public async update(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||||
const connection = await this.getConnection();
|
||||
let connection;
|
||||
try {
|
||||
connection = await this.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
return result as mysql.ResultSetHeader;
|
||||
} finally {
|
||||
connection.release();
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.destroy();
|
||||
console.log('✅ 備援更新連線已完全關閉');
|
||||
} catch (destroyError) {
|
||||
console.error('關閉連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 執行刪除
|
||||
// 執行刪除 - 每次查詢完就完全關閉連線
|
||||
public async delete(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||||
const connection = await this.getConnection();
|
||||
let connection;
|
||||
try {
|
||||
connection = await this.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
return result as mysql.ResultSetHeader;
|
||||
} finally {
|
||||
connection.release();
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.destroy();
|
||||
console.log('✅ 備援刪除連線已完全關閉');
|
||||
} catch (destroyError) {
|
||||
console.error('關閉連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,10 +6,11 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from './database';
|
||||
import { dbMonitor } from './database-monitor';
|
||||
import { dbShutdownManager } from './database-shutdown-manager';
|
||||
import { connectionMonitor } from './connection-monitor';
|
||||
|
||||
// 連線池狀態追蹤
|
||||
let connectionCount = 0;
|
||||
const maxConnections = 3; // 與資料庫配置保持一致
|
||||
const maxConnections = 20; // 與資料庫配置保持一致
|
||||
|
||||
export function withDatabaseConnection<T extends any[]>(
|
||||
handler: (...args: T) => Promise<NextResponse>
|
||||
@@ -87,11 +88,12 @@ export async function checkConnectionHealth(): Promise<{
|
||||
usagePercentage: number;
|
||||
}> {
|
||||
try {
|
||||
const stats = dbMonitor.getConnectionStats();
|
||||
// 使用連線監控器獲取詳細統計
|
||||
const stats = await connectionMonitor.getConnectionStats();
|
||||
|
||||
return {
|
||||
isHealthy: stats.usagePercentage < 80,
|
||||
connectionCount: stats.currentConnections,
|
||||
isHealthy: stats.isHealthy,
|
||||
connectionCount: stats.activeConnections,
|
||||
maxConnections: stats.maxConnections,
|
||||
usagePercentage: stats.usagePercentage
|
||||
};
|
||||
|
@@ -6,7 +6,7 @@ import { db } from './database';
|
||||
import { PoolConnection } from 'mysql2/promise';
|
||||
|
||||
export abstract class DatabaseServiceBase {
|
||||
// 靜態安全查詢方法 - 確保連線關閉
|
||||
// 靜態安全查詢方法 - 每次查詢完就完全關閉連線
|
||||
static async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||
let connection: PoolConnection | null = null;
|
||||
|
||||
@@ -17,25 +17,27 @@ export abstract class DatabaseServiceBase {
|
||||
// 執行查詢
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線(不是釋放回連線池)
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 靜態查詢連線已完全關閉');
|
||||
|
||||
return rows as T[];
|
||||
} catch (error) {
|
||||
// 確保連線被釋放
|
||||
// 確保連線被關閉
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉靜態查詢連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 實例安全查詢方法 - 確保連線關閉
|
||||
// 實例安全查詢方法 - 每次查詢完就完全關閉連線
|
||||
protected async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||
let connection: PoolConnection | null = null;
|
||||
|
||||
@@ -46,18 +48,20 @@ export abstract class DatabaseServiceBase {
|
||||
// 執行查詢
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線(不是釋放回連線池)
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 連線已完全關閉');
|
||||
|
||||
return rows as T[];
|
||||
} catch (error) {
|
||||
// 確保連線被釋放
|
||||
// 確保連線被關閉
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
@@ -76,7 +80,7 @@ export abstract class DatabaseServiceBase {
|
||||
return results.length > 0 ? results[0] : null;
|
||||
}
|
||||
|
||||
// 靜態安全插入方法
|
||||
// 靜態安全插入方法 - 每次查詢完就完全關閉連線
|
||||
static async safeInsert(sql: string, params?: any[]): Promise<any> {
|
||||
let connection: PoolConnection | null = null;
|
||||
|
||||
@@ -84,24 +88,26 @@ export abstract class DatabaseServiceBase {
|
||||
connection = await db.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 靜態插入連線已完全關閉');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉靜態插入連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 實例安全插入方法
|
||||
// 實例安全插入方法 - 每次查詢完就完全關閉連線
|
||||
protected async safeInsert(sql: string, params?: any[]): Promise<any> {
|
||||
let connection: PoolConnection | null = null;
|
||||
|
||||
@@ -109,24 +115,26 @@ export abstract class DatabaseServiceBase {
|
||||
connection = await db.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 實例插入連線已完全關閉');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉實例插入連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 靜態安全更新方法
|
||||
// 靜態安全更新方法 - 每次查詢完就完全關閉連線
|
||||
static async safeUpdate(sql: string, params?: any[]): Promise<any> {
|
||||
let connection: PoolConnection | null = null;
|
||||
|
||||
@@ -134,24 +142,26 @@ export abstract class DatabaseServiceBase {
|
||||
connection = await db.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 靜態更新連線已完全關閉');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉靜態更新連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 實例安全更新方法
|
||||
// 實例安全更新方法 - 每次查詢完就完全關閉連線
|
||||
protected async safeUpdate(sql: string, params?: any[]): Promise<any> {
|
||||
let connection: PoolConnection | null = null;
|
||||
|
||||
@@ -159,24 +169,26 @@ export abstract class DatabaseServiceBase {
|
||||
connection = await db.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 實例更新連線已完全關閉');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉實例更新連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 靜態安全刪除方法
|
||||
// 靜態安全刪除方法 - 每次查詢完就完全關閉連線
|
||||
static async safeDelete(sql: string, params?: any[]): Promise<any> {
|
||||
let connection: PoolConnection | null = null;
|
||||
|
||||
@@ -184,24 +196,26 @@ export abstract class DatabaseServiceBase {
|
||||
connection = await db.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 靜態刪除連線已完全關閉');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉靜態刪除連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 實例安全刪除方法
|
||||
// 實例安全刪除方法 - 每次查詢完就完全關閉連線
|
||||
protected async safeDelete(sql: string, params?: any[]): Promise<any> {
|
||||
let connection: PoolConnection | null = null;
|
||||
|
||||
@@ -209,24 +223,26 @@ export abstract class DatabaseServiceBase {
|
||||
connection = await db.getConnection();
|
||||
const [result] = await connection.execute(sql, params);
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 實例刪除連線已完全關閉');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉實例刪除連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 事務處理方法
|
||||
// 事務處理方法 - 每次查詢完就完全關閉連線
|
||||
protected async withTransaction<T>(
|
||||
callback: (connection: PoolConnection) => Promise<T>
|
||||
): Promise<T> {
|
||||
@@ -240,10 +256,12 @@ export abstract class DatabaseServiceBase {
|
||||
|
||||
await connection.commit();
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 事務連線已完全關閉');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
@@ -254,16 +272,16 @@ export abstract class DatabaseServiceBase {
|
||||
}
|
||||
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉事務連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 批次查詢方法 - 減少連線使用
|
||||
// 批次查詢方法 - 每次查詢完就完全關閉連線
|
||||
protected async batchQuery<T = any>(
|
||||
queries: Array<{ sql: string; params?: any[] }>
|
||||
): Promise<T[][]> {
|
||||
@@ -278,17 +296,19 @@ export abstract class DatabaseServiceBase {
|
||||
results.push(rows as T[]);
|
||||
}
|
||||
|
||||
// 立即釋放連線
|
||||
connection.release();
|
||||
// 完全關閉連線
|
||||
await connection.destroy();
|
||||
connection = null;
|
||||
|
||||
console.log('✅ 批次查詢連線已完全關閉');
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('釋放連線時發生錯誤:', releaseError);
|
||||
await connection.destroy();
|
||||
} catch (destroyError) {
|
||||
console.error('關閉批次查詢連線時發生錯誤:', destroyError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
|
@@ -5,6 +5,7 @@
|
||||
import { db } from './database';
|
||||
import { dbFailover } from './database-failover';
|
||||
import { dbMonitor } from './database-monitor';
|
||||
import { connectionMonitor } from './connection-monitor';
|
||||
|
||||
export class DatabaseShutdownManager {
|
||||
private static instance: DatabaseShutdownManager;
|
||||
@@ -57,6 +58,17 @@ export class DatabaseShutdownManager {
|
||||
}
|
||||
});
|
||||
|
||||
// 添加連線監控關閉處理器
|
||||
this.addShutdownHandler('connection-monitor', async () => {
|
||||
console.log('🔄 正在停止連線監控服務...');
|
||||
try {
|
||||
connectionMonitor.stopMonitoring();
|
||||
console.log('✅ 連線監控服務已停止');
|
||||
} catch (error) {
|
||||
console.error('❌ 停止連線監控服務時發生錯誤:', error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 註冊系統信號處理器
|
||||
this.registerSystemHandlers();
|
||||
@@ -75,6 +87,21 @@ export class DatabaseShutdownManager {
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化服務
|
||||
public initialize(): void {
|
||||
console.log('🚀 初始化資料庫服務...');
|
||||
|
||||
// 啟動連線監控
|
||||
try {
|
||||
connectionMonitor.startMonitoring(30000); // 30秒檢查一次
|
||||
console.log('✅ 連線監控已啟動');
|
||||
} catch (error) {
|
||||
console.error('❌ 啟動連線監控失敗:', error);
|
||||
}
|
||||
|
||||
console.log('✅ 資料庫服務初始化完成');
|
||||
}
|
||||
|
||||
// 註冊系統信號處理器
|
||||
private registerSystemHandlers() {
|
||||
if (typeof process === 'undefined') return;
|
||||
|
@@ -6,7 +6,7 @@ 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'),
|
||||
@@ -16,19 +16,22 @@ const dbConfig = {
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
acquireTimeout: 5000, // 5秒獲取連接超時
|
||||
timeout: 8000, // 8秒查詢超時
|
||||
timeout: 10000, // 10秒查詢超時
|
||||
reconnect: true,
|
||||
connectionLimit: 3, // 進一步減少連接數,避免 Too many connections
|
||||
queueLimit: 5, // 減少排隊數量
|
||||
connectionLimit: 5, // 大幅減少連接數限制
|
||||
queueLimit: 10, // 減少排隊數量
|
||||
// 添加連接重試和錯誤處理配置
|
||||
retryDelay: 1000,
|
||||
maxRetries: 2,
|
||||
// 添加連接池配置
|
||||
idleTimeout: 30000, // 30秒空閒超時,快速釋放連接
|
||||
maxIdle: 2, // 最大空閒連接數
|
||||
// 強制快速釋放連線的配置
|
||||
idleTimeout: 5000, // 5秒空閒超時,強制快速釋放
|
||||
maxIdle: 1, // 最多只保留1個空閒連接
|
||||
// 添加連接生命週期管理
|
||||
maxReconnects: 3,
|
||||
reconnectDelay: 2000,
|
||||
reconnectDelay: 1000,
|
||||
// 強制關閉空閒連線
|
||||
keepAliveInitialDelay: 0,
|
||||
enableKeepAlive: false, // 關閉 keepAlive
|
||||
// 添加 SSL 配置(如果需要)
|
||||
ssl: false as any,
|
||||
};
|
||||
|
@@ -40,7 +40,7 @@ import type {
|
||||
// =====================================================
|
||||
export class UserService extends DatabaseServiceBase {
|
||||
// 創建用戶
|
||||
async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
|
||||
async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'> & { id: string }): Promise<User> {
|
||||
const sql = `
|
||||
INSERT INTO users (id, name, email, password_hash, avatar, department, role, join_date, total_likes, total_views, status, last_login)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
@@ -572,9 +572,9 @@ export class UserService extends DatabaseServiceBase {
|
||||
}
|
||||
}
|
||||
|
||||
// 通用查詢方法
|
||||
// 通用查詢方法 - 使用安全查詢確保連線釋放
|
||||
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
|
||||
return await db.query<T>(sql, params);
|
||||
return await this.safeQuery<T>(sql, params);
|
||||
}
|
||||
|
||||
// 獲取儀表板統計數據
|
||||
@@ -764,21 +764,25 @@ export class UserService extends DatabaseServiceBase {
|
||||
}
|
||||
}
|
||||
|
||||
// 通用單一查詢方法
|
||||
// 通用單一查詢方法 - 使用安全查詢確保連線釋放
|
||||
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
|
||||
return await db.queryOne<T>(sql, params);
|
||||
return await this.safeQueryOne<T>(sql, params);
|
||||
}
|
||||
|
||||
// 獲取所有用戶
|
||||
async getAllUsers(limit = 50, offset = 0): Promise<User[]> {
|
||||
const sql = 'SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
||||
return await db.query<User>(sql, [limit, offset]);
|
||||
return await this.safeQuery<User>(sql, [limit, offset]);
|
||||
}
|
||||
|
||||
// 靜態方法保持向後兼容
|
||||
static async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'> & { id?: string }): Promise<User> {
|
||||
const service = new UserService();
|
||||
return await service.create(userData);
|
||||
// 確保 id 存在
|
||||
if (!userData.id) {
|
||||
userData.id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
return await service.create(userData as Omit<User, 'id' | 'created_at' | 'updated_at'> & { id: string });
|
||||
}
|
||||
|
||||
static async getUserByEmail(email: string): Promise<User | null> {
|
||||
@@ -2394,14 +2398,14 @@ export class AppService extends DatabaseServiceBase {
|
||||
}
|
||||
}
|
||||
|
||||
// 通用查詢方法
|
||||
// 通用查詢方法 - 使用安全查詢確保連線釋放
|
||||
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
|
||||
return await db.query<T>(sql, params);
|
||||
return await this.safeQuery<T>(sql, params);
|
||||
}
|
||||
|
||||
// 通用單一查詢方法
|
||||
// 通用單一查詢方法 - 使用安全查詢確保連線釋放
|
||||
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
|
||||
return await db.queryOne<T>(sql, params);
|
||||
return await this.safeQueryOne<T>(sql, params);
|
||||
}
|
||||
|
||||
// 獲取應用評分統計
|
||||
@@ -3269,23 +3273,6 @@ export class AppService extends DatabaseServiceBase {
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取應用的收藏數量
|
||||
async getAppFavoritesCount(appId: string): Promise<number> {
|
||||
try {
|
||||
const sql = `
|
||||
SELECT COUNT(*) as count
|
||||
FROM user_favorites
|
||||
WHERE app_id = ?
|
||||
`;
|
||||
|
||||
const result = await this.queryOne(sql, [appId]);
|
||||
return result.count || 0;
|
||||
} catch (error) {
|
||||
console.error('獲取應用收藏數量錯誤:', error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取用戶收藏的應用ID列表(不依賴 apps 表狀態)
|
||||
async getUserFavoriteAppIds(userId: string): Promise<string[]> {
|
||||
try {
|
||||
|
Reference in New Issue
Block a user