清理不必要的測試檔案
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
|
||||
import { dbShutdownManager } from './database-shutdown-manager';
|
||||
import { startConnectionMonitoring } from './database-middleware';
|
||||
import { smartPool } from './smart-connection-pool';
|
||||
|
||||
// 應用程式初始化
|
||||
export function initializeApp() {
|
||||
@@ -20,11 +19,6 @@ export function initializeApp() {
|
||||
console.log('📊 啟動資料庫連線監控...');
|
||||
startConnectionMonitoring();
|
||||
|
||||
// 初始化智能連線池
|
||||
console.log('🧠 初始化智能連線池...');
|
||||
const poolStats = smartPool.getConnectionStats();
|
||||
console.log('✅ 智能連線池已初始化:', poolStats);
|
||||
|
||||
console.log('✅ 應用程式初始化完成');
|
||||
|
||||
} catch (error) {
|
||||
|
@@ -1,231 +0,0 @@
|
||||
// =====================================================
|
||||
// 客戶端連線清理腳本
|
||||
// =====================================================
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,178 +0,0 @@
|
||||
// =====================================================
|
||||
// 連線生命週期管理器
|
||||
// =====================================================
|
||||
|
||||
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,7 +5,6 @@
|
||||
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;
|
||||
@@ -58,16 +57,6 @@ export class DatabaseShutdownManager {
|
||||
}
|
||||
});
|
||||
|
||||
// 添加智能連線池關閉處理器
|
||||
this.addShutdownHandler('smart-pool', async () => {
|
||||
console.log('🔄 正在清理智能連線池...');
|
||||
try {
|
||||
smartPool.forceCleanup();
|
||||
console.log('✅ 智能連線池已清理');
|
||||
} catch (error) {
|
||||
console.error('❌ 清理智能連線池時發生錯誤:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 註冊系統信號處理器
|
||||
this.registerSystemHandlers();
|
||||
|
@@ -1,175 +0,0 @@
|
||||
// =====================================================
|
||||
// 緊急連線清理工具
|
||||
// =====================================================
|
||||
|
||||
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();
|
@@ -1,251 +0,0 @@
|
||||
// =====================================================
|
||||
// 基於 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();
|
@@ -1,280 +0,0 @@
|
||||
// =====================================================
|
||||
// 智能連線清理器
|
||||
// =====================================================
|
||||
|
||||
import mysql from 'mysql2/promise';
|
||||
import { db } from './database';
|
||||
import { smartIPDetector } from './smart-ip-detector';
|
||||
import { NextRequest } from 'next/server';
|
||||
|
||||
export interface CleanupResult {
|
||||
success: boolean;
|
||||
killedCount: number;
|
||||
message: string;
|
||||
details: {
|
||||
userRealIP?: string;
|
||||
infrastructureIPs?: string[];
|
||||
cleanedConnections: Array<{
|
||||
id: number;
|
||||
host: string;
|
||||
time: number;
|
||||
state: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
export class SmartConnectionCleaner {
|
||||
private static instance: SmartConnectionCleaner;
|
||||
|
||||
public static getInstance(): SmartConnectionCleaner {
|
||||
if (!SmartConnectionCleaner.instance) {
|
||||
SmartConnectionCleaner.instance = new SmartConnectionCleaner();
|
||||
}
|
||||
return SmartConnectionCleaner.instance;
|
||||
}
|
||||
|
||||
// 智能清理連線
|
||||
public async smartCleanup(request: NextRequest): Promise<CleanupResult> {
|
||||
try {
|
||||
// 1. 使用智能 IP 偵測器獲取真實 IP
|
||||
const ipDetection = smartIPDetector.detectClientIP(request);
|
||||
const userRealIP = ipDetection.detectedIP;
|
||||
|
||||
console.log('🧠 智能清理開始:', {
|
||||
detectedIP: userRealIP,
|
||||
confidence: ipDetection.confidence,
|
||||
source: ipDetection.source,
|
||||
isUserRealIP: ipDetection.isUserRealIP,
|
||||
infrastructureIPs: ipDetection.infrastructureIPs
|
||||
});
|
||||
|
||||
// 2. 建立資料庫連線
|
||||
const connection = await db.getConnection();
|
||||
let killedCount = 0;
|
||||
const cleanedConnections: Array<{
|
||||
id: number;
|
||||
host: string;
|
||||
time: number;
|
||||
state: string;
|
||||
}> = [];
|
||||
|
||||
try {
|
||||
// 3. 獲取所有相關連線
|
||||
const [allConnections] = await connection.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(`🔍 找到 ${allConnections.length} 個總連線`);
|
||||
|
||||
// 4. 分類連線
|
||||
const userConnections = allConnections.filter((conn: any) =>
|
||||
this.isUserConnection(conn.HOST, userRealIP)
|
||||
);
|
||||
|
||||
const infrastructureConnections = allConnections.filter((conn: any) =>
|
||||
this.isInfrastructureConnection(conn.HOST)
|
||||
);
|
||||
|
||||
console.log(`👤 用戶連線: ${userConnections.length} 個`);
|
||||
console.log(`🏗️ 基礎設施連線: ${infrastructureConnections.length} 個`);
|
||||
|
||||
// 5. 清理用戶連線(優先)
|
||||
if (userConnections.length > 0) {
|
||||
console.log(`🧹 開始清理用戶連線 (IP: ${userRealIP})`);
|
||||
|
||||
for (const conn of userConnections) {
|
||||
if (conn.ID !== connection.threadId) {
|
||||
try {
|
||||
await connection.execute(`KILL ${conn.ID}`);
|
||||
console.log(`💀 已殺死用戶連線 ${conn.ID} (HOST: ${conn.HOST})`);
|
||||
killedCount++;
|
||||
cleanedConnections.push({
|
||||
id: conn.ID,
|
||||
host: conn.HOST,
|
||||
time: conn.TIME,
|
||||
state: conn.STATE || 'Sleep'
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.log(`⚠️ 無法殺死用戶連線 ${conn.ID}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 清理基礎設施連線(可選,根據配置)
|
||||
const shouldCleanInfrastructure = process.env.CLEAN_INFRASTRUCTURE_CONNECTIONS === 'true';
|
||||
|
||||
if (shouldCleanInfrastructure && infrastructureConnections.length > 0) {
|
||||
console.log(`🧹 開始清理基礎設施連線`);
|
||||
|
||||
for (const conn of infrastructureConnections) {
|
||||
if (conn.ID !== connection.threadId) {
|
||||
try {
|
||||
await connection.execute(`KILL ${conn.ID}`);
|
||||
console.log(`💀 已殺死基礎設施連線 ${conn.ID} (HOST: ${conn.HOST})`);
|
||||
killedCount++;
|
||||
cleanedConnections.push({
|
||||
id: conn.ID,
|
||||
host: conn.HOST,
|
||||
time: conn.TIME,
|
||||
state: conn.STATE || 'Sleep'
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.log(`⚠️ 無法殺死基礎設施連線 ${conn.ID}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ 智能清理完成: 共清理 ${killedCount} 個連線`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
killedCount,
|
||||
message: `智能清理完成: 共清理 ${killedCount} 個連線`,
|
||||
details: {
|
||||
userRealIP: userRealIP !== 'unknown' ? userRealIP : undefined,
|
||||
infrastructureIPs: ipDetection.infrastructureIPs,
|
||||
cleanedConnections
|
||||
}
|
||||
};
|
||||
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 智能清理失敗:', error);
|
||||
return {
|
||||
success: false,
|
||||
killedCount: 0,
|
||||
message: `智能清理失敗: ${error instanceof Error ? error.message : '未知錯誤'}`,
|
||||
details: {
|
||||
cleanedConnections: []
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 檢查是否為用戶連線
|
||||
private isUserConnection(host: string, userIP: string): boolean {
|
||||
if (!host || !userIP || userIP === 'unknown') return false;
|
||||
|
||||
// 直接匹配用戶 IP
|
||||
if (host.includes(userIP)) return true;
|
||||
|
||||
// 匹配用戶 IP 的變體格式
|
||||
const ipVariants = [
|
||||
userIP,
|
||||
userIP.replace(/\./g, '-'), // 61.227.253.171 -> 61-227-253-171
|
||||
userIP.replace(/\./g, '_'), // 61.227.253.171 -> 61_227_253_171
|
||||
];
|
||||
|
||||
return ipVariants.some(variant => host.includes(variant));
|
||||
}
|
||||
|
||||
// 檢查是否為基礎設施連線
|
||||
private isInfrastructureConnection(host: string): boolean {
|
||||
if (!host) return false;
|
||||
|
||||
// AWS EC2 實例
|
||||
if (host.includes('ec2-') && host.includes('.amazonaws.com')) return true;
|
||||
|
||||
// 其他基礎設施模式
|
||||
const infrastructurePatterns = [
|
||||
'.amazonaws.com',
|
||||
'.vercel.app',
|
||||
'vercel',
|
||||
'cloudflare',
|
||||
'fastly.com',
|
||||
'cloudfront.net'
|
||||
];
|
||||
|
||||
return infrastructurePatterns.some(pattern => host.includes(pattern));
|
||||
}
|
||||
|
||||
// 獲取連線統計
|
||||
public async getConnectionStats(): Promise<{
|
||||
total: number;
|
||||
user: number;
|
||||
infrastructure: number;
|
||||
other: number;
|
||||
details: Array<{
|
||||
id: number;
|
||||
host: string;
|
||||
time: number;
|
||||
state: string;
|
||||
type: 'user' | 'infrastructure' | 'other';
|
||||
}>;
|
||||
}> {
|
||||
try {
|
||||
const connection = await db.getConnection();
|
||||
|
||||
try {
|
||||
const [connections] = await connection.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 stats = {
|
||||
total: connections.length,
|
||||
user: 0,
|
||||
infrastructure: 0,
|
||||
other: 0,
|
||||
details: [] as Array<{
|
||||
id: number;
|
||||
host: string;
|
||||
time: number;
|
||||
state: string;
|
||||
type: 'user' | 'infrastructure' | 'other';
|
||||
}>
|
||||
};
|
||||
|
||||
for (const conn of connections) {
|
||||
let type: 'user' | 'infrastructure' | 'other' = 'other';
|
||||
|
||||
if (this.isInfrastructureConnection(conn.HOST)) {
|
||||
type = 'infrastructure';
|
||||
stats.infrastructure++;
|
||||
} else if (this.isUserConnection(conn.HOST, 'unknown')) {
|
||||
type = 'user';
|
||||
stats.user++;
|
||||
} else {
|
||||
stats.other++;
|
||||
}
|
||||
|
||||
stats.details.push({
|
||||
id: conn.ID,
|
||||
host: conn.HOST,
|
||||
time: conn.TIME,
|
||||
state: conn.STATE || 'Sleep',
|
||||
type
|
||||
});
|
||||
}
|
||||
|
||||
return stats;
|
||||
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 獲取連線統計失敗:', error);
|
||||
return {
|
||||
total: 0,
|
||||
user: 0,
|
||||
infrastructure: 0,
|
||||
other: 0,
|
||||
details: []
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 導出單例實例
|
||||
export const smartConnectionCleaner = SmartConnectionCleaner.getInstance();
|
@@ -1,202 +0,0 @@
|
||||
// =====================================================
|
||||
// 智能連線池包裝器
|
||||
// =====================================================
|
||||
|
||||
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();
|
@@ -1,153 +0,0 @@
|
||||
// =====================================================
|
||||
// 智能 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();
|
@@ -1,272 +0,0 @@
|
||||
// =====================================================
|
||||
// 智能 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;
|
||||
}
|
||||
|
||||
// 檢查是否為基礎設施 IP(Vercel、AWS、Cloudflare 等)
|
||||
private isInfrastructureIP(ip: string): boolean {
|
||||
if (!ip) return false;
|
||||
|
||||
// Vercel/AWS EC2 實例
|
||||
if (ip.includes('ec2-') && ip.includes('.compute-1.amazonaws.com')) return true;
|
||||
if (ip.includes('ec2-') && ip.includes('.amazonaws.com')) return true;
|
||||
|
||||
// AWS 其他服務
|
||||
if (ip.includes('.amazonaws.com')) return true;
|
||||
|
||||
// Vercel 相關
|
||||
if (ip.includes('vercel')) return true;
|
||||
|
||||
// Cloudflare 相關(但 cf-connecting-ip 是我們想要的)
|
||||
if (ip.includes('cloudflare') && !ip.includes('cf-connecting-ip')) return true;
|
||||
|
||||
// 其他常見的 CDN/代理服務
|
||||
const infrastructurePatterns = [
|
||||
'fastly.com',
|
||||
'cloudfront.net',
|
||||
'akamai.net',
|
||||
'maxcdn.com',
|
||||
'keycdn.com'
|
||||
];
|
||||
|
||||
return infrastructurePatterns.some(pattern => ip.includes(pattern));
|
||||
}
|
||||
|
||||
// 檢查是否為用戶真實 IP
|
||||
private isUserRealIP(ip: string): boolean {
|
||||
if (!ip || ip === 'unknown') return false;
|
||||
|
||||
// 不是基礎設施 IP
|
||||
if (this.isInfrastructureIP(ip)) return false;
|
||||
|
||||
// 是公網 IP
|
||||
if (!this.isPublicIP(ip)) return false;
|
||||
|
||||
// 是有效的 IP 格式
|
||||
if (!this.isValidIP(ip)) 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 (最高可信度) - 但要是用戶真實 IP
|
||||
const cfIP = uniqueCandidates.find(ip => sources[ip] === 'cf-connecting-ip');
|
||||
if (cfIP && this.isUserRealIP(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.isUserRealIP(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 userRealIP = forwardedIPs.find(ip => this.isUserRealIP(ip));
|
||||
if (userRealIP) {
|
||||
selectedIP = userRealIP;
|
||||
confidence = 'medium';
|
||||
source = 'x-forwarded-for';
|
||||
}
|
||||
}
|
||||
|
||||
// 優先級 4: 任何用戶真實 IP
|
||||
if (selectedIP === 'unknown') {
|
||||
const userRealIP = uniqueCandidates.find(ip => this.isUserRealIP(ip));
|
||||
if (userRealIP) {
|
||||
selectedIP = userRealIP;
|
||||
confidence = 'medium';
|
||||
source = sources[userRealIP] || 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// 優先級 5: 任何公網 IP(非基礎設施)
|
||||
if (selectedIP === 'unknown') {
|
||||
const publicIP = uniqueCandidates.find(ip =>
|
||||
this.isPublicIP(ip) && !this.isInfrastructureIP(ip)
|
||||
);
|
||||
if (publicIP) {
|
||||
selectedIP = publicIP;
|
||||
confidence = 'low';
|
||||
source = sources[publicIP] || 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// 優先級 6: 任何非本地 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.') &&
|
||||
!this.isInfrastructureIP(ip)
|
||||
);
|
||||
if (nonLocalIP) {
|
||||
selectedIP = nonLocalIP;
|
||||
confidence = 'low';
|
||||
source = sources[nonLocalIP] || 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// 優先級 7: 第一個候選 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,
|
||||
isInfrastructureIP: this.isInfrastructureIP(selectedIP),
|
||||
isUserRealIP: this.isUserRealIP(selectedIP),
|
||||
infrastructureIPs: uniqueCandidates.filter(ip => this.isInfrastructureIP(ip)),
|
||||
userRealIPs: uniqueCandidates.filter(ip => this.isUserRealIP(ip))
|
||||
});
|
||||
|
||||
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();
|
@@ -1,186 +0,0 @@
|
||||
// =====================================================
|
||||
// 終極連線殺手 - 強制清理所有連線
|
||||
// =====================================================
|
||||
|
||||
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