From 38ae30d611eaa1c88dd8835a21d7ec69e8dd93e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Sun, 21 Sep 2025 21:23:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=B3=87=E6=96=99=E5=BA=AB?= =?UTF-8?q?=E6=9C=AA=E9=97=9C=E9=96=89=E5=95=8F=E9=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/admin/connection-monitor/route.ts | 85 +++++++++ app/api/admin/force-cleanup/route.ts | 66 +++++++ app/api/admin/kill-connections/route.ts | 70 ++++++++ app/api/test-connection/route.ts | 67 +++++++ lib/connection-monitor.ts | 206 ++++++++++++++++++++++ lib/database-failover.ts | 58 +++++- lib/database-middleware.ts | 10 +- lib/database-service-base.ts | 144 ++++++++------- lib/database-shutdown-manager.ts | 27 +++ lib/database.ts | 19 +- lib/services/database-service.ts | 43 ++--- 11 files changed, 684 insertions(+), 111 deletions(-) create mode 100644 app/api/admin/connection-monitor/route.ts create mode 100644 app/api/admin/force-cleanup/route.ts create mode 100644 app/api/admin/kill-connections/route.ts create mode 100644 app/api/test-connection/route.ts create mode 100644 lib/connection-monitor.ts diff --git a/app/api/admin/connection-monitor/route.ts b/app/api/admin/connection-monitor/route.ts new file mode 100644 index 0000000..df53257 --- /dev/null +++ b/app/api/admin/connection-monitor/route.ts @@ -0,0 +1,85 @@ +// ===================================================== +// 連線監控 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { connectionMonitor } from '@/lib/connection-monitor'; + +export async function GET(request: NextRequest) { + try { + // 獲取連線統計 + const stats = await connectionMonitor.getConnectionStats(); + + // 獲取監控狀態 + const isMonitoring = connectionMonitor.isCurrentlyMonitoring(); + + return NextResponse.json({ + success: true, + data: { + stats, + isMonitoring, + timestamp: new Date().toISOString() + } + }); + + } catch (error) { + console.error('獲取連線監控數據失敗:', error); + return NextResponse.json({ + success: false, + message: '獲取連線監控數據失敗', + error: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { action } = body; + + switch (action) { + case 'start_monitoring': + const interval = body.interval || 30000; + connectionMonitor.startMonitoring(interval); + return NextResponse.json({ + success: true, + message: `連線監控已啟動,檢查間隔: ${interval}ms` + }); + + case 'stop_monitoring': + connectionMonitor.stopMonitoring(); + return NextResponse.json({ + success: true, + message: '連線監控已停止' + }); + + case 'force_cleanup': + await connectionMonitor.forceCleanup(); + return NextResponse.json({ + success: true, + message: '強制清理連線完成' + }); + + case 'check_health': + const healthStats = await connectionMonitor.checkConnectionHealth(); + return NextResponse.json({ + success: true, + data: healthStats + }); + + default: + return NextResponse.json({ + success: false, + message: '無效的操作' + }, { status: 400 }); + } + + } catch (error) { + console.error('執行連線監控操作失敗:', error); + return NextResponse.json({ + success: false, + message: '執行連線監控操作失敗', + error: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/admin/force-cleanup/route.ts b/app/api/admin/force-cleanup/route.ts new file mode 100644 index 0000000..456ee05 --- /dev/null +++ b/app/api/admin/force-cleanup/route.ts @@ -0,0 +1,66 @@ +// ===================================================== +// 強制清理連線 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { db } from '@/lib/database'; +import { connectionMonitor } from '@/lib/connection-monitor'; + +export async function POST(request: NextRequest) { + try { + console.log('🧹 開始強制清理資料庫連線...'); + + // 獲取清理前的連線狀態 + const beforeStats = await connectionMonitor.getConnectionStats(); + console.log(`清理前連線數: ${beforeStats.activeConnections}`); + + // 強制關閉連線池 + try { + await db.close(); + console.log('✅ 主要資料庫連線池已關閉'); + } catch (error) { + console.error('❌ 關閉主要連線池失敗:', error); + } + + // 等待一段時間讓連線完全關閉 + await new Promise(resolve => setTimeout(resolve, 2000)); + + // 重新初始化連線池 + try { + // 重新創建連線池實例 + const { Database } = await import('@/lib/database'); + const newDb = Database.getInstance(); + console.log('✅ 資料庫連線池已重新初始化'); + } catch (error) { + console.error('❌ 重新初始化連線池失敗:', error); + } + + // 獲取清理後的連線狀態 + const afterStats = await connectionMonitor.getConnectionStats(); + console.log(`清理後連線數: ${afterStats.activeConnections}`); + + return NextResponse.json({ + success: true, + message: '強制清理完成', + data: { + before: { + activeConnections: beforeStats.activeConnections, + usagePercentage: beforeStats.usagePercentage + }, + after: { + activeConnections: afterStats.activeConnections, + usagePercentage: afterStats.usagePercentage + }, + cleaned: beforeStats.activeConnections - afterStats.activeConnections + } + }); + + } catch (error) { + console.error('強制清理失敗:', error); + return NextResponse.json({ + success: false, + message: '強制清理失敗', + error: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/admin/kill-connections/route.ts b/app/api/admin/kill-connections/route.ts new file mode 100644 index 0000000..a2eab49 --- /dev/null +++ b/app/api/admin/kill-connections/route.ts @@ -0,0 +1,70 @@ +// ===================================================== +// 強制終止連線 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { db } from '@/lib/database'; + +export async function POST(request: NextRequest) { + try { + console.log('💀 開始強制終止所有資料庫連線...'); + + // 獲取所有 AI_Platform 的連線 + const connections = await db.query(` + SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE + FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE USER = 'AI_Platform' AND DB = 'db_AI_Platform' + `); + + console.log(`找到 ${connections.length} 個 AI_Platform 連線`); + + const killedConnections = []; + + // 終止每個連線 + for (const conn of connections) { + try { + await db.query(`KILL CONNECTION ${conn.ID}`); + killedConnections.push({ + id: conn.ID, + host: conn.HOST, + time: conn.TIME, + command: conn.COMMAND + }); + console.log(`✅ 已終止連線 ${conn.ID} (閒置 ${conn.TIME} 秒)`); + } catch (error) { + console.error(`❌ 終止連線 ${conn.ID} 失敗:`, error); + } + } + + // 等待連線完全關閉 + await new Promise(resolve => setTimeout(resolve, 2000)); + + // 檢查剩餘連線 + const remainingConnections = await db.query(` + SELECT COUNT(*) as count + FROM INFORMATION_SCHEMA.PROCESSLIST + WHERE USER = 'AI_Platform' AND DB = 'db_AI_Platform' + `); + + const remainingCount = remainingConnections[0]?.count || 0; + + return NextResponse.json({ + success: true, + message: '強制終止連線完成', + data: { + totalFound: connections.length, + killed: killedConnections.length, + remaining: remainingCount, + killedConnections: killedConnections + } + }); + + } catch (error) { + console.error('強制終止連線失敗:', error); + return NextResponse.json({ + success: false, + message: '強制終止連線失敗', + error: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/test-connection/route.ts b/app/api/test-connection/route.ts new file mode 100644 index 0000000..83cc7ea --- /dev/null +++ b/app/api/test-connection/route.ts @@ -0,0 +1,67 @@ +// ===================================================== +// 連線測試 API - 驗證連線釋放 +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { connectionMonitor } from '@/lib/connection-monitor'; + +export async function GET(request: NextRequest) { + try { + // 獲取測試前的連線狀態 + const beforeStats = await connectionMonitor.getConnectionStats(); + + // 執行一些測試查詢 + const testQueries = [ + 'SELECT 1 as test1', + 'SELECT 2 as test2', + 'SELECT 3 as test3', + 'SELECT COUNT(*) as user_count FROM users', + 'SELECT COUNT(*) as app_count FROM apps' + ]; + + console.log('🧪 開始連線測試...'); + console.log(`測試前連線數: ${beforeStats.activeConnections}`); + + // 執行測試查詢 + for (let i = 0; i < testQueries.length; i++) { + const { db } = await import('@/lib/database'); + await db.query(testQueries[i]); + console.log(`✅ 完成測試查詢 ${i + 1}`); + } + + // 等待一小段時間讓連線釋放 + await new Promise(resolve => setTimeout(resolve, 1000)); + + // 獲取測試後的連線狀態 + const afterStats = await connectionMonitor.getConnectionStats(); + + console.log(`測試後連線數: ${afterStats.activeConnections}`); + + return NextResponse.json({ + success: true, + message: '連線測試完成', + data: { + before: { + activeConnections: beforeStats.activeConnections, + usagePercentage: beforeStats.usagePercentage + }, + after: { + activeConnections: afterStats.activeConnections, + usagePercentage: afterStats.usagePercentage + }, + difference: { + connectionChange: afterStats.activeConnections - beforeStats.activeConnections, + isReleased: afterStats.activeConnections <= beforeStats.activeConnections + } + } + }); + + } catch (error) { + console.error('連線測試失敗:', error); + return NextResponse.json({ + success: false, + message: '連線測試失敗', + error: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/lib/connection-monitor.ts b/lib/connection-monitor.ts new file mode 100644 index 0000000..696859f --- /dev/null +++ b/lib/connection-monitor.ts @@ -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 { + 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 { + 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 { + 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(); diff --git a/lib/database-failover.ts b/lib/database-failover.ts index 5d9f0af..1208736 100644 --- a/lib/database-failover.ts +++ b/lib/database-failover.ts @@ -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 { - 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 { - 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 { - 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); + } + } } } diff --git a/lib/database-middleware.ts b/lib/database-middleware.ts index ed270b1..305de33 100644 --- a/lib/database-middleware.ts +++ b/lib/database-middleware.ts @@ -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( handler: (...args: T) => Promise @@ -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 }; diff --git a/lib/database-service-base.ts b/lib/database-service-base.ts index 9671d6c..ffd3689 100644 --- a/lib/database-service-base.ts +++ b/lib/database-service-base.ts @@ -6,7 +6,7 @@ import { db } from './database'; import { PoolConnection } from 'mysql2/promise'; export abstract class DatabaseServiceBase { - // 靜態安全查詢方法 - 確保連線關閉 + // 靜態安全查詢方法 - 每次查詢完就完全關閉連線 static async safeQuery(sql: string, params?: any[]): Promise { 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(sql: string, params?: any[]): Promise { 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 { 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 { 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 { 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 { 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 { 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 { 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( callback: (connection: PoolConnection) => Promise ): Promise { @@ -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( queries: Array<{ sql: string; params?: any[] }> ): Promise { @@ -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; diff --git a/lib/database-shutdown-manager.ts b/lib/database-shutdown-manager.ts index 8744b24..b784ab6 100644 --- a/lib/database-shutdown-manager.ts +++ b/lib/database-shutdown-manager.ts @@ -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; diff --git a/lib/database.ts b/lib/database.ts index 942d731..abf817b 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -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, }; diff --git a/lib/services/database-service.ts b/lib/services/database-service.ts index 57b741e..8955a6a 100644 --- a/lib/services/database-service.ts +++ b/lib/services/database-service.ts @@ -40,7 +40,7 @@ import type { // ===================================================== export class UserService extends DatabaseServiceBase { // 創建用戶 - async create(userData: Omit): Promise { + async create(userData: Omit & { id: string }): Promise { 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(sql: string, params: any[] = []): Promise { - return await db.query(sql, params); + return await this.safeQuery(sql, params); } // 獲取儀表板統計數據 @@ -764,21 +764,25 @@ export class UserService extends DatabaseServiceBase { } } - // 通用單一查詢方法 + // 通用單一查詢方法 - 使用安全查詢確保連線釋放 async queryOne(sql: string, params: any[] = []): Promise { - return await db.queryOne(sql, params); + return await this.safeQueryOne(sql, params); } // 獲取所有用戶 async getAllUsers(limit = 50, offset = 0): Promise { const sql = 'SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?'; - return await db.query(sql, [limit, offset]); + return await this.safeQuery(sql, [limit, offset]); } // 靜態方法保持向後兼容 static async createUser(userData: Omit & { id?: string }): Promise { 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 & { id: string }); } static async getUserByEmail(email: string): Promise { @@ -2394,14 +2398,14 @@ export class AppService extends DatabaseServiceBase { } } - // 通用查詢方法 + // 通用查詢方法 - 使用安全查詢確保連線釋放 async query(sql: string, params: any[] = []): Promise { - return await db.query(sql, params); + return await this.safeQuery(sql, params); } - // 通用單一查詢方法 + // 通用單一查詢方法 - 使用安全查詢確保連線釋放 async queryOne(sql: string, params: any[] = []): Promise { - return await db.queryOne(sql, params); + return await this.safeQueryOne(sql, params); } // 獲取應用評分統計 @@ -3269,23 +3273,6 @@ export class AppService extends DatabaseServiceBase { } } - // 獲取應用的收藏數量 - async getAppFavoritesCount(appId: string): Promise { - 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 { try {