Files
ai-showcase-platform/lib/ip-based-connection-manager.ts

252 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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