// ===================================================== // 基於 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(); 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();