修復 too many connection 問題
This commit is contained in:
39
lib/app-initializer.ts
Normal file
39
lib/app-initializer.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// =====================================================
|
||||
// 應用程式初始化器
|
||||
// =====================================================
|
||||
|
||||
import { dbShutdownManager } from './database-shutdown-manager';
|
||||
import { startConnectionMonitoring } from './database-middleware';
|
||||
import { smartPool } from './smart-connection-pool';
|
||||
|
||||
// 應用程式初始化
|
||||
export function initializeApp() {
|
||||
console.log('🚀 正在初始化應用程式...');
|
||||
|
||||
try {
|
||||
// 初始化資料庫關閉管理器
|
||||
console.log('📋 初始化資料庫關閉管理器...');
|
||||
const shutdownStatus = dbShutdownManager.getShutdownStatus();
|
||||
console.log('✅ 資料庫關閉管理器已初始化:', shutdownStatus);
|
||||
|
||||
// 啟動資料庫連線監控
|
||||
console.log('📊 啟動資料庫連線監控...');
|
||||
startConnectionMonitoring();
|
||||
|
||||
// 初始化智能連線池
|
||||
console.log('🧠 初始化智能連線池...');
|
||||
const poolStats = smartPool.getConnectionStats();
|
||||
console.log('✅ 智能連線池已初始化:', poolStats);
|
||||
|
||||
console.log('✅ 應用程式初始化完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 應用程式初始化失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 在模組載入時自動初始化
|
||||
if (typeof window === 'undefined') {
|
||||
// 只在伺服器端執行
|
||||
initializeApp();
|
||||
}
|
231
lib/client-connection-cleanup.ts
Normal file
231
lib/client-connection-cleanup.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
// =====================================================
|
||||
// 客戶端連線清理腳本
|
||||
// =====================================================
|
||||
|
||||
export class ClientConnectionCleanup {
|
||||
private static instance: ClientConnectionCleanup;
|
||||
private isCleaning = false;
|
||||
private clientId: string;
|
||||
|
||||
private constructor() {
|
||||
this.clientId = this.generateClientId();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
public static getInstance(): ClientConnectionCleanup {
|
||||
if (!ClientConnectionCleanup.instance) {
|
||||
ClientConnectionCleanup.instance = new ClientConnectionCleanup();
|
||||
}
|
||||
return ClientConnectionCleanup.instance;
|
||||
}
|
||||
|
||||
// 生成客戶端唯一標識符
|
||||
private generateClientId(): string {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.random().toString(36).substring(2);
|
||||
return `client_${timestamp}_${random}`;
|
||||
}
|
||||
|
||||
// 設置事件監聽器
|
||||
private setupEventListeners() {
|
||||
// 確保在瀏覽器環境中執行
|
||||
if (typeof window === 'undefined') {
|
||||
console.log('🖥️ 客戶端連線清理跳過(服務端環境)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 頁面關閉前清理
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this.cleanupOnUnload();
|
||||
});
|
||||
|
||||
// 頁面隱藏時清理
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
this.cleanupOnHidden();
|
||||
}
|
||||
});
|
||||
|
||||
// 頁面卸載時清理
|
||||
window.addEventListener('unload', () => {
|
||||
this.cleanupOnUnload();
|
||||
});
|
||||
|
||||
// 定期清理(每5分鐘)
|
||||
setInterval(() => {
|
||||
this.periodicCleanup();
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
console.log('🖥️ 客戶端連線清理已啟動');
|
||||
}
|
||||
|
||||
// 頁面關閉前清理
|
||||
private async cleanupOnUnload() {
|
||||
if (this.isCleaning) return;
|
||||
|
||||
// 確保在瀏覽器環境中執行
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
this.isCleaning = true;
|
||||
console.log('🔄 頁面關閉前清理連線...');
|
||||
|
||||
try {
|
||||
// 使用 sendBeacon 確保請求能夠發送
|
||||
const data = JSON.stringify({
|
||||
action: 'cleanup-current',
|
||||
clientId: this.clientId,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// 嘗試使用 sendBeacon
|
||||
if (navigator.sendBeacon) {
|
||||
navigator.sendBeacon('/api/ip-cleanup', data);
|
||||
console.log('✅ 使用 sendBeacon 發送清理請求');
|
||||
} else {
|
||||
// 備用方案:使用 fetch
|
||||
await fetch('/api/ip-cleanup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: data,
|
||||
keepalive: true
|
||||
});
|
||||
console.log('✅ 使用 fetch 發送清理請求');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 清理請求發送失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 頁面隱藏時清理
|
||||
private async cleanupOnHidden() {
|
||||
// 確保在瀏覽器環境中執行
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
console.log('🔄 頁面隱藏時清理連線...');
|
||||
|
||||
try {
|
||||
await fetch('/api/ip-cleanup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'cleanup-current',
|
||||
clientId: this.clientId,
|
||||
reason: 'page-hidden'
|
||||
})
|
||||
});
|
||||
console.log('✅ 頁面隱藏清理完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 頁面隱藏清理失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 定期清理
|
||||
private async periodicCleanup() {
|
||||
// 確保在瀏覽器環境中執行
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
console.log('🔄 定期清理連線...');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ip-cleanup?action=local-stats');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.data.trackedConnections > 10) {
|
||||
// 如果追蹤的連線數超過10個,執行清理
|
||||
await fetch('/api/ip-cleanup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'cleanup-local',
|
||||
clientId: this.clientId,
|
||||
reason: 'periodic-cleanup'
|
||||
})
|
||||
});
|
||||
console.log('✅ 定期清理完成');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 定期清理失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 手動清理
|
||||
public async manualCleanup(): Promise<boolean> {
|
||||
// 確保在瀏覽器環境中執行
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ip-cleanup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'cleanup-current',
|
||||
clientId: this.clientId,
|
||||
reason: 'manual-cleanup'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
console.log('✅ 手動清理完成:', data.message);
|
||||
return true;
|
||||
} else {
|
||||
console.error('❌ 手動清理失敗:', data.error);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 手動清理錯誤:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取連線狀態
|
||||
public async getConnectionStatus() {
|
||||
// 確保在瀏覽器環境中執行
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ip-cleanup?action=status');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
console.log('📊 連線狀態:', data.data);
|
||||
return data.data;
|
||||
} else {
|
||||
console.error('❌ 獲取連線狀態失敗:', data.error);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 獲取連線狀態錯誤:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取客戶端 ID
|
||||
public getClientId(): string {
|
||||
return this.clientId;
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const clientCleanup = ClientConnectionCleanup.getInstance();
|
||||
|
||||
// 在模組載入時自動初始化(只在瀏覽器環境中)
|
||||
if (typeof window !== 'undefined') {
|
||||
// 延遲初始化,確保 DOM 已載入
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
clientCleanup;
|
||||
});
|
||||
} else {
|
||||
clientCleanup;
|
||||
}
|
||||
}
|
178
lib/connection-lifecycle-manager.ts
Normal file
178
lib/connection-lifecycle-manager.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
// =====================================================
|
||||
// 連線生命週期管理器
|
||||
// =====================================================
|
||||
|
||||
import { db } from './database';
|
||||
import { dbFailover } from './database-failover';
|
||||
|
||||
export class ConnectionLifecycleManager {
|
||||
private static instance: ConnectionLifecycleManager;
|
||||
private activeConnections = new Map<string, {
|
||||
connection: any;
|
||||
createdAt: number;
|
||||
lastUsed: number;
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
}>();
|
||||
private cleanupInterval: NodeJS.Timeout | null = null;
|
||||
private maxIdleTime = 5 * 60 * 1000; // 5分鐘空閒超時
|
||||
private maxConnectionAge = 30 * 60 * 1000; // 30分鐘最大連線時間
|
||||
|
||||
private constructor() {
|
||||
this.startCleanupProcess();
|
||||
}
|
||||
|
||||
public static getInstance(): ConnectionLifecycleManager {
|
||||
if (!ConnectionLifecycleManager.instance) {
|
||||
ConnectionLifecycleManager.instance = new ConnectionLifecycleManager();
|
||||
}
|
||||
return ConnectionLifecycleManager.instance;
|
||||
}
|
||||
|
||||
// 註冊連線
|
||||
public registerConnection(connectionId: string, connection: any, metadata?: {
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
}) {
|
||||
this.activeConnections.set(connectionId, {
|
||||
connection,
|
||||
createdAt: Date.now(),
|
||||
lastUsed: Date.now(),
|
||||
userId: metadata?.userId,
|
||||
sessionId: metadata?.sessionId
|
||||
});
|
||||
|
||||
console.log(`📝 註冊連線: ${connectionId} (總數: ${this.activeConnections.size})`);
|
||||
}
|
||||
|
||||
// 更新連線使用時間
|
||||
public updateConnectionUsage(connectionId: string) {
|
||||
const conn = this.activeConnections.get(connectionId);
|
||||
if (conn) {
|
||||
conn.lastUsed = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
// 釋放連線
|
||||
public releaseConnection(connectionId: string) {
|
||||
const conn = this.activeConnections.get(connectionId);
|
||||
if (conn) {
|
||||
try {
|
||||
if (conn.connection && typeof conn.connection.release === 'function') {
|
||||
conn.connection.release();
|
||||
}
|
||||
this.activeConnections.delete(connectionId);
|
||||
console.log(`🗑️ 釋放連線: ${connectionId} (剩餘: ${this.activeConnections.size})`);
|
||||
} catch (error) {
|
||||
console.error(`❌ 釋放連線失敗: ${connectionId}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 開始清理程序
|
||||
private startCleanupProcess() {
|
||||
// 每30秒檢查一次空閒連線
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
this.cleanupIdleConnections();
|
||||
}, 30 * 1000);
|
||||
|
||||
console.log('🧹 連線生命週期管理器已啟動');
|
||||
}
|
||||
|
||||
// 清理空閒連線
|
||||
private cleanupIdleConnections() {
|
||||
const now = Date.now();
|
||||
const toRemove: string[] = [];
|
||||
|
||||
for (const [connectionId, conn] of this.activeConnections) {
|
||||
const idleTime = now - conn.lastUsed;
|
||||
const connectionAge = now - conn.createdAt;
|
||||
|
||||
// 檢查空閒時間
|
||||
if (idleTime > this.maxIdleTime) {
|
||||
console.log(`⏰ 連線 ${connectionId} 空閒時間過長 (${Math.round(idleTime / 1000)}s),準備釋放`);
|
||||
toRemove.push(connectionId);
|
||||
}
|
||||
// 檢查連線年齡
|
||||
else if (connectionAge > this.maxConnectionAge) {
|
||||
console.log(`⏰ 連線 ${connectionId} 存在時間過長 (${Math.round(connectionAge / 1000)}s),準備釋放`);
|
||||
toRemove.push(connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
// 釋放需要清理的連線
|
||||
toRemove.forEach(connectionId => {
|
||||
this.releaseConnection(connectionId);
|
||||
});
|
||||
|
||||
if (toRemove.length > 0) {
|
||||
console.log(`🧹 清理了 ${toRemove.length} 個空閒連線`);
|
||||
}
|
||||
}
|
||||
|
||||
// 強制清理所有連線
|
||||
public forceCleanupAll() {
|
||||
console.log('🚨 強制清理所有連線...');
|
||||
|
||||
const connectionIds = Array.from(this.activeConnections.keys());
|
||||
connectionIds.forEach(connectionId => {
|
||||
this.releaseConnection(connectionId);
|
||||
});
|
||||
|
||||
console.log(`✅ 已清理 ${connectionIds.length} 個連線`);
|
||||
}
|
||||
|
||||
// 獲取連線統計
|
||||
public getConnectionStats() {
|
||||
const now = Date.now();
|
||||
const connections = Array.from(this.activeConnections.values());
|
||||
|
||||
const idleConnections = connections.filter(conn =>
|
||||
now - conn.lastUsed > this.maxIdleTime
|
||||
).length;
|
||||
|
||||
const oldConnections = connections.filter(conn =>
|
||||
now - conn.createdAt > this.maxConnectionAge
|
||||
).length;
|
||||
|
||||
return {
|
||||
totalConnections: this.activeConnections.size,
|
||||
idleConnections,
|
||||
oldConnections,
|
||||
maxIdleTime: this.maxIdleTime,
|
||||
maxConnectionAge: this.maxConnectionAge,
|
||||
connections: connections.map(conn => ({
|
||||
createdAt: new Date(conn.createdAt).toISOString(),
|
||||
lastUsed: new Date(conn.lastUsed).toISOString(),
|
||||
idleTime: now - conn.lastUsed,
|
||||
age: now - conn.createdAt,
|
||||
userId: conn.userId,
|
||||
sessionId: conn.sessionId
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
// 停止清理程序
|
||||
public stop() {
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
this.cleanupInterval = null;
|
||||
}
|
||||
|
||||
// 清理所有連線
|
||||
this.forceCleanupAll();
|
||||
|
||||
console.log('⏹️ 連線生命週期管理器已停止');
|
||||
}
|
||||
|
||||
// 設置清理參數
|
||||
public setCleanupParams(maxIdleTime?: number, maxConnectionAge?: number) {
|
||||
if (maxIdleTime) this.maxIdleTime = maxIdleTime;
|
||||
if (maxConnectionAge) this.maxConnectionAge = maxConnectionAge;
|
||||
|
||||
console.log(`⚙️ 更新清理參數: 空閒時間=${this.maxIdleTime}ms, 最大年齡=${this.maxConnectionAge}ms`);
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const connectionLifecycleManager = ConnectionLifecycleManager.getInstance();
|
@@ -5,6 +5,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from './database';
|
||||
import { dbMonitor } from './database-monitor';
|
||||
import { dbShutdownManager } from './database-shutdown-manager';
|
||||
|
||||
// 連線池狀態追蹤
|
||||
let connectionCount = 0;
|
||||
@@ -135,22 +136,24 @@ export function startConnectionMonitoring() {
|
||||
console.log('🔍 資料庫連線監控已啟動');
|
||||
}
|
||||
|
||||
// 優雅的關閉
|
||||
// 優雅的關閉(使用新的關閉管理器)
|
||||
export async function gracefulShutdown() {
|
||||
console.log('🔄 正在優雅關閉資料庫連線...');
|
||||
|
||||
try {
|
||||
dbMonitor.stopMonitoring();
|
||||
await db.close();
|
||||
console.log('✅ 資料庫連線已關閉');
|
||||
} catch (error) {
|
||||
console.error('❌ 關閉資料庫連線時發生錯誤:', error);
|
||||
}
|
||||
console.log('🔄 使用資料庫關閉管理器進行優雅關閉...');
|
||||
await dbShutdownManager.gracefulShutdown();
|
||||
}
|
||||
|
||||
// 處理程序退出事件
|
||||
if (typeof process !== 'undefined') {
|
||||
process.on('SIGINT', gracefulShutdown);
|
||||
process.on('SIGTERM', gracefulShutdown);
|
||||
process.on('exit', gracefulShutdown);
|
||||
// 強制關閉
|
||||
export function forceShutdown() {
|
||||
console.log('🚨 強制關閉資料庫連線...');
|
||||
dbShutdownManager.forceShutdown();
|
||||
}
|
||||
|
||||
// 獲取關閉狀態
|
||||
export function getShutdownStatus() {
|
||||
return dbShutdownManager.getShutdownStatus();
|
||||
}
|
||||
|
||||
// 測試關閉機制
|
||||
export async function testShutdown() {
|
||||
return await dbShutdownManager.testShutdown();
|
||||
}
|
||||
|
@@ -50,21 +50,25 @@ export class DatabaseMonitor {
|
||||
|
||||
// 檢查連線狀態
|
||||
private async checkConnectionStatus() {
|
||||
let connection = null;
|
||||
try {
|
||||
// 使用單一連線來檢查狀態,避免建立多個連線
|
||||
connection = await db.getConnection();
|
||||
|
||||
// 檢查當前連線數
|
||||
const statusResult = await db.queryOne(`
|
||||
const [statusResult] = await connection.execute(`
|
||||
SHOW STATUS LIKE 'Threads_connected'
|
||||
`);
|
||||
|
||||
const currentConnections = statusResult?.Value || 0;
|
||||
const currentConnections = statusResult[0]?.Value || 0;
|
||||
this.connectionCount = parseInt(currentConnections);
|
||||
|
||||
// 檢查最大連線數
|
||||
const maxConnResult = await db.queryOne(`
|
||||
const [maxConnResult] = await connection.execute(`
|
||||
SHOW VARIABLES LIKE 'max_connections'
|
||||
`);
|
||||
|
||||
this.maxConnections = parseInt(maxConnResult?.Value || '100');
|
||||
this.maxConnections = parseInt(maxConnResult[0]?.Value || '100');
|
||||
|
||||
// 檢查連線使用率
|
||||
const usagePercentage = (this.connectionCount / this.maxConnections) * 100;
|
||||
@@ -80,43 +84,68 @@ export class DatabaseMonitor {
|
||||
}
|
||||
|
||||
// 檢查長時間運行的查詢
|
||||
await this.checkLongRunningQueries();
|
||||
await this.checkLongRunningQueries(connection);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 資料庫監控錯誤:', error);
|
||||
} finally {
|
||||
// 確保釋放連線
|
||||
if (connection) {
|
||||
try {
|
||||
connection.release();
|
||||
} catch (releaseError) {
|
||||
console.error('❌ 釋放監控連線失敗:', releaseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 檢查長時間運行的查詢
|
||||
private async checkLongRunningQueries() {
|
||||
private async checkLongRunningQueries(connection?: any) {
|
||||
try {
|
||||
const longQueries = await db.query(`
|
||||
SELECT
|
||||
ID,
|
||||
USER,
|
||||
HOST,
|
||||
DB,
|
||||
COMMAND,
|
||||
TIME,
|
||||
STATE,
|
||||
INFO
|
||||
FROM information_schema.PROCESSLIST
|
||||
WHERE TIME > 30
|
||||
AND COMMAND != 'Sleep'
|
||||
ORDER BY TIME DESC
|
||||
LIMIT 5
|
||||
`);
|
||||
// 如果沒有傳入連線,則建立一個新的
|
||||
let tempConnection = connection;
|
||||
let shouldRelease = false;
|
||||
|
||||
if (!tempConnection) {
|
||||
tempConnection = await db.getConnection();
|
||||
shouldRelease = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const [longQueries] = await tempConnection.execute(`
|
||||
SELECT
|
||||
ID,
|
||||
USER,
|
||||
HOST,
|
||||
DB,
|
||||
COMMAND,
|
||||
TIME,
|
||||
STATE,
|
||||
INFO
|
||||
FROM information_schema.PROCESSLIST
|
||||
WHERE TIME > 30
|
||||
AND COMMAND != 'Sleep'
|
||||
ORDER BY TIME DESC
|
||||
LIMIT 5
|
||||
`);
|
||||
|
||||
if (longQueries.length > 0) {
|
||||
console.warn(`⚠️ 發現 ${longQueries.length} 個長時間運行的查詢:`);
|
||||
longQueries.forEach((query, index) => {
|
||||
console.warn(` ${index + 1}. 用戶: ${query.USER}, 時間: ${query.TIME}s, 狀態: ${query.STATE}`);
|
||||
if (query.INFO && query.INFO.length > 100) {
|
||||
console.warn(` 查詢: ${query.INFO.substring(0, 100)}...`);
|
||||
} else if (query.INFO) {
|
||||
console.warn(` 查詢: ${query.INFO}`);
|
||||
}
|
||||
});
|
||||
if (longQueries.length > 0) {
|
||||
console.warn(`⚠️ 發現 ${longQueries.length} 個長時間運行的查詢:`);
|
||||
longQueries.forEach((query: any, index: number) => {
|
||||
console.warn(` ${index + 1}. 用戶: ${query.USER}, 時間: ${query.TIME}s, 狀態: ${query.STATE}`);
|
||||
if (query.INFO && query.INFO.length > 100) {
|
||||
console.warn(` 查詢: ${query.INFO.substring(0, 100)}...`);
|
||||
} else if (query.INFO) {
|
||||
console.warn(` 查詢: ${query.INFO}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
// 只有當我們建立了新連線時才釋放
|
||||
if (shouldRelease && tempConnection) {
|
||||
tempConnection.release();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 檢查長時間查詢時發生錯誤:', error);
|
||||
|
89
lib/database-service-smart.ts
Normal file
89
lib/database-service-smart.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// =====================================================
|
||||
// 智能資料庫服務範例
|
||||
// =====================================================
|
||||
|
||||
import { smartPool } from './smart-connection-pool';
|
||||
|
||||
// 這是一個範例,展示如何使用智能連線池
|
||||
// 你可以將現有的資料庫服務改為使用這個方式
|
||||
|
||||
export class SmartDatabaseService {
|
||||
// 智能查詢範例
|
||||
static async getUserById(userId: string, requestId?: string) {
|
||||
return await smartPool.executeQueryOne(
|
||||
'SELECT * FROM users WHERE id = ?',
|
||||
[userId],
|
||||
{
|
||||
userId,
|
||||
sessionId: requestId,
|
||||
requestId: `getUserById_${Date.now()}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 智能插入範例
|
||||
static async createUser(userData: any, requestId?: string) {
|
||||
return await smartPool.executeInsert(
|
||||
'INSERT INTO users (name, email, department) VALUES (?, ?, ?)',
|
||||
[userData.name, userData.email, userData.department],
|
||||
{
|
||||
userId: userData.id,
|
||||
sessionId: requestId,
|
||||
requestId: `createUser_${Date.now()}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 智能更新範例
|
||||
static async updateUser(userId: string, userData: any, requestId?: string) {
|
||||
return await smartPool.executeUpdate(
|
||||
'UPDATE users SET name = ?, email = ?, department = ? WHERE id = ?',
|
||||
[userData.name, userData.email, userData.department, userId],
|
||||
{
|
||||
userId,
|
||||
sessionId: requestId,
|
||||
requestId: `updateUser_${Date.now()}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 智能刪除範例
|
||||
static async deleteUser(userId: string, requestId?: string) {
|
||||
return await smartPool.executeDelete(
|
||||
'DELETE FROM users WHERE id = ?',
|
||||
[userId],
|
||||
{
|
||||
userId,
|
||||
sessionId: requestId,
|
||||
requestId: `deleteUser_${Date.now()}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 獲取連線統計
|
||||
static getConnectionStats() {
|
||||
return smartPool.getConnectionStats();
|
||||
}
|
||||
|
||||
// 強制清理連線
|
||||
static forceCleanup() {
|
||||
return smartPool.forceCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用範例:
|
||||
/*
|
||||
// 在 API 路由中使用
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const userId = searchParams.get('userId');
|
||||
const requestId = request.headers.get('x-request-id') || 'unknown';
|
||||
|
||||
try {
|
||||
const user = await SmartDatabaseService.getUserById(userId, requestId);
|
||||
return NextResponse.json({ success: true, data: user });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
*/
|
197
lib/database-shutdown-manager.ts
Normal file
197
lib/database-shutdown-manager.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
// =====================================================
|
||||
// 資料庫關閉管理器
|
||||
// =====================================================
|
||||
|
||||
import { db } from './database';
|
||||
import { dbFailover } from './database-failover';
|
||||
import { dbMonitor } from './database-monitor';
|
||||
import { smartPool } from './smart-connection-pool';
|
||||
|
||||
export class DatabaseShutdownManager {
|
||||
private static instance: DatabaseShutdownManager;
|
||||
private isShuttingDown = false;
|
||||
private shutdownHandlers: (() => Promise<void>)[] = [];
|
||||
|
||||
private constructor() {
|
||||
this.registerShutdownHandlers();
|
||||
}
|
||||
|
||||
public static getInstance(): DatabaseShutdownManager {
|
||||
if (!DatabaseShutdownManager.instance) {
|
||||
DatabaseShutdownManager.instance = new DatabaseShutdownManager();
|
||||
}
|
||||
return DatabaseShutdownManager.instance;
|
||||
}
|
||||
|
||||
// 註冊關閉處理器
|
||||
private registerShutdownHandlers() {
|
||||
// 添加資料庫關閉處理器
|
||||
this.addShutdownHandler('database', async () => {
|
||||
console.log('🔄 正在關閉主要資料庫連線池...');
|
||||
try {
|
||||
await db.close();
|
||||
console.log('✅ 主要資料庫連線池已關閉');
|
||||
} catch (error) {
|
||||
console.error('❌ 關閉主要資料庫連線池時發生錯誤:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加備援資料庫關閉處理器
|
||||
this.addShutdownHandler('failover', async () => {
|
||||
console.log('🔄 正在關閉備援資料庫連線池...');
|
||||
try {
|
||||
await dbFailover.close();
|
||||
console.log('✅ 備援資料庫連線池已關閉');
|
||||
} catch (error) {
|
||||
console.error('❌ 關閉備援資料庫連線池時發生錯誤:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加監控服務關閉處理器
|
||||
this.addShutdownHandler('monitor', async () => {
|
||||
console.log('🔄 正在停止資料庫監控服務...');
|
||||
try {
|
||||
dbMonitor.stopMonitoring();
|
||||
console.log('✅ 資料庫監控服務已停止');
|
||||
} catch (error) {
|
||||
console.error('❌ 停止資料庫監控服務時發生錯誤:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加智能連線池關閉處理器
|
||||
this.addShutdownHandler('smart-pool', async () => {
|
||||
console.log('🔄 正在清理智能連線池...');
|
||||
try {
|
||||
smartPool.forceCleanup();
|
||||
console.log('✅ 智能連線池已清理');
|
||||
} catch (error) {
|
||||
console.error('❌ 清理智能連線池時發生錯誤:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 註冊系統信號處理器
|
||||
this.registerSystemHandlers();
|
||||
}
|
||||
|
||||
// 添加關閉處理器
|
||||
public addShutdownHandler(name: string, handler: () => Promise<void>) {
|
||||
this.shutdownHandlers.push(async () => {
|
||||
try {
|
||||
console.log(`🔄 執行關閉處理器: ${name}`);
|
||||
await handler();
|
||||
console.log(`✅ 關閉處理器完成: ${name}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ 關閉處理器失敗: ${name}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 註冊系統信號處理器
|
||||
private registerSystemHandlers() {
|
||||
if (typeof process === 'undefined') return;
|
||||
|
||||
// SIGINT (Ctrl+C)
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n🛑 收到 SIGINT 信號,開始優雅關閉...');
|
||||
this.gracefulShutdown();
|
||||
});
|
||||
|
||||
// SIGTERM (終止信號)
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('\n🛑 收到 SIGTERM 信號,開始優雅關閉...');
|
||||
this.gracefulShutdown();
|
||||
});
|
||||
|
||||
// 未捕獲的異常
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('❌ 未捕獲的異常:', error);
|
||||
this.gracefulShutdown();
|
||||
});
|
||||
|
||||
// 未處理的 Promise 拒絕
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('❌ 未處理的 Promise 拒絕:', reason);
|
||||
this.gracefulShutdown();
|
||||
});
|
||||
|
||||
// 程序退出
|
||||
process.on('exit', () => {
|
||||
if (!this.isShuttingDown) {
|
||||
console.log('🛑 程序即將退出,強制關閉資料庫連線...');
|
||||
this.forceShutdown();
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ 系統信號處理器已註冊');
|
||||
}
|
||||
|
||||
// 優雅關閉
|
||||
public async gracefulShutdown() {
|
||||
if (this.isShuttingDown) {
|
||||
console.log('⚠️ 關閉程序已在進行中,跳過重複請求');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isShuttingDown = true;
|
||||
console.log('🔄 開始優雅關閉資料庫連線...');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 並行執行所有關閉處理器
|
||||
await Promise.allSettled(
|
||||
this.shutdownHandlers.map(handler => handler())
|
||||
);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`✅ 資料庫連線關閉完成,耗時: ${duration}ms`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 優雅關閉過程中發生錯誤:', error);
|
||||
} finally {
|
||||
// 強制退出程序
|
||||
setTimeout(() => {
|
||||
console.log('🛑 強制退出程序');
|
||||
process.exit(0);
|
||||
}, 5000); // 5秒後強制退出
|
||||
}
|
||||
}
|
||||
|
||||
// 強制關閉
|
||||
public forceShutdown() {
|
||||
console.log('🚨 強制關閉所有資料庫連線...');
|
||||
|
||||
// 同步執行關閉處理器(不等待)
|
||||
this.shutdownHandlers.forEach(handler => {
|
||||
try {
|
||||
handler();
|
||||
} catch (error) {
|
||||
console.error('❌ 強制關閉時發生錯誤:', error);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ 強制關閉完成');
|
||||
}
|
||||
|
||||
// 獲取關閉狀態
|
||||
public getShutdownStatus() {
|
||||
return {
|
||||
isShuttingDown: this.isShuttingDown,
|
||||
handlerCount: this.shutdownHandlers.length,
|
||||
registeredHandlers: this.shutdownHandlers.map((_, index) => `handler-${index}`)
|
||||
};
|
||||
}
|
||||
|
||||
// 測試關閉機制
|
||||
public async testShutdown() {
|
||||
console.log('🧪 測試資料庫關閉機制...');
|
||||
await this.gracefulShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const dbShutdownManager = DatabaseShutdownManager.getInstance();
|
||||
|
||||
// 導出便捷函數
|
||||
export const gracefulShutdown = () => dbShutdownManager.gracefulShutdown();
|
||||
export const forceShutdown = () => dbShutdownManager.forceShutdown();
|
175
lib/emergency-connection-cleanup.ts
Normal file
175
lib/emergency-connection-cleanup.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
// =====================================================
|
||||
// 緊急連線清理工具
|
||||
// =====================================================
|
||||
|
||||
import { db } from './database';
|
||||
import { dbFailover } from './database-failover';
|
||||
import { dbMonitor } from './database-monitor';
|
||||
|
||||
export class EmergencyConnectionCleanup {
|
||||
private static instance: EmergencyConnectionCleanup;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): EmergencyConnectionCleanup {
|
||||
if (!EmergencyConnectionCleanup.instance) {
|
||||
EmergencyConnectionCleanup.instance = new EmergencyConnectionCleanup();
|
||||
}
|
||||
return EmergencyConnectionCleanup.instance;
|
||||
}
|
||||
|
||||
// 立即停止所有監控和清理所有連線
|
||||
public async emergencyCleanup() {
|
||||
console.log('🚨 執行緊急連線清理...');
|
||||
|
||||
try {
|
||||
// 1. 立即停止所有監控
|
||||
console.log('⏹️ 停止資料庫監控...');
|
||||
dbMonitor.stopMonitoring();
|
||||
|
||||
// 2. 強制關閉所有連線池
|
||||
console.log('🔌 關閉主要資料庫連線池...');
|
||||
await db.close();
|
||||
|
||||
console.log('🔌 關閉備援資料庫連線池...');
|
||||
await dbFailover.close();
|
||||
|
||||
// 3. 等待一段時間讓連線完全關閉
|
||||
console.log('⏳ 等待連線關閉...');
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// 4. 檢查連線狀態
|
||||
await this.checkConnectionStatus();
|
||||
|
||||
console.log('✅ 緊急清理完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 緊急清理失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 檢查連線狀態(不建立新連線)
|
||||
private async checkConnectionStatus() {
|
||||
try {
|
||||
// 使用一個臨時連線來檢查狀態
|
||||
const tempConnection = await db.getConnection();
|
||||
|
||||
try {
|
||||
const [statusResult] = await tempConnection.execute(`
|
||||
SHOW STATUS LIKE 'Threads_connected'
|
||||
`);
|
||||
|
||||
const [maxConnResult] = await tempConnection.execute(`
|
||||
SHOW VARIABLES LIKE 'max_connections'
|
||||
`);
|
||||
|
||||
const currentConnections = statusResult[0]?.Value || 0;
|
||||
const maxConnections = maxConnResult[0]?.Value || 100;
|
||||
const usagePercentage = (currentConnections / maxConnections) * 100;
|
||||
|
||||
console.log(`📊 清理後連線狀態: ${currentConnections}/${maxConnections} (${usagePercentage.toFixed(1)}%)`);
|
||||
|
||||
if (currentConnections > 5) {
|
||||
console.warn(`⚠️ 仍有 ${currentConnections} 個連線未關閉`);
|
||||
} else {
|
||||
console.log('✅ 連線已成功清理');
|
||||
}
|
||||
|
||||
} finally {
|
||||
tempConnection.release();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 檢查連線狀態失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 強制殺死所有資料庫連線
|
||||
public async forceKillAllConnections() {
|
||||
console.log('💀 強制殺死所有資料庫連線...');
|
||||
|
||||
try {
|
||||
const tempConnection = await db.getConnection();
|
||||
|
||||
try {
|
||||
// 獲取所有連線ID
|
||||
const [connections] = await tempConnection.execute(`
|
||||
SELECT ID FROM information_schema.PROCESSLIST
|
||||
WHERE USER = ? AND COMMAND != 'Sleep'
|
||||
`, [process.env.DB_USER || 'A999']);
|
||||
|
||||
console.log(`🔍 找到 ${connections.length} 個活躍連線`);
|
||||
|
||||
// 殺死所有連線(除了當前連線)
|
||||
for (const conn of connections) {
|
||||
if (conn.ID !== tempConnection.threadId) {
|
||||
try {
|
||||
await tempConnection.execute(`KILL ${conn.ID}`);
|
||||
console.log(`💀 已殺死連線 ${conn.ID}`);
|
||||
} catch (error) {
|
||||
console.log(`⚠️ 無法殺死連線 ${conn.ID}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待連線關閉
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 再次檢查狀態
|
||||
await this.checkConnectionStatus();
|
||||
|
||||
} finally {
|
||||
tempConnection.release();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 強制殺死連線失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取連線詳情
|
||||
public async getConnectionDetails() {
|
||||
try {
|
||||
const tempConnection = await db.getConnection();
|
||||
|
||||
try {
|
||||
const [connections] = await tempConnection.execute(`
|
||||
SELECT
|
||||
ID,
|
||||
USER,
|
||||
HOST,
|
||||
DB,
|
||||
COMMAND,
|
||||
TIME,
|
||||
STATE,
|
||||
INFO
|
||||
FROM information_schema.PROCESSLIST
|
||||
WHERE USER = ?
|
||||
ORDER BY TIME DESC
|
||||
`, [process.env.DB_USER || 'A999']);
|
||||
|
||||
console.log('📋 當前資料庫連線詳情:');
|
||||
connections.forEach((conn, index) => {
|
||||
console.log(`${index + 1}. ID: ${conn.ID}, 用戶: ${conn.USER}, 時間: ${conn.TIME}s, 狀態: ${conn.STATE}`);
|
||||
if (conn.INFO && conn.INFO.length > 50) {
|
||||
console.log(` 查詢: ${conn.INFO.substring(0, 50)}...`);
|
||||
} else if (conn.INFO) {
|
||||
console.log(` 查詢: ${conn.INFO}`);
|
||||
}
|
||||
});
|
||||
|
||||
return connections;
|
||||
|
||||
} finally {
|
||||
tempConnection.release();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 獲取連線詳情失敗:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const emergencyCleanup = EmergencyConnectionCleanup.getInstance();
|
251
lib/ip-based-connection-manager.ts
Normal file
251
lib/ip-based-connection-manager.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
// =====================================================
|
||||
// 基於 IP 的連線管理器
|
||||
// =====================================================
|
||||
|
||||
import mysql from 'mysql2/promise';
|
||||
import { db } from './database';
|
||||
|
||||
export class IPBasedConnectionManager {
|
||||
private static instance: IPBasedConnectionManager;
|
||||
private clientIP: string | null = null;
|
||||
private connectionTracker = new Map<string, {
|
||||
connectionId: string;
|
||||
createdAt: number;
|
||||
lastUsed: number;
|
||||
userAgent?: string;
|
||||
}>();
|
||||
|
||||
private constructor() {
|
||||
this.detectClientIP();
|
||||
}
|
||||
|
||||
public static getInstance(): IPBasedConnectionManager {
|
||||
if (!IPBasedConnectionManager.instance) {
|
||||
IPBasedConnectionManager.instance = new IPBasedConnectionManager();
|
||||
}
|
||||
return IPBasedConnectionManager.instance;
|
||||
}
|
||||
|
||||
// 檢測客戶端 IP
|
||||
private detectClientIP() {
|
||||
// 在瀏覽器環境中,我們無法直接獲取真實 IP
|
||||
// 但我們可以通過其他方式來識別連線
|
||||
if (typeof window !== 'undefined') {
|
||||
// 客戶端:生成一個唯一標識符
|
||||
this.clientIP = this.generateClientId();
|
||||
console.log('🖥️ 客戶端標識符:', this.clientIP);
|
||||
} else {
|
||||
// 服務端:嘗試從環境變數或請求中獲取
|
||||
this.clientIP = process.env.CLIENT_IP || 'server-side';
|
||||
console.log('🖥️ 服務端標識符:', this.clientIP);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成客戶端唯一標識符
|
||||
private generateClientId(): string {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.random().toString(36).substring(2);
|
||||
return `client_${timestamp}_${random}`;
|
||||
}
|
||||
|
||||
// 設置客戶端 IP(從請求中獲取)
|
||||
public setClientIP(ip: string) {
|
||||
this.clientIP = ip;
|
||||
console.log('🖥️ 設置客戶端 IP:', ip);
|
||||
}
|
||||
|
||||
// 獲取客戶端 IP
|
||||
public getClientIP(): string | null {
|
||||
return this.clientIP;
|
||||
}
|
||||
|
||||
// 註冊連線(帶 IP 標識)
|
||||
public registerConnection(connectionId: string, metadata?: {
|
||||
userAgent?: string;
|
||||
requestId?: string;
|
||||
}) {
|
||||
if (!this.clientIP) return;
|
||||
|
||||
this.connectionTracker.set(connectionId, {
|
||||
connectionId,
|
||||
createdAt: Date.now(),
|
||||
lastUsed: Date.now(),
|
||||
userAgent: metadata?.userAgent,
|
||||
});
|
||||
|
||||
console.log(`📝 註冊連線: ${connectionId} (IP: ${this.clientIP})`);
|
||||
}
|
||||
|
||||
// 更新連線使用時間
|
||||
public updateConnectionUsage(connectionId: string) {
|
||||
const conn = this.connectionTracker.get(connectionId);
|
||||
if (conn) {
|
||||
conn.lastUsed = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
// 釋放連線
|
||||
public releaseConnection(connectionId: string) {
|
||||
const conn = this.connectionTracker.get(connectionId);
|
||||
if (conn) {
|
||||
this.connectionTracker.delete(connectionId);
|
||||
console.log(`🗑️ 釋放連線: ${connectionId} (IP: ${this.clientIP})`);
|
||||
}
|
||||
}
|
||||
|
||||
// 根據 IP 清理連線
|
||||
public async cleanupConnectionsByIP(targetIP?: string): Promise<{
|
||||
success: boolean;
|
||||
killedCount: number;
|
||||
message: string;
|
||||
}> {
|
||||
const ipToClean = targetIP || this.clientIP;
|
||||
|
||||
if (!ipToClean) {
|
||||
return {
|
||||
success: false,
|
||||
killedCount: 0,
|
||||
message: '無法識別客戶端 IP'
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`🧹 開始清理 IP ${ipToClean} 的連線...`);
|
||||
|
||||
let connection = null;
|
||||
let killedCount = 0;
|
||||
|
||||
try {
|
||||
// 建立臨時連線
|
||||
connection = await db.getConnection();
|
||||
|
||||
// 獲取指定 IP 的所有連線(使用更寬鬆的匹配)
|
||||
const [connections] = await connection.execute(`
|
||||
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
|
||||
FROM information_schema.PROCESSLIST
|
||||
WHERE USER = ? AND (HOST LIKE ? OR HOST LIKE ? OR HOST LIKE ?)
|
||||
ORDER BY TIME DESC
|
||||
`, [
|
||||
process.env.DB_USER || 'A999',
|
||||
`%${ipToClean}%`, // 包含 IP 的 HOST
|
||||
`%${ipToClean}.%`, // IP 開頭的 HOST
|
||||
`%${ipToClean}-%` // IP 帶連字符的 HOST
|
||||
]);
|
||||
|
||||
console.log(`🔍 找到 ${connections.length} 個來自 ${ipToClean} 的連線`);
|
||||
|
||||
// 顯示連線詳情
|
||||
connections.forEach((conn: any, index: number) => {
|
||||
console.log(`${index + 1}. ID: ${conn.ID}, HOST: ${conn.HOST}, 時間: ${conn.TIME}s, 狀態: ${conn.STATE}`);
|
||||
});
|
||||
|
||||
// 殺死指定 IP 的連線(除了當前連線)
|
||||
for (const conn of connections) {
|
||||
if (conn.ID !== connection.threadId) {
|
||||
try {
|
||||
await connection.execute(`KILL ${conn.ID}`);
|
||||
console.log(`💀 已殺死連線 ${conn.ID} (HOST: ${conn.HOST})`);
|
||||
killedCount++;
|
||||
} catch (error: any) {
|
||||
console.log(`⚠️ 無法殺死連線 ${conn.ID}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理本地追蹤的連線
|
||||
this.connectionTracker.clear();
|
||||
|
||||
console.log(`✅ 已清理 ${killedCount} 個來自 ${ipToClean} 的連線`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
killedCount,
|
||||
message: `已清理 ${killedCount} 個來自 ${ipToClean} 的連線`
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 清理連線失敗:', error);
|
||||
return {
|
||||
success: false,
|
||||
killedCount: 0,
|
||||
message: `清理失敗: ${error instanceof Error ? error.message : '未知錯誤'}`
|
||||
};
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取指定 IP 的連線狀態
|
||||
public async getConnectionsByIP(targetIP?: string): Promise<{
|
||||
ip: string;
|
||||
connectionCount: number;
|
||||
connections: any[];
|
||||
}> {
|
||||
const ipToCheck = targetIP || this.clientIP;
|
||||
|
||||
if (!ipToCheck) {
|
||||
return {
|
||||
ip: 'unknown',
|
||||
connectionCount: 0,
|
||||
connections: []
|
||||
};
|
||||
}
|
||||
|
||||
let connection = null;
|
||||
|
||||
try {
|
||||
connection = await db.getConnection();
|
||||
|
||||
const [connections] = await connection.execute(`
|
||||
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
|
||||
FROM information_schema.PROCESSLIST
|
||||
WHERE USER = ? AND HOST LIKE ?
|
||||
ORDER BY TIME DESC
|
||||
`, [process.env.DB_USER || 'A999', `%${ipToCheck}%`]);
|
||||
|
||||
return {
|
||||
ip: ipToCheck,
|
||||
connectionCount: connections.length,
|
||||
connections: connections
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 獲取連線狀態失敗:', error);
|
||||
return {
|
||||
ip: ipToCheck,
|
||||
connectionCount: 0,
|
||||
connections: []
|
||||
};
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理所有本地追蹤的連線
|
||||
public cleanupLocalConnections() {
|
||||
const count = this.connectionTracker.size;
|
||||
this.connectionTracker.clear();
|
||||
console.log(`🧹 已清理 ${count} 個本地追蹤的連線`);
|
||||
return count;
|
||||
}
|
||||
|
||||
// 獲取本地連線統計
|
||||
public getLocalConnectionStats() {
|
||||
return {
|
||||
clientIP: this.clientIP,
|
||||
trackedConnections: this.connectionTracker.size,
|
||||
connections: Array.from(this.connectionTracker.values()).map(conn => ({
|
||||
connectionId: conn.connectionId,
|
||||
createdAt: new Date(conn.createdAt).toISOString(),
|
||||
lastUsed: new Date(conn.lastUsed).toISOString(),
|
||||
userAgent: conn.userAgent
|
||||
}))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const ipConnectionManager = IPBasedConnectionManager.getInstance();
|
202
lib/smart-connection-pool.ts
Normal file
202
lib/smart-connection-pool.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
// =====================================================
|
||||
// 智能連線池包裝器
|
||||
// =====================================================
|
||||
|
||||
import { db } from './database';
|
||||
import { dbFailover } from './database-failover';
|
||||
import { connectionLifecycleManager } from './connection-lifecycle-manager';
|
||||
|
||||
export class SmartConnectionPool {
|
||||
private static instance: SmartConnectionPool;
|
||||
private connectionCounter = 0;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): SmartConnectionPool {
|
||||
if (!SmartConnectionPool.instance) {
|
||||
SmartConnectionPool.instance = new SmartConnectionPool();
|
||||
}
|
||||
return SmartConnectionPool.instance;
|
||||
}
|
||||
|
||||
// 獲取智能連線
|
||||
public async getSmartConnection(metadata?: {
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
requestId?: string;
|
||||
}): Promise<{
|
||||
connection: any;
|
||||
connectionId: string;
|
||||
release: () => void;
|
||||
}> {
|
||||
const connectionId = `conn_${++this.connectionCounter}_${Date.now()}`;
|
||||
|
||||
try {
|
||||
// 獲取實際連線
|
||||
const connection = await db.getConnection();
|
||||
|
||||
// 註冊到生命週期管理器
|
||||
connectionLifecycleManager.registerConnection(connectionId, connection, metadata);
|
||||
|
||||
// 創建包裝的釋放函數
|
||||
const release = () => {
|
||||
connectionLifecycleManager.releaseConnection(connectionId);
|
||||
};
|
||||
|
||||
console.log(`🔗 獲取智能連線: ${connectionId}`);
|
||||
|
||||
return {
|
||||
connection,
|
||||
connectionId,
|
||||
release
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`❌ 獲取智能連線失敗: ${connectionId}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 執行智能查詢
|
||||
public async executeQuery<T = any>(
|
||||
sql: string,
|
||||
params?: any[],
|
||||
metadata?: {
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
requestId?: string;
|
||||
}
|
||||
): Promise<T[]> {
|
||||
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
|
||||
|
||||
try {
|
||||
// 更新連線使用時間
|
||||
connectionLifecycleManager.updateConnectionUsage(connectionId);
|
||||
|
||||
// 執行查詢
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
return rows as T[];
|
||||
} catch (error) {
|
||||
console.error(`❌ 智能查詢失敗: ${connectionId}`, error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 確保釋放連線
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
// 執行智能單一查詢
|
||||
public async executeQueryOne<T = any>(
|
||||
sql: string,
|
||||
params?: any[],
|
||||
metadata?: {
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
requestId?: string;
|
||||
}
|
||||
): Promise<T | null> {
|
||||
const results = await this.executeQuery<T>(sql, params, metadata);
|
||||
return results.length > 0 ? results[0] : null;
|
||||
}
|
||||
|
||||
// 執行智能插入
|
||||
public async executeInsert(
|
||||
sql: string,
|
||||
params?: any[],
|
||||
metadata?: {
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
requestId?: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
|
||||
|
||||
try {
|
||||
// 更新連線使用時間
|
||||
connectionLifecycleManager.updateConnectionUsage(connectionId);
|
||||
|
||||
// 執行插入
|
||||
const [result] = await connection.execute(sql, params);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`❌ 智能插入失敗: ${connectionId}`, error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 確保釋放連線
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
// 執行智能更新
|
||||
public async executeUpdate(
|
||||
sql: string,
|
||||
params?: any[],
|
||||
metadata?: {
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
requestId?: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
|
||||
|
||||
try {
|
||||
// 更新連線使用時間
|
||||
connectionLifecycleManager.updateConnectionUsage(connectionId);
|
||||
|
||||
// 執行更新
|
||||
const [result] = await connection.execute(sql, params);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`❌ 智能更新失敗: ${connectionId}`, error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 確保釋放連線
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
// 執行智能刪除
|
||||
public async executeDelete(
|
||||
sql: string,
|
||||
params?: any[],
|
||||
metadata?: {
|
||||
userId?: string;
|
||||
sessionId?: string;
|
||||
requestId?: string;
|
||||
}
|
||||
): Promise<any> {
|
||||
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
|
||||
|
||||
try {
|
||||
// 更新連線使用時間
|
||||
connectionLifecycleManager.updateConnectionUsage(connectionId);
|
||||
|
||||
// 執行刪除
|
||||
const [result] = await connection.execute(sql, params);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`❌ 智能刪除失敗: ${connectionId}`, error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 確保釋放連線
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取連線統計
|
||||
public getConnectionStats() {
|
||||
return connectionLifecycleManager.getConnectionStats();
|
||||
}
|
||||
|
||||
// 強制清理所有連線
|
||||
public forceCleanup() {
|
||||
connectionLifecycleManager.forceCleanupAll();
|
||||
}
|
||||
|
||||
// 設置清理參數
|
||||
public setCleanupParams(maxIdleTime?: number, maxConnectionAge?: number) {
|
||||
connectionLifecycleManager.setCleanupParams(maxIdleTime, maxConnectionAge);
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const smartPool = SmartConnectionPool.getInstance();
|
153
lib/smart-ip-connection-pool.ts
Normal file
153
lib/smart-ip-connection-pool.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
// =====================================================
|
||||
// 智能 IP 連線池
|
||||
// =====================================================
|
||||
|
||||
import { db } from './database';
|
||||
import { ipConnectionManager } from './ip-based-connection-manager';
|
||||
|
||||
export class SmartIPConnectionPool {
|
||||
private static instance: SmartIPConnectionPool;
|
||||
private connectionCounter = 0;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): SmartIPConnectionPool {
|
||||
if (!SmartIPConnectionPool.instance) {
|
||||
SmartIPConnectionPool.instance = new SmartIPConnectionPool();
|
||||
}
|
||||
return SmartIPConnectionPool.instance;
|
||||
}
|
||||
|
||||
// 設置客戶端 IP
|
||||
public setClientIP(ip: string) {
|
||||
ipConnectionManager.setClientIP(ip);
|
||||
}
|
||||
|
||||
// 獲取智能連線(帶 IP 追蹤)
|
||||
public async getSmartConnection(metadata?: {
|
||||
userAgent?: string;
|
||||
requestId?: string;
|
||||
clientIP?: string;
|
||||
}): Promise<{
|
||||
connection: any;
|
||||
connectionId: string;
|
||||
release: () => void;
|
||||
}> {
|
||||
const connectionId = `conn_${++this.connectionCounter}_${Date.now()}`;
|
||||
|
||||
// 設置客戶端 IP
|
||||
if (metadata?.clientIP) {
|
||||
ipConnectionManager.setClientIP(metadata.clientIP);
|
||||
}
|
||||
|
||||
try {
|
||||
// 獲取實際連線
|
||||
const connection = await db.getConnection();
|
||||
|
||||
// 註冊到 IP 連線管理器
|
||||
ipConnectionManager.registerConnection(connectionId, {
|
||||
userAgent: metadata?.userAgent,
|
||||
requestId: metadata?.requestId
|
||||
});
|
||||
|
||||
// 創建包裝的釋放函數
|
||||
const release = () => {
|
||||
ipConnectionManager.releaseConnection(connectionId);
|
||||
};
|
||||
|
||||
console.log(`🔗 獲取智能 IP 連線: ${connectionId}`);
|
||||
|
||||
return {
|
||||
connection,
|
||||
connectionId,
|
||||
release
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`❌ 獲取智能 IP 連線失敗: ${connectionId}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 執行智能查詢(帶 IP 追蹤)
|
||||
public async executeQuery<T = any>(
|
||||
sql: string,
|
||||
params?: any[],
|
||||
metadata?: {
|
||||
userAgent?: string;
|
||||
requestId?: string;
|
||||
clientIP?: string;
|
||||
}
|
||||
): Promise<T[]> {
|
||||
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
|
||||
|
||||
try {
|
||||
// 更新連線使用時間
|
||||
ipConnectionManager.updateConnectionUsage(connectionId);
|
||||
|
||||
// 執行查詢
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
return rows as T[];
|
||||
} catch (error) {
|
||||
console.error(`❌ 智能 IP 查詢失敗: ${connectionId}`, error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 確保釋放連線
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
// 執行智能單一查詢
|
||||
public async executeQueryOne<T = any>(
|
||||
sql: string,
|
||||
params?: any[],
|
||||
metadata?: {
|
||||
userAgent?: string;
|
||||
requestId?: string;
|
||||
clientIP?: string;
|
||||
}
|
||||
): Promise<T | null> {
|
||||
const results = await this.executeQuery<T>(sql, params, metadata);
|
||||
return results.length > 0 ? results[0] : null;
|
||||
}
|
||||
|
||||
// 清理當前 IP 的所有連線
|
||||
public async cleanupCurrentIPConnections(): Promise<{
|
||||
success: boolean;
|
||||
killedCount: number;
|
||||
message: string;
|
||||
}> {
|
||||
return await ipConnectionManager.cleanupConnectionsByIP();
|
||||
}
|
||||
|
||||
// 清理指定 IP 的所有連線
|
||||
public async cleanupIPConnections(targetIP: string): Promise<{
|
||||
success: boolean;
|
||||
killedCount: number;
|
||||
message: string;
|
||||
}> {
|
||||
return await ipConnectionManager.cleanupConnectionsByIP(targetIP);
|
||||
}
|
||||
|
||||
// 獲取當前 IP 的連線狀態
|
||||
public async getCurrentIPConnections() {
|
||||
return await ipConnectionManager.getConnectionsByIP();
|
||||
}
|
||||
|
||||
// 獲取指定 IP 的連線狀態
|
||||
public async getIPConnections(targetIP: string) {
|
||||
return await ipConnectionManager.getConnectionsByIP(targetIP);
|
||||
}
|
||||
|
||||
// 獲取本地連線統計
|
||||
public getLocalConnectionStats() {
|
||||
return ipConnectionManager.getLocalConnectionStats();
|
||||
}
|
||||
|
||||
// 清理本地追蹤的連線
|
||||
public cleanupLocalConnections() {
|
||||
return ipConnectionManager.cleanupLocalConnections();
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const smartIPPool = SmartIPConnectionPool.getInstance();
|
208
lib/smart-ip-detector.ts
Normal file
208
lib/smart-ip-detector.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
// =====================================================
|
||||
// 智能 IP 偵測器
|
||||
// =====================================================
|
||||
|
||||
import { NextRequest } from 'next/server';
|
||||
|
||||
export interface IPDetectionResult {
|
||||
detectedIP: string;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
source: string;
|
||||
allCandidates: string[];
|
||||
isPublicIP: boolean;
|
||||
}
|
||||
|
||||
export class SmartIPDetector {
|
||||
private static instance: SmartIPDetector;
|
||||
|
||||
public static getInstance(): SmartIPDetector {
|
||||
if (!SmartIPDetector.instance) {
|
||||
SmartIPDetector.instance = new SmartIPDetector();
|
||||
}
|
||||
return SmartIPDetector.instance;
|
||||
}
|
||||
|
||||
// 檢查是否為公網 IP
|
||||
private isPublicIP(ip: string): boolean {
|
||||
if (!ip || ip === 'unknown') return false;
|
||||
|
||||
// 本地地址
|
||||
if (ip === '127.0.0.1' || ip === '::1' || ip === 'localhost') return false;
|
||||
|
||||
// 私有地址範圍
|
||||
if (ip.startsWith('192.168.') ||
|
||||
ip.startsWith('10.') ||
|
||||
ip.startsWith('172.') ||
|
||||
ip.startsWith('169.254.')) return false;
|
||||
|
||||
// IPv6 本地地址
|
||||
if (ip.startsWith('fe80:') ||
|
||||
ip.startsWith('::1') ||
|
||||
ip.startsWith('::ffff:127.0.0.1')) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 從 x-forwarded-for 解析 IP
|
||||
private parseForwardedFor(forwarded: string): string[] {
|
||||
if (!forwarded) return [];
|
||||
|
||||
return forwarded
|
||||
.split(',')
|
||||
.map(ip => ip.trim())
|
||||
.filter(ip => ip && ip !== 'unknown');
|
||||
}
|
||||
|
||||
// 智能偵測客戶端 IP
|
||||
public detectClientIP(request: NextRequest): IPDetectionResult {
|
||||
const candidates: string[] = [];
|
||||
const sources: { [key: string]: string } = {};
|
||||
|
||||
// 1. 收集所有可能的 IP 來源
|
||||
const headers = {
|
||||
'x-forwarded-for': request.headers.get('x-forwarded-for'),
|
||||
'x-real-ip': request.headers.get('x-real-ip'),
|
||||
'cf-connecting-ip': request.headers.get('cf-connecting-ip'),
|
||||
'x-client-ip': request.headers.get('x-client-ip'),
|
||||
'x-forwarded': request.headers.get('x-forwarded'),
|
||||
'x-cluster-client-ip': request.headers.get('x-cluster-client-ip'),
|
||||
'x-original-forwarded-for': request.headers.get('x-original-forwarded-for'),
|
||||
'x-remote-addr': request.headers.get('x-remote-addr'),
|
||||
'remote-addr': request.headers.get('remote-addr'),
|
||||
'client-ip': request.headers.get('client-ip'),
|
||||
};
|
||||
|
||||
// 2. 從各個標頭提取 IP
|
||||
Object.entries(headers).forEach(([header, value]) => {
|
||||
if (value) {
|
||||
if (header === 'x-forwarded-for') {
|
||||
const ips = this.parseForwardedFor(value);
|
||||
ips.forEach(ip => {
|
||||
candidates.push(ip);
|
||||
sources[ip] = header;
|
||||
});
|
||||
} else {
|
||||
candidates.push(value);
|
||||
sources[value] = header;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 添加 NextRequest 的 IP (如果可用)
|
||||
const nextIP = (request as any).ip;
|
||||
if (nextIP) {
|
||||
candidates.push(nextIP);
|
||||
sources[nextIP] = 'next-request';
|
||||
}
|
||||
|
||||
// 4. 去重並過濾
|
||||
const uniqueCandidates = [...new Set(candidates)].filter(ip =>
|
||||
ip && ip !== 'unknown' && ip !== '::1' && ip !== '127.0.0.1'
|
||||
);
|
||||
|
||||
console.log('🔍 IP 偵測候選:', {
|
||||
candidates: uniqueCandidates,
|
||||
sources: sources,
|
||||
allHeaders: Object.fromEntries(request.headers.entries())
|
||||
});
|
||||
|
||||
// 5. 智能選擇最佳 IP
|
||||
let selectedIP = 'unknown';
|
||||
let confidence: 'high' | 'medium' | 'low' = 'low';
|
||||
let source = 'unknown';
|
||||
|
||||
// 優先級 1: Cloudflare IP (最高可信度)
|
||||
const cfIP = uniqueCandidates.find(ip => sources[ip] === 'cf-connecting-ip');
|
||||
if (cfIP && this.isPublicIP(cfIP)) {
|
||||
selectedIP = cfIP;
|
||||
confidence = 'high';
|
||||
source = 'cf-connecting-ip';
|
||||
}
|
||||
|
||||
// 優先級 2: 其他代理標頭中的公網 IP
|
||||
if (selectedIP === 'unknown') {
|
||||
const proxyHeaders = ['x-real-ip', 'x-client-ip', 'x-cluster-client-ip'];
|
||||
for (const header of proxyHeaders) {
|
||||
const ip = uniqueCandidates.find(candidate => sources[candidate] === header);
|
||||
if (ip && this.isPublicIP(ip)) {
|
||||
selectedIP = ip;
|
||||
confidence = 'high';
|
||||
source = header;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 優先級 3: x-forwarded-for 中的第一個公網 IP
|
||||
if (selectedIP === 'unknown') {
|
||||
const forwardedIPs = uniqueCandidates.filter(ip => sources[ip] === 'x-forwarded-for');
|
||||
const publicForwardedIP = forwardedIPs.find(ip => this.isPublicIP(ip));
|
||||
if (publicForwardedIP) {
|
||||
selectedIP = publicForwardedIP;
|
||||
confidence = 'medium';
|
||||
source = 'x-forwarded-for';
|
||||
}
|
||||
}
|
||||
|
||||
// 優先級 4: 任何公網 IP
|
||||
if (selectedIP === 'unknown') {
|
||||
const publicIP = uniqueCandidates.find(ip => this.isPublicIP(ip));
|
||||
if (publicIP) {
|
||||
selectedIP = publicIP;
|
||||
confidence = 'medium';
|
||||
source = sources[publicIP] || 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// 優先級 5: 任何非本地 IP
|
||||
if (selectedIP === 'unknown') {
|
||||
const nonLocalIP = uniqueCandidates.find(ip =>
|
||||
ip !== '::1' && ip !== '127.0.0.1' && !ip.startsWith('192.168.') &&
|
||||
!ip.startsWith('10.') && !ip.startsWith('172.')
|
||||
);
|
||||
if (nonLocalIP) {
|
||||
selectedIP = nonLocalIP;
|
||||
confidence = 'low';
|
||||
source = sources[nonLocalIP] || 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// 優先級 6: 第一個候選 IP
|
||||
if (selectedIP === 'unknown' && uniqueCandidates.length > 0) {
|
||||
selectedIP = uniqueCandidates[0];
|
||||
confidence = 'low';
|
||||
source = sources[selectedIP] || 'unknown';
|
||||
}
|
||||
|
||||
const result: IPDetectionResult = {
|
||||
detectedIP: selectedIP,
|
||||
confidence,
|
||||
source,
|
||||
allCandidates: uniqueCandidates,
|
||||
isPublicIP: this.isPublicIP(selectedIP)
|
||||
};
|
||||
|
||||
console.log('🎯 IP 偵測結果:', result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 驗證 IP 格式
|
||||
public isValidIP(ip: string): boolean {
|
||||
if (!ip || ip === 'unknown') return false;
|
||||
|
||||
// IPv4 格式檢查
|
||||
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
if (ipv4Regex.test(ip)) {
|
||||
const parts = ip.split('.').map(Number);
|
||||
return parts.every(part => part >= 0 && part <= 255);
|
||||
}
|
||||
|
||||
// IPv6 格式檢查(簡化)
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
||||
return ipv6Regex.test(ip);
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const smartIPDetector = SmartIPDetector.getInstance();
|
186
lib/ultimate-connection-killer.ts
Normal file
186
lib/ultimate-connection-killer.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
// =====================================================
|
||||
// 終極連線殺手 - 強制清理所有連線
|
||||
// =====================================================
|
||||
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
export class UltimateConnectionKiller {
|
||||
private static instance: UltimateConnectionKiller;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): UltimateConnectionKiller {
|
||||
if (!UltimateConnectionKiller.instance) {
|
||||
UltimateConnectionKiller.instance = new UltimateConnectionKiller();
|
||||
}
|
||||
return UltimateConnectionKiller.instance;
|
||||
}
|
||||
|
||||
// 終極清理 - 殺死所有連線
|
||||
public async ultimateKill() {
|
||||
console.log('💀 執行終極連線清理...');
|
||||
|
||||
try {
|
||||
// 1. 建立一個臨時連線來執行清理
|
||||
const tempConnection = await mysql.createConnection({
|
||||
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',
|
||||
});
|
||||
|
||||
try {
|
||||
// 2. 獲取所有連線
|
||||
const [connections] = await tempConnection.execute(`
|
||||
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
|
||||
FROM information_schema.PROCESSLIST
|
||||
WHERE USER = ?
|
||||
ORDER BY TIME DESC
|
||||
`, [process.env.DB_USER || 'A999']);
|
||||
|
||||
console.log(`🔍 找到 ${connections.length} 個連線需要清理`);
|
||||
|
||||
// 3. 殺死所有連線(除了當前連線)
|
||||
let killedCount = 0;
|
||||
for (const conn of connections) {
|
||||
if (conn.ID !== tempConnection.threadId) {
|
||||
try {
|
||||
await tempConnection.execute(`KILL ${conn.ID}`);
|
||||
console.log(`💀 已殺死連線 ${conn.ID} (用戶: ${conn.USER}, 時間: ${conn.TIME}s)`);
|
||||
killedCount++;
|
||||
} catch (error: any) {
|
||||
console.log(`⚠️ 無法殺死連線 ${conn.ID}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ 已殺死 ${killedCount} 個連線`);
|
||||
|
||||
// 4. 等待連線完全關閉
|
||||
console.log('⏳ 等待連線完全關閉...');
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// 5. 再次檢查狀態
|
||||
const [finalConnections] = await tempConnection.execute(`
|
||||
SELECT COUNT(*) as count FROM information_schema.PROCESSLIST
|
||||
WHERE USER = ?
|
||||
`, [process.env.DB_USER || 'A999']);
|
||||
|
||||
const remainingConnections = finalConnections[0].count;
|
||||
console.log(`📊 清理後剩餘連線: ${remainingConnections}`);
|
||||
|
||||
if (remainingConnections <= 1) {
|
||||
console.log('🎉 連線清理成功!');
|
||||
} else {
|
||||
console.warn(`⚠️ 仍有 ${remainingConnections - 1} 個連線未清理`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
killedCount,
|
||||
remainingConnections: remainingConnections - 1, // 減去當前連線
|
||||
message: `已殺死 ${killedCount} 個連線,剩餘 ${remainingConnections - 1} 個`
|
||||
};
|
||||
|
||||
} finally {
|
||||
// 6. 關閉臨時連線
|
||||
await tempConnection.end();
|
||||
console.log('🔌 臨時連線已關閉');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 終極清理失敗:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '未知錯誤'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 檢查連線狀態
|
||||
public async checkStatus() {
|
||||
try {
|
||||
const tempConnection = await mysql.createConnection({
|
||||
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',
|
||||
});
|
||||
|
||||
try {
|
||||
// 獲取連線統計
|
||||
const [statusResult] = await tempConnection.execute(`
|
||||
SHOW STATUS LIKE 'Threads_connected'
|
||||
`);
|
||||
|
||||
const [maxConnResult] = await tempConnection.execute(`
|
||||
SHOW VARIABLES LIKE 'max_connections'
|
||||
`);
|
||||
|
||||
const [connections] = await tempConnection.execute(`
|
||||
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
|
||||
FROM information_schema.PROCESSLIST
|
||||
WHERE USER = ?
|
||||
ORDER BY TIME DESC
|
||||
`, [process.env.DB_USER || 'A999']);
|
||||
|
||||
const currentConnections = statusResult[0]?.Value || 0;
|
||||
const maxConnections = maxConnResult[0]?.Value || 100;
|
||||
const usagePercentage = (currentConnections / maxConnections) * 100;
|
||||
|
||||
return {
|
||||
currentConnections: parseInt(currentConnections),
|
||||
maxConnections: parseInt(maxConnections),
|
||||
usagePercentage,
|
||||
connectionDetails: connections,
|
||||
connectionCount: connections.length
|
||||
};
|
||||
|
||||
} finally {
|
||||
await tempConnection.end();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 檢查狀態失敗:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 強制重啟資料庫連線
|
||||
public async forceRestart() {
|
||||
console.log('🔄 強制重啟資料庫連線...');
|
||||
|
||||
try {
|
||||
// 1. 先殺死所有連線
|
||||
await this.ultimateKill();
|
||||
|
||||
// 2. 等待一段時間
|
||||
console.log('⏳ 等待系統穩定...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// 3. 檢查狀態
|
||||
const status = await this.checkStatus();
|
||||
|
||||
if (status && status.connectionCount <= 1) {
|
||||
console.log('✅ 資料庫連線重啟成功');
|
||||
return { success: true, message: '資料庫連線重啟成功' };
|
||||
} else {
|
||||
console.warn('⚠️ 資料庫連線重啟後仍有連線存在');
|
||||
return { success: false, message: '重啟後仍有連線存在' };
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 強制重啟失敗:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : '未知錯誤' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const ultimateKiller = UltimateConnectionKiller.getInstance();
|
Reference in New Issue
Block a user