From 808d5bb52cb1563536291ecf53ea5a54e6c3100f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Sun, 21 Sep 2025 02:46:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=BE=A9=20too=20many=20connection=20?= =?UTF-8?q?=E5=95=8F=E9=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/connection-monitor/page.tsx | 371 +++++++++++++++++++ app/admin/database-shutdown/page.tsx | 267 ++++++++++++++ app/admin/emergency-cleanup/page.tsx | 281 ++++++++++++++ app/admin/ip-cleanup/page.tsx | 442 +++++++++++++++++++++++ app/admin/ultimate-kill/page.tsx | 314 ++++++++++++++++ app/api/competitions/[id]/route.ts | 37 ++ app/api/connection-monitor/route.ts | 122 +++++++ app/api/debug-ip/route.ts | 59 +++ app/api/emergency-cleanup/route.ts | 104 ++++++ app/api/ip-cleanup/route.ts | 177 +++++++++ app/api/set-client-ip/route.ts | 78 ++++ app/api/test-shutdown/route.ts | 100 +++++ app/api/ultimate-kill/route.ts | 96 +++++ app/auto-ip-test/page.tsx | 297 +++++++++++++++ app/debug-ip/page.tsx | 325 +++++++++++++++++ app/layout.tsx | 3 + app/set-ip/page.tsx | 174 +++++++++ app/test-ip-cleanup/page.tsx | 226 ++++++++++++ components/client-connection-cleanup.tsx | 28 ++ lib/app-initializer.ts | 39 ++ lib/client-connection-cleanup.ts | 231 ++++++++++++ lib/connection-lifecycle-manager.ts | 178 +++++++++ lib/database-middleware.ts | 33 +- lib/database-monitor.ts | 93 +++-- lib/database-service-smart.ts | 89 +++++ lib/database-shutdown-manager.ts | 197 ++++++++++ lib/emergency-connection-cleanup.ts | 175 +++++++++ lib/ip-based-connection-manager.ts | 251 +++++++++++++ lib/smart-connection-pool.ts | 202 +++++++++++ lib/smart-ip-connection-pool.ts | 153 ++++++++ lib/smart-ip-detector.ts | 208 +++++++++++ lib/ultimate-connection-killer.ts | 186 ++++++++++ scripts/insert-team-members.sql | 50 --- scripts/insert-test-team-members.js | 82 ----- scripts/test-team-data.js | 70 ---- scripts/ultimate-kill.js | 93 +++++ 36 files changed, 5582 insertions(+), 249 deletions(-) create mode 100644 app/admin/connection-monitor/page.tsx create mode 100644 app/admin/database-shutdown/page.tsx create mode 100644 app/admin/emergency-cleanup/page.tsx create mode 100644 app/admin/ip-cleanup/page.tsx create mode 100644 app/admin/ultimate-kill/page.tsx create mode 100644 app/api/competitions/[id]/route.ts create mode 100644 app/api/connection-monitor/route.ts create mode 100644 app/api/debug-ip/route.ts create mode 100644 app/api/emergency-cleanup/route.ts create mode 100644 app/api/ip-cleanup/route.ts create mode 100644 app/api/set-client-ip/route.ts create mode 100644 app/api/test-shutdown/route.ts create mode 100644 app/api/ultimate-kill/route.ts create mode 100644 app/auto-ip-test/page.tsx create mode 100644 app/debug-ip/page.tsx create mode 100644 app/set-ip/page.tsx create mode 100644 app/test-ip-cleanup/page.tsx create mode 100644 components/client-connection-cleanup.tsx create mode 100644 lib/app-initializer.ts create mode 100644 lib/client-connection-cleanup.ts create mode 100644 lib/connection-lifecycle-manager.ts create mode 100644 lib/database-service-smart.ts create mode 100644 lib/database-shutdown-manager.ts create mode 100644 lib/emergency-connection-cleanup.ts create mode 100644 lib/ip-based-connection-manager.ts create mode 100644 lib/smart-connection-pool.ts create mode 100644 lib/smart-ip-connection-pool.ts create mode 100644 lib/smart-ip-detector.ts create mode 100644 lib/ultimate-connection-killer.ts delete mode 100644 scripts/insert-team-members.sql delete mode 100644 scripts/insert-test-team-members.js delete mode 100644 scripts/test-team-data.js create mode 100644 scripts/ultimate-kill.js diff --git a/app/admin/connection-monitor/page.tsx b/app/admin/connection-monitor/page.tsx new file mode 100644 index 0000000..48cc42f --- /dev/null +++ b/app/admin/connection-monitor/page.tsx @@ -0,0 +1,371 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Loader2, Database, Trash2, Settings, Activity, Clock, Users } from 'lucide-react'; + +interface ConnectionStats { + totalConnections: number; + idleConnections: number; + oldConnections: number; + maxIdleTime: number; + maxConnectionAge: number; + connections: Array<{ + createdAt: string; + lastUsed: string; + idleTime: number; + age: number; + userId?: string; + sessionId?: string; + }>; +} + +export default function ConnectionMonitorPage() { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + const [maxIdleTime, setMaxIdleTime] = useState(300000); // 5分鐘 + const [maxConnectionAge, setMaxConnectionAge] = useState(1800000); // 30分鐘 + + // 獲取連線統計 + const fetchStats = async () => { + try { + setLoading(true); + const response = await fetch('/api/connection-monitor?action=stats'); + const data = await response.json(); + + if (data.success) { + setStats(data.data); + setMessage('統計更新成功'); + setError(''); + } else { + setError(data.error || '獲取統計失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 強制清理連線 + const forceCleanup = async () => { + if (!confirm('確定要強制清理所有連線嗎?這可能會影響正在進行的操作!')) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/connection-monitor?action=cleanup'); + const data = await response.json(); + + if (data.success) { + setMessage('連線清理完成'); + setError(''); + await fetchStats(); // 重新獲取統計 + } else { + setError(data.error || '清理失敗'); + } + } catch (err) { + setError('清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 測試智能連線 + const testConnection = async () => { + try { + setLoading(true); + const response = await fetch('/api/connection-monitor?action=test'); + const data = await response.json(); + + if (data.success) { + setMessage('智能連線測試成功'); + setError(''); + await fetchStats(); // 重新獲取統計 + } else { + setError(data.error || '測試失敗'); + } + } catch (err) { + setError('測試錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 更新清理配置 + const updateConfig = async () => { + try { + setLoading(true); + const response = await fetch('/api/connection-monitor', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + action: 'config', + maxIdleTime, + maxConnectionAge + }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage('清理配置更新成功'); + setError(''); + await fetchStats(); // 重新獲取統計 + } else { + setError(data.error || '配置更新失敗'); + } + } catch (err) { + setError('配置更新錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 格式化時間 + const formatTime = (ms: number) => { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + if (hours > 0) return `${hours}h ${minutes % 60}m`; + if (minutes > 0) return `${minutes}m ${seconds % 60}s`; + return `${seconds}s`; + }; + + // 組件載入時獲取統計 + useEffect(() => { + fetchStats(); + + // 每30秒自動更新統計 + const interval = setInterval(fetchStats, 30000); + return () => clearInterval(interval); + }, []); + + return ( +
+
+
+

連線監控管理

+

+ 監控和管理資料庫連線的生命週期 +

+
+ +
+ + {/* 統計概覽 */} + {stats && ( +
+ + +
+ +
+

總連線數

+

{stats.totalConnections}

+
+
+
+
+ + + +
+ +
+

空閒連線

+

{stats.idleConnections}

+
+
+
+
+ + + +
+ +
+

舊連線

+

{stats.oldConnections}

+
+
+
+
+ + + +
+ +
+

健康狀態

+

+ {stats.idleConnections + stats.oldConnections === 0 ? '良好' : '需清理'} +

+
+
+
+
+
+ )} + + {/* 操作按鈕 */} + + + + + 連線管理 + + + 管理和清理資料庫連線 + + + +
+ + + + + +
+ + {/* 配置設定 */} +
+
+ + setMaxIdleTime(Number(e.target.value))} + placeholder="300000 (5分鐘)" + /> +
+
+ + setMaxConnectionAge(Number(e.target.value))} + placeholder="1800000 (30分鐘)" + /> +
+
+
+
+ + {/* 連線詳情 */} + {stats && stats.connections.length > 0 && ( + + + 連線詳情 + + 當前活躍的資料庫連線列表 + + + +
+ {stats.connections.map((conn, index) => ( +
+
+ #{index + 1} +
+

+ 創建時間: {new Date(conn.createdAt).toLocaleString()} +

+

+ 最後使用: {new Date(conn.lastUsed).toLocaleString()} +

+
+
+
+ stats.maxIdleTime ? "destructive" : "default"}> + 空閒: {formatTime(conn.idleTime)} + + stats.maxConnectionAge ? "destructive" : "default"}> + 年齡: {formatTime(conn.age)} + + {conn.userId && ( + 用戶: {conn.userId} + )} +
+
+ ))} +
+
+
+ )} + + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} + + {/* 說明資訊 */} + + + 智能連線管理說明 + + +

自動清理: 系統每30秒自動檢查並清理空閒或過期的連線

+

智能追蹤: 追蹤每個連線的創建時間、最後使用時間和用戶信息

+

即時監控: 實時顯示連線狀態,幫助識別連線洩漏問題

+

自動釋放: 查詢完成後自動釋放連線,避免連線累積

+

用戶關閉網頁: 空閒連線會在設定的時間後自動清理

+
+
+
+ ); +} diff --git a/app/admin/database-shutdown/page.tsx b/app/admin/database-shutdown/page.tsx new file mode 100644 index 0000000..9b9ea18 --- /dev/null +++ b/app/admin/database-shutdown/page.tsx @@ -0,0 +1,267 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Loader2, Database, Power, AlertTriangle, CheckCircle } from 'lucide-react'; + +interface ShutdownStatus { + isShuttingDown: boolean; + handlerCount: number; + registeredHandlers: string[]; +} + +export default function DatabaseShutdownPage() { + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + // 獲取關閉狀態 + const fetchStatus = async () => { + try { + setLoading(true); + const response = await fetch('/api/test-shutdown?action=status'); + const data = await response.json(); + + if (data.success) { + setStatus(data.data); + setMessage('狀態更新成功'); + setError(''); + } else { + setError(data.error || '獲取狀態失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 測試關閉機制 + const testShutdown = async () => { + try { + setLoading(true); + const response = await fetch('/api/test-shutdown?action=test'); + const data = await response.json(); + + if (data.success) { + setMessage('關閉機制測試成功'); + setError(''); + await fetchStatus(); // 重新獲取狀態 + } else { + setError(data.error || '測試失敗'); + } + } catch (err) { + setError('測試錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 強制關閉測試 + const forceShutdown = async () => { + if (!confirm('確定要執行強制關閉測試嗎?這可能會影響應用程式運行!')) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/test-shutdown?action=force'); + const data = await response.json(); + + if (data.success) { + setMessage('強制關閉測試完成'); + setError(''); + await fetchStatus(); // 重新獲取狀態 + } else { + setError(data.error || '強制關閉測試失敗'); + } + } catch (err) { + setError('強制關閉錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 優雅關閉測試 + const gracefulShutdown = async () => { + if (!confirm('確定要執行優雅關閉測試嗎?這會關閉所有資料庫連線!')) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/test-shutdown', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'graceful' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage('優雅關閉測試完成'); + setError(''); + await fetchStatus(); // 重新獲取狀態 + } else { + setError(data.error || '優雅關閉測試失敗'); + } + } catch (err) { + setError('優雅關閉錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 組件載入時獲取狀態 + useEffect(() => { + fetchStatus(); + }, []); + + return ( +
+
+
+

資料庫關閉管理

+

+ 監控和管理資料庫連線的關閉機制 +

+
+ +
+ + {/* 狀態顯示 */} + {status && ( + + + + + 關閉狀態 + + + 當前資料庫關閉管理器的運行狀態 + + + +
+
+ 關閉狀態: + + {status.isShuttingDown ? "關閉中" : "正常"} + +
+
+ 處理器數量: + {status.handlerCount} +
+
+ 狀態: + + {status.isShuttingDown ? "異常" : "正常"} + +
+
+ +
+ 已註冊的處理器: +
+ {status.registeredHandlers.map((handler, index) => ( + + {handler} + + ))} +
+
+
+
+ )} + + {/* 操作按鈕 */} + + + + + 關閉操作 + + + 測試和管理資料庫關閉機制 + + + +
+ + + + + +
+
+
+ + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} + + {/* 說明資訊 */} + + + 使用說明 + + +

測試關閉機制: 檢查關閉處理器是否正常註冊,不會實際關閉連線

+

強制關閉測試: 模擬強制關閉所有資料庫連線(用於測試)

+

優雅關閉測試: 執行完整的優雅關閉流程(會實際關閉連線)

+

• 當應用程式收到 SIGINT 或 SIGTERM 信號時,會自動執行優雅關閉

+

• 關閉管理器會確保所有資料庫連線池都被正確關閉

+
+
+
+ ); +} diff --git a/app/admin/emergency-cleanup/page.tsx b/app/admin/emergency-cleanup/page.tsx new file mode 100644 index 0000000..eb5124d --- /dev/null +++ b/app/admin/emergency-cleanup/page.tsx @@ -0,0 +1,281 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Loader2, Database, AlertTriangle, Trash2, Eye, Zap } from 'lucide-react'; + +interface ConnectionDetail { + ID: number; + USER: string; + HOST: string; + DB: string; + COMMAND: string; + TIME: number; + STATE: string; + INFO?: string; +} + +interface ConnectionData { + connectionCount: number; + connections: ConnectionDetail[]; +} + +export default function EmergencyCleanupPage() { + const [connectionData, setConnectionData] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + // 獲取連線詳情 + const fetchConnectionDetails = async () => { + try { + setLoading(true); + const response = await fetch('/api/emergency-cleanup?action=details'); + const data = await response.json(); + + if (data.success) { + setConnectionData(data.data); + setMessage('連線詳情更新成功'); + setError(''); + } else { + setError(data.error || '獲取連線詳情失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 執行緊急清理 + const executeEmergencyCleanup = async () => { + if (!confirm('確定要執行緊急清理嗎?這會關閉所有資料庫連線!')) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/emergency-cleanup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'cleanup' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage('緊急清理完成'); + setError(''); + await fetchConnectionDetails(); // 重新獲取詳情 + } else { + setError(data.error || '緊急清理失敗'); + } + } catch (err) { + setError('緊急清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 強制殺死所有連線 + const forceKillAllConnections = async () => { + if (!confirm('確定要強制殺死所有連線嗎?這會立即終止所有資料庫連線!')) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/emergency-cleanup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'kill-all' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage('強制殺死連線完成'); + setError(''); + await fetchConnectionDetails(); // 重新獲取詳情 + } else { + setError(data.error || '強制殺死連線失敗'); + } + } catch (err) { + setError('強制殺死連線錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 組件載入時獲取連線詳情 + useEffect(() => { + fetchConnectionDetails(); + }, []); + + return ( +
+
+
+

緊急連線清理

+

+ 緊急清理和強制關閉資料庫連線 +

+
+ +
+ + {/* 警告提示 */} + + + + 警告: 這些操作會立即關閉所有資料庫連線,可能影響正在運行的應用程式。請謹慎使用! + + + + {/* 連線統計 */} + {connectionData && ( + + + + + 當前連線狀態 + + + 資料庫連線的詳細信息 + + + +
+ + 總連線數: {connectionData.connectionCount} + + 10 ? "destructive" : "default"} + className="text-lg px-3 py-1" + > + 狀態: {connectionData.connectionCount > 10 ? "異常" : "正常"} + +
+
+
+ )} + + {/* 緊急操作 */} + + + + + 緊急操作 + + + 立即清理和關閉資料庫連線 + + + +
+ + + +
+
+
+ + {/* 連線詳情列表 */} + {connectionData && connectionData.connections.length > 0 && ( + + + 連線詳情列表 + + 當前所有資料庫連線的詳細信息 + + + +
+ {connectionData.connections.map((conn, index) => ( +
+
+ #{conn.ID} +
+

+ 用戶: {conn.USER} | 主機: {conn.HOST} +

+

+ 資料庫: {conn.DB} | 命令: {conn.COMMAND} +

+

+ 時間: {conn.TIME}s | 狀態: {conn.STATE} +

+ {conn.INFO && ( +

+ 查詢: {conn.INFO.length > 100 ? conn.INFO.substring(0, 100) + '...' : conn.INFO} +

+ )} +
+
+
+ ))} +
+
+
+ )} + + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} + + {/* 使用說明 */} + + + 使用說明 + + +

緊急清理連線: 停止監控並關閉所有連線池,這是較溫和的方式

+

強制殺死所有連線: 直接殺死資料庫中的所有連線,這是更激進的方式

+

重新整理: 獲取最新的連線狀態和詳情

+

• 建議先嘗試「緊急清理連線」,如果無效再使用「強制殺死所有連線」

+
+
+
+ ); +} diff --git a/app/admin/ip-cleanup/page.tsx b/app/admin/ip-cleanup/page.tsx new file mode 100644 index 0000000..c811651 --- /dev/null +++ b/app/admin/ip-cleanup/page.tsx @@ -0,0 +1,442 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Loader2, Database, AlertTriangle, Trash2, Eye, Zap, Globe } from 'lucide-react'; + +interface ConnectionDetail { + ID: number; + USER: string; + HOST: string; + DB: string; + COMMAND: string; + TIME: number; + STATE: string; + INFO?: string; +} + +interface ConnectionStatus { + ip: string; + connectionCount: number; + connections: ConnectionDetail[]; +} + +interface LocalStats { + clientIP: string; + trackedConnections: number; + connections: Array<{ + connectionId: string; + createdAt: string; + lastUsed: string; + userAgent?: string; + }>; +} + +export default function IPCleanupPage() { + const [connectionStatus, setConnectionStatus] = useState(null); + const [localStats, setLocalStats] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + const [targetIP, setTargetIP] = useState(''); + + // 獲取當前 IP 的連線狀態 + const fetchCurrentIPStatus = async () => { + try { + setLoading(true); + const response = await fetch('/api/ip-cleanup?action=status'); + const data = await response.json(); + + if (data.success) { + setConnectionStatus(data.data); + setMessage('當前 IP 連線狀態更新成功'); + setError(''); + } else { + setError(data.error || '獲取當前 IP 連線狀態失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 獲取指定 IP 的連線狀態 + const fetchSpecificIPStatus = async () => { + if (!targetIP.trim()) { + setError('請輸入目標 IP 地址'); + return; + } + + try { + setLoading(true); + const response = await fetch(`/api/ip-cleanup?action=status-specific&ip=${encodeURIComponent(targetIP)}`); + const data = await response.json(); + + if (data.success) { + setConnectionStatus(data.data); + setMessage(`指定 IP ${targetIP} 的連線狀態更新成功`); + setError(''); + } else { + setError(data.error || '獲取指定 IP 連線狀態失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 獲取本地連線統計 + const fetchLocalStats = async () => { + try { + setLoading(true); + const response = await fetch('/api/ip-cleanup?action=local-stats'); + const data = await response.json(); + + if (data.success) { + setLocalStats(data.data); + setMessage('本地連線統計更新成功'); + setError(''); + } else { + setError(data.error || '獲取本地連線統計失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 清理當前 IP 的連線 + const cleanupCurrentIP = async () => { + if (!confirm('確定要清理當前 IP 的所有連線嗎?')) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/ip-cleanup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'cleanup-current' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage(data.message || '當前 IP 連線清理完成'); + setError(''); + await fetchCurrentIPStatus(); // 重新獲取狀態 + } else { + setError(data.error || '當前 IP 連線清理失敗'); + } + } catch (err) { + setError('當前 IP 連線清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 清理指定 IP 的連線 + const cleanupSpecificIP = async () => { + if (!targetIP.trim()) { + setError('請輸入目標 IP 地址'); + return; + } + + if (!confirm(`確定要清理 IP ${targetIP} 的所有連線嗎?`)) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/ip-cleanup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + action: 'cleanup-specific', + targetIP: targetIP.trim() + }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage(data.message || `IP ${targetIP} 連線清理完成`); + setError(''); + await fetchSpecificIPStatus(); // 重新獲取狀態 + } else { + setError(data.error || `IP ${targetIP} 連線清理失敗`); + } + } catch (err) { + setError(`IP ${targetIP} 連線清理錯誤: ` + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 清理本地追蹤的連線 + const cleanupLocalConnections = async () => { + try { + setLoading(true); + const response = await fetch('/api/ip-cleanup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'cleanup-local' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage(data.message || '本地連線清理完成'); + setError(''); + await fetchLocalStats(); // 重新獲取統計 + } else { + setError(data.error || '本地連線清理失敗'); + } + } catch (err) { + setError('本地連線清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 組件載入時獲取狀態 + useEffect(() => { + fetchCurrentIPStatus(); + fetchLocalStats(); + }, []); + + return ( +
+
+
+

IP 連線管理

+

+ 基於 IP 地址的智能連線清理和管理 +

+
+
+ + +
+
+ + {/* 當前 IP 連線狀態 */} + {connectionStatus && ( + + + + + IP 連線狀態 + + + IP: {connectionStatus.ip} | 連線數: {connectionStatus.connectionCount} + + + +
+ + 連線數: {connectionStatus.connectionCount} + + 5 ? "destructive" : "default"} + className="text-lg px-3 py-1" + > + 狀態: {connectionStatus.connectionCount > 5 ? "異常" : "正常"} + +
+
+
+ )} + + {/* 本地連線統計 */} + {localStats && ( + + + + + 本地連線統計 + + + 客戶端 IP: {localStats.clientIP} | 追蹤連線數: {localStats.trackedConnections} + + + +
+ {localStats.connections.map((conn, index) => ( +
+
+

連線 ID: {conn.connectionId}

+

+ 創建: {new Date(conn.createdAt).toLocaleString()} | + 最後使用: {new Date(conn.lastUsed).toLocaleString()} +

+
+
+ ))} +
+
+
+ )} + + {/* 操作按鈕 */} + + + + + IP 連線清理 + + + 清理指定 IP 地址的資料庫連線 + + + +
+ + + +
+ + {/* 指定 IP 清理 */} +
+ +
+ setTargetIP(e.target.value)} + placeholder="例如: 192.168.1.100" + className="flex-1" + /> + + +
+
+
+
+ + {/* 連線詳情列表 */} + {connectionStatus && connectionStatus.connections.length > 0 && ( + + + 連線詳情列表 + + IP {connectionStatus.ip} 的所有資料庫連線 + + + +
+ {connectionStatus.connections.map((conn, index) => ( +
+
+ #{conn.ID} +
+

+ 用戶: {conn.USER} | 主機: {conn.HOST} +

+

+ 資料庫: {conn.DB} | 命令: {conn.COMMAND} +

+

+ 時間: {conn.TIME}s | 狀態: {conn.STATE} +

+ {conn.INFO && ( +

+ 查詢: {conn.INFO.length > 100 ? conn.INFO.substring(0, 100) + '...' : conn.INFO} +

+ )} +
+
+
+ ))} +
+
+
+ )} + + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} + + {/* 使用說明 */} + + + IP 連線管理說明 + + +

當前 IP 連線: 顯示和清理當前訪問者的所有資料庫連線

+

指定 IP 連線: 輸入特定 IP 地址來查看和清理該 IP 的連線

+

本地追蹤連線: 清理應用程式內部追蹤的連線記錄

+

智能清理: 只清理指定 IP 的連線,不影響其他用戶

+

關閉網站時: 系統會自動清理當前 IP 的所有連線

+
+
+
+ ); +} diff --git a/app/admin/ultimate-kill/page.tsx b/app/admin/ultimate-kill/page.tsx new file mode 100644 index 0000000..e36e546 --- /dev/null +++ b/app/admin/ultimate-kill/page.tsx @@ -0,0 +1,314 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Loader2, Database, AlertTriangle, Trash2, Eye, Zap, Skull } from 'lucide-react'; + +interface ConnectionStatus { + currentConnections: number; + maxConnections: number; + usagePercentage: number; + connectionDetails: Array<{ + ID: number; + USER: string; + HOST: string; + DB: string; + COMMAND: string; + TIME: number; + STATE: string; + INFO?: string; + }>; + connectionCount: number; +} + +export default function UltimateKillPage() { + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + // 獲取連線狀態 + const fetchStatus = async () => { + try { + setLoading(true); + const response = await fetch('/api/ultimate-kill?action=status'); + const data = await response.json(); + + if (data.success) { + setStatus(data.data); + setMessage('狀態更新成功'); + setError(''); + } else { + setError(data.error || '獲取狀態失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 終極清理 - 殺死所有連線 + const ultimateKill = async () => { + if (!confirm('確定要執行終極清理嗎?這會強制殺死所有資料庫連線!')) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/ultimate-kill', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'kill-all' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage(data.message || '終極清理完成'); + setError(''); + await fetchStatus(); // 重新獲取狀態 + } else { + setError(data.error || '終極清理失敗'); + } + } catch (err) { + setError('終極清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 強制重啟資料庫連線 + const forceRestart = async () => { + if (!confirm('確定要強制重啟資料庫連線嗎?這會先殺死所有連線然後重新建立!')) { + return; + } + + try { + setLoading(true); + const response = await fetch('/api/ultimate-kill', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'restart' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage(data.message || '強制重啟完成'); + setError(''); + await fetchStatus(); // 重新獲取狀態 + } else { + setError(data.error || '強制重啟失敗'); + } + } catch (err) { + setError('強制重啟錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 組件載入時獲取狀態 + useEffect(() => { + fetchStatus(); + + // 每10秒自動更新狀態 + const interval = setInterval(fetchStatus, 10000); + return () => clearInterval(interval); + }, []); + + return ( +
+
+
+

終極連線清理

+

+ 強制殺死所有資料庫連線 - 最後手段 +

+
+ +
+ + {/* 嚴重警告 */} + + + + 嚴重警告: 這些操作會立即強制殺死所有資料庫連線,可能導致正在運行的應用程式崩潰。請確保沒有重要操作正在進行! + + + + {/* 連線狀態概覽 */} + {status && ( +
+ + +
+ +
+

總連線數

+

{status.currentConnections}

+
+
+
+
+ + + +
+ +
+

最大連線數

+

{status.maxConnections}

+
+
+
+
+ + + +
+ +
+

使用率

+

{status.usagePercentage.toFixed(1)}%

+
+
+
+
+ + + +
+ +
+

狀態

+

+ {status.connectionCount <= 1 ? '正常' : '異常'} +

+
+
+
+
+
+ )} + + {/* 終極操作 */} + + + + + 終極操作 + + + 強制殺死所有資料庫連線 - 最後手段 + + + +
+ + + +
+
+
+ + {/* 連線詳情列表 */} + {status && status.connectionDetails.length > 0 && ( + + + 連線詳情列表 + + 當前所有資料庫連線的詳細信息 + + + +
+ {status.connectionDetails.map((conn, index) => ( +
+
+ #{conn.ID} +
+

+ 用戶: {conn.USER} | 主機: {conn.HOST} +

+

+ 資料庫: {conn.DB} | 命令: {conn.COMMAND} +

+

+ 時間: {conn.TIME}s | 狀態: {conn.STATE} +

+ {conn.INFO && ( +

+ 查詢: {conn.INFO.length > 100 ? conn.INFO.substring(0, 100) + '...' : conn.INFO} +

+ )} +
+
+
+ ))} +
+
+
+ )} + + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} + + {/* 使用說明 */} + + + 終極清理說明 + + +

終極清理: 直接殺死資料庫中的所有連線,立即生效

+

強制重啟: 先殺死所有連線,等待系統穩定,然後重新建立連線

+

自動更新: 頁面每10秒自動更新連線狀態

+

最後手段: 這些操作是最後的手段,會立即終止所有連線

+

• 建議先嘗試「終極清理」,如果問題持續再使用「強制重啟」

+
+
+
+ ); +} diff --git a/app/api/competitions/[id]/route.ts b/app/api/competitions/[id]/route.ts new file mode 100644 index 0000000..c6a2c71 --- /dev/null +++ b/app/api/competitions/[id]/route.ts @@ -0,0 +1,37 @@ +// ===================================================== +// 獲取競賽詳情 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { CompetitionService } from '@/lib/services/database-service'; + +export async function GET(request: NextRequest, { params }: { params: { id: string } }) { + try { + const { id } = await params; + + // 獲取競賽詳情 + const competition = await CompetitionService.getCompetitionById(id); + + if (!competition) { + return NextResponse.json({ + success: false, + message: '競賽不存在', + error: '找不到指定的競賽' + }, { status: 404 }); + } + + return NextResponse.json({ + success: true, + message: '競賽詳情獲取成功', + data: competition + }); + + } catch (error) { + console.error('獲取競賽詳情失敗:', error); + return NextResponse.json({ + success: false, + message: '獲取競賽詳情失敗', + error: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/connection-monitor/route.ts b/app/api/connection-monitor/route.ts new file mode 100644 index 0000000..81e55c9 --- /dev/null +++ b/app/api/connection-monitor/route.ts @@ -0,0 +1,122 @@ +// ===================================================== +// 連線監控 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { smartPool } from '@/lib/smart-connection-pool'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const action = searchParams.get('action') || 'stats'; + + switch (action) { + case 'stats': + // 獲取連線統計 + const stats = smartPool.getConnectionStats(); + return NextResponse.json({ + success: true, + message: '連線統計獲取成功', + data: stats + }); + + case 'cleanup': + // 強制清理連線 + console.log('🧹 執行強制連線清理...'); + smartPool.forceCleanup(); + const newStats = smartPool.getConnectionStats(); + return NextResponse.json({ + success: true, + message: '連線清理完成', + data: newStats + }); + + case 'test': + // 測試智能連線 + try { + const testResult = await smartPool.executeQueryOne( + 'SELECT 1 as test_value', + [], + { userId: 'test', sessionId: 'test-session', requestId: 'test-request' } + ); + + return NextResponse.json({ + success: true, + message: '智能連線測試成功', + data: { + testResult, + stats: smartPool.getConnectionStats() + } + }); + } catch (error) { + return NextResponse.json({ + success: false, + error: '智能連線測試失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['stats', 'cleanup', 'test'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ 連線監控 API 錯誤:', error); + return NextResponse.json({ + success: false, + error: 'API 請求失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { action, maxIdleTime, maxConnectionAge } = body; + + switch (action) { + case 'config': + // 更新清理配置 + smartPool.setCleanupParams(maxIdleTime, maxConnectionAge); + return NextResponse.json({ + success: true, + message: '清理配置更新成功', + data: { + maxIdleTime: maxIdleTime || '未變更', + maxConnectionAge: maxConnectionAge || '未變更' + } + }); + + case 'cleanup': + // 強制清理連線 + console.log('🧹 執行強制連線清理...'); + smartPool.forceCleanup(); + const stats = smartPool.getConnectionStats(); + return NextResponse.json({ + success: true, + message: '連線清理完成', + data: stats + }); + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['config', 'cleanup'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ 連線監控 POST API 錯誤:', error); + return NextResponse.json({ + success: false, + error: 'API 請求失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/debug-ip/route.ts b/app/api/debug-ip/route.ts new file mode 100644 index 0000000..e192a2d --- /dev/null +++ b/app/api/debug-ip/route.ts @@ -0,0 +1,59 @@ +// ===================================================== +// IP 調試 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { smartIPDetector } from '@/lib/smart-ip-detector'; + +export async function GET(request: NextRequest) { + try { + // 使用智能 IP 偵測器 + const ipDetection = smartIPDetector.detectClientIP(request); + + // 收集所有可能的 IP 信息 + const ipInfo = { + // 智能偵測結果 + smartDetection: ipDetection, + // 請求標頭 + 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'), + 'user-agent': request.headers.get('user-agent'), + 'host': request.headers.get('host'), + }, + // NextRequest 的 IP + nextRequestIP: request.ip, + // 環境變數 + env: { + NODE_ENV: process.env.NODE_ENV, + VERCEL: process.env.VERCEL, + VERCEL_REGION: process.env.VERCEL_REGION, + }, + // 所有標頭(用於調試) + allHeaders: Object.fromEntries(request.headers.entries()), + }; + + return NextResponse.json({ + success: true, + message: 'IP 調試信息獲取成功', + data: ipInfo, + timestamp: new Date().toISOString() + }); + + } catch (error) { + console.error('❌ IP 調試 API 錯誤:', error); + return NextResponse.json({ + success: false, + error: 'IP 調試失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/emergency-cleanup/route.ts b/app/api/emergency-cleanup/route.ts new file mode 100644 index 0000000..d0189a8 --- /dev/null +++ b/app/api/emergency-cleanup/route.ts @@ -0,0 +1,104 @@ +// ===================================================== +// 緊急連線清理 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { emergencyCleanup } from '@/lib/emergency-connection-cleanup'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { action } = body; + + switch (action) { + case 'cleanup': + // 執行緊急清理 + console.log('🚨 收到緊急清理請求'); + await emergencyCleanup.emergencyCleanup(); + + return NextResponse.json({ + success: true, + message: '緊急清理完成', + timestamp: new Date().toISOString() + }); + + case 'kill-all': + // 強制殺死所有連線 + console.log('💀 收到強制殺死連線請求'); + await emergencyCleanup.forceKillAllConnections(); + + return NextResponse.json({ + success: true, + message: '強制殺死連線完成', + timestamp: new Date().toISOString() + }); + + case 'details': + // 獲取連線詳情 + const connections = await emergencyCleanup.getConnectionDetails(); + + return NextResponse.json({ + success: true, + message: '連線詳情獲取成功', + data: { + connectionCount: connections.length, + connections: connections + }, + timestamp: new Date().toISOString() + }); + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['cleanup', 'kill-all', 'details'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ 緊急清理 API 錯誤:', error); + return NextResponse.json({ + success: false, + error: '緊急清理失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const action = searchParams.get('action') || 'details'; + + switch (action) { + case 'details': + // 獲取連線詳情 + const connections = await emergencyCleanup.getConnectionDetails(); + + return NextResponse.json({ + success: true, + message: '連線詳情獲取成功', + data: { + connectionCount: connections.length, + connections: connections + }, + timestamp: new Date().toISOString() + }); + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['details'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ 緊急清理 GET API 錯誤:', error); + return NextResponse.json({ + success: false, + error: '獲取連線詳情失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/ip-cleanup/route.ts b/app/api/ip-cleanup/route.ts new file mode 100644 index 0000000..e2b2d09 --- /dev/null +++ b/app/api/ip-cleanup/route.ts @@ -0,0 +1,177 @@ +// ===================================================== +// 基於 IP 的連線清理 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { smartIPPool } from '@/lib/smart-ip-connection-pool'; +import { smartIPDetector } from '@/lib/smart-ip-detector'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { action, targetIP } = body; + // 使用智能 IP 偵測器 + const ipDetection = smartIPDetector.detectClientIP(request); + const clientIP = ipDetection.detectedIP; + + console.log('🎯 智能 IP 偵測結果:', { + detectedIP: clientIP, + confidence: ipDetection.confidence, + source: ipDetection.source, + isPublicIP: ipDetection.isPublicIP, + allCandidates: ipDetection.allCandidates + }); + + // 設置客戶端 IP + smartIPPool.setClientIP(clientIP); + + switch (action) { + case 'cleanup-current': + // 清理當前 IP 的連線 + console.log(`🧹 收到清理當前 IP 連線請求: ${clientIP}`); + const cleanupResult = await smartIPPool.cleanupCurrentIPConnections(); + + return NextResponse.json({ + success: cleanupResult.success, + message: cleanupResult.message, + data: { + clientIP, + killedCount: cleanupResult.killedCount + }, + timestamp: new Date().toISOString() + }); + + case 'cleanup-specific': + // 清理指定 IP 的連線 + if (!targetIP) { + return NextResponse.json({ + success: false, + error: '缺少目標 IP 參數' + }, { status: 400 }); + } + + console.log(`🧹 收到清理指定 IP 連線請求: ${targetIP}`); + const specificCleanupResult = await smartIPPool.cleanupIPConnections(targetIP); + + return NextResponse.json({ + success: specificCleanupResult.success, + message: specificCleanupResult.message, + data: { + targetIP, + killedCount: specificCleanupResult.killedCount + }, + timestamp: new Date().toISOString() + }); + + case 'cleanup-local': + // 清理本地追蹤的連線 + console.log('🧹 收到清理本地連線請求'); + const localCleanupCount = smartIPPool.cleanupLocalConnections(); + + return NextResponse.json({ + success: true, + message: `已清理 ${localCleanupCount} 個本地追蹤的連線`, + data: { + clientIP, + cleanedCount: localCleanupCount + }, + timestamp: new Date().toISOString() + }); + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['cleanup-current', 'cleanup-specific', 'cleanup-local'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ IP 清理 API 錯誤:', error); + return NextResponse.json({ + success: false, + error: 'IP 清理失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const action = searchParams.get('action') || 'status'; + const targetIP = searchParams.get('ip'); + // 使用智能 IP 偵測器 + const ipDetection = smartIPDetector.detectClientIP(request); + const clientIP = ipDetection.detectedIP; + + // 設置客戶端 IP + smartIPPool.setClientIP(clientIP); + + switch (action) { + case 'status': + // 獲取當前 IP 的連線狀態 + const currentIPStatus = await smartIPPool.getCurrentIPConnections(); + + return NextResponse.json({ + success: true, + message: '連線狀態獲取成功', + data: { + detectedIP: clientIP, + ...currentIPStatus + }, + timestamp: new Date().toISOString() + }); + + case 'status-specific': + // 獲取指定 IP 的連線狀態 + if (!targetIP) { + return NextResponse.json({ + success: false, + error: '缺少目標 IP 參數' + }, { status: 400 }); + } + + const specificIPStatus = await smartIPPool.getIPConnections(targetIP); + + return NextResponse.json({ + success: true, + message: '指定 IP 連線狀態獲取成功', + data: { + targetIP, + ...specificIPStatus + }, + timestamp: new Date().toISOString() + }); + + case 'local-stats': + // 獲取本地連線統計 + const localStats = smartIPPool.getLocalConnectionStats(); + + return NextResponse.json({ + success: true, + message: '本地連線統計獲取成功', + data: { + detectedIP: clientIP, + ...localStats + }, + timestamp: new Date().toISOString() + }); + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['status', 'status-specific', 'local-stats'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ IP 清理 GET API 錯誤:', error); + return NextResponse.json({ + success: false, + error: '獲取連線狀態失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/set-client-ip/route.ts b/app/api/set-client-ip/route.ts new file mode 100644 index 0000000..2d8ec17 --- /dev/null +++ b/app/api/set-client-ip/route.ts @@ -0,0 +1,78 @@ +// ===================================================== +// 手動設置客戶端 IP API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { smartIPPool } from '@/lib/smart-ip-connection-pool'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { clientIP } = body; + + if (!clientIP) { + return NextResponse.json({ + success: false, + error: '缺少客戶端 IP 參數' + }, { status: 400 }); + } + + console.log('🔧 手動設置客戶端 IP:', clientIP); + + // 設置客戶端 IP + smartIPPool.setClientIP(clientIP); + + return NextResponse.json({ + success: true, + message: `客戶端 IP 已設置為: ${clientIP}`, + data: { + clientIP, + timestamp: new Date().toISOString() + } + }); + + } catch (error) { + console.error('❌ 設置客戶端 IP 失敗:', error); + return NextResponse.json({ + success: false, + error: '設置客戶端 IP 失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const clientIP = searchParams.get('ip'); + + if (!clientIP) { + return NextResponse.json({ + success: false, + error: '缺少 IP 參數' + }, { status: 400 }); + } + + console.log('🔧 通過 GET 設置客戶端 IP:', clientIP); + + // 設置客戶端 IP + smartIPPool.setClientIP(clientIP); + + return NextResponse.json({ + success: true, + message: `客戶端 IP 已設置為: ${clientIP}`, + data: { + clientIP, + timestamp: new Date().toISOString() + } + }); + + } catch (error) { + console.error('❌ 設置客戶端 IP 失敗:', error); + return NextResponse.json({ + success: false, + error: '設置客戶端 IP 失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/test-shutdown/route.ts b/app/api/test-shutdown/route.ts new file mode 100644 index 0000000..e7c61a2 --- /dev/null +++ b/app/api/test-shutdown/route.ts @@ -0,0 +1,100 @@ +// ===================================================== +// 測試資料庫關閉機制 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { dbShutdownManager } from '@/lib/database-shutdown-manager'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const action = searchParams.get('action') || 'status'; + + switch (action) { + case 'status': + // 獲取關閉狀態 + const status = dbShutdownManager.getShutdownStatus(); + return NextResponse.json({ + success: true, + message: '關閉狀態查詢成功', + data: status + }); + + case 'test': + // 測試關閉機制(不會真的關閉) + console.log('🧪 測試關閉機制...'); + const testStatus = dbShutdownManager.getShutdownStatus(); + return NextResponse.json({ + success: true, + message: '關閉機制測試成功', + data: { + ...testStatus, + testTime: new Date().toISOString() + } + }); + + case 'force': + // 強制關閉(僅用於測試) + console.log('🚨 執行強制關閉測試...'); + dbShutdownManager.forceShutdown(); + return NextResponse.json({ + success: true, + message: '強制關閉測試完成', + data: { + timestamp: new Date().toISOString() + } + }); + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['status', 'test', 'force'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ 測試關閉機制時發生錯誤:', error); + return NextResponse.json({ + success: false, + error: '測試失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { action } = body; + + switch (action) { + case 'graceful': + // 優雅關閉(僅用於測試) + console.log('🔄 執行優雅關閉測試...'); + await dbShutdownManager.gracefulShutdown(); + return NextResponse.json({ + success: true, + message: '優雅關閉測試完成', + data: { + timestamp: new Date().toISOString() + } + }); + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['graceful'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ 執行關閉測試時發生錯誤:', error); + return NextResponse.json({ + success: false, + error: '關閉測試失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/ultimate-kill/route.ts b/app/api/ultimate-kill/route.ts new file mode 100644 index 0000000..75a9956 --- /dev/null +++ b/app/api/ultimate-kill/route.ts @@ -0,0 +1,96 @@ +// ===================================================== +// 終極連線清理 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { ultimateKiller } from '@/lib/ultimate-connection-killer'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { action } = body; + + switch (action) { + case 'kill-all': + // 終極清理 - 殺死所有連線 + console.log('💀 收到終極清理請求'); + const killResult = await ultimateKiller.ultimateKill(); + + return NextResponse.json({ + success: killResult.success, + message: killResult.message || '終極清理完成', + data: killResult, + timestamp: new Date().toISOString() + }); + + case 'restart': + // 強制重啟資料庫連線 + console.log('🔄 收到強制重啟請求'); + const restartResult = await ultimateKiller.forceRestart(); + + return NextResponse.json({ + success: restartResult.success, + message: restartResult.message || '強制重啟完成', + data: restartResult, + timestamp: new Date().toISOString() + }); + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['kill-all', 'restart'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ 終極清理 API 錯誤:', error); + return NextResponse.json({ + success: false, + error: '終極清理失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const action = searchParams.get('action') || 'status'; + + switch (action) { + case 'status': + // 檢查連線狀態 + const status = await ultimateKiller.checkStatus(); + + if (status) { + return NextResponse.json({ + success: true, + message: '連線狀態獲取成功', + data: status, + timestamp: new Date().toISOString() + }); + } else { + return NextResponse.json({ + success: false, + error: '無法獲取連線狀態' + }, { status: 500 }); + } + + default: + return NextResponse.json({ + success: false, + error: '無效的操作參數', + availableActions: ['status'] + }, { status: 400 }); + } + + } catch (error) { + console.error('❌ 終極清理 GET API 錯誤:', error); + return NextResponse.json({ + success: false, + error: '獲取連線狀態失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/auto-ip-test/page.tsx b/app/auto-ip-test/page.tsx new file mode 100644 index 0000000..f931129 --- /dev/null +++ b/app/auto-ip-test/page.tsx @@ -0,0 +1,297 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Loader2, Database, CheckCircle, AlertTriangle, RefreshCw, Trash2 } from 'lucide-react'; + +interface IPDetectionResult { + detectedIP: string; + confidence: 'high' | 'medium' | 'low'; + source: string; + isPublicIP: boolean; + allCandidates: string[]; +} + +interface ConnectionStatus { + ip: string; + connectionCount: number; + connections: any[]; +} + +export default function AutoIPTestPage() { + const [ipDetection, setIpDetection] = useState(null); + const [connectionStatus, setConnectionStatus] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + // 自動偵測 IP + const detectIP = async () => { + try { + setLoading(true); + const response = await fetch('/api/debug-ip'); + const data = await response.json(); + + if (data.success && data.data.smartDetection) { + setIpDetection(data.data.smartDetection); + setMessage(`IP 偵測成功: ${data.data.smartDetection.detectedIP}`); + setError(''); + } else { + setError('IP 偵測失敗'); + } + } catch (err) { + setError('IP 偵測錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 獲取連線狀態 + const getConnectionStatus = async () => { + try { + setLoading(true); + const response = await fetch('/api/ip-cleanup?action=status'); + const data = await response.json(); + + if (data.success) { + setConnectionStatus(data.data); + setMessage(`連線狀態獲取成功: ${data.data.connectionCount} 個連線`); + setError(''); + } else { + setError('獲取連線狀態失敗'); + } + } catch (err) { + setError('獲取連線狀態錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 清理當前 IP 連線 + const cleanupConnections = async () => { + try { + setLoading(true); + const response = await fetch('/api/ip-cleanup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'cleanup-current' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage(`清理成功: ${data.message}`); + setError(''); + // 重新獲取連線狀態 + await getConnectionStatus(); + } else { + setError(data.error || '清理失敗'); + } + } catch (err) { + setError('清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 組件載入時自動偵測 + useEffect(() => { + detectIP(); + }, []); + + return ( +
+
+
+

自動 IP 偵測測試

+

+ 測試智能 IP 偵測和自動連線清理功能 +

+
+
+ + +
+
+ + {/* IP 偵測結果 */} + {ipDetection && ( + + + + + 智能 IP 偵測結果 + + + 系統自動偵測到的客戶端 IP 地址 + + + +
+ + {ipDetection.detectedIP} + + + 可信度: {ipDetection.confidence} + + + 來源: {ipDetection.source} + +
+ + {ipDetection.allCandidates.length > 1 && ( +
+

所有候選 IP:

+
+ {ipDetection.allCandidates.map((ip, index) => ( + + {ip} + + ))} +
+
+ )} +
+
+ )} + + {/* 連線狀態 */} + {connectionStatus && ( + + + + + 連線狀態 + + + 當前 IP 的資料庫連線狀態 + + + +
+ + IP: {connectionStatus.ip} + + 0 ? "destructive" : "default"} + className="text-lg px-3 py-1" + > + 連線數: {connectionStatus.connectionCount} + +
+ + {connectionStatus.connections.length > 0 && ( +
+

連線詳情:

+
+ {connectionStatus.connections.map((conn, index) => ( +
+ ID: {conn.ID} + HOST: {conn.HOST} + 時間: {conn.TIME}s + 狀態: {conn.STATE || 'Sleep'} +
+ ))} +
+
+ )} +
+
+ )} + + {/* 操作按鈕 */} + + + 操作 + + 測試自動 IP 偵測和連線清理功能 + + + +
+ + + +
+
+
+ + {/* 使用說明 */} + + + 使用說明 + + +

自動偵測: 頁面載入時會自動偵測你的 IP 地址

+

智能算法: 使用多種方法偵測最準確的 IP 地址

+

連線檢查: 檢查你的 IP 在資料庫中的連線狀態

+

自動清理: 點擊「清理我的連線」來清理你的 IP 連線

+

關閉頁面: 關閉此頁面時會自動觸發連線清理

+
+
+ + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} +
+ ); +} diff --git a/app/debug-ip/page.tsx b/app/debug-ip/page.tsx new file mode 100644 index 0000000..765dd75 --- /dev/null +++ b/app/debug-ip/page.tsx @@ -0,0 +1,325 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Loader2, Database, Eye, RefreshCw } from 'lucide-react'; + +interface IPInfo { + smartDetection?: { + detectedIP: string; + confidence: 'high' | 'medium' | 'low'; + source: string; + allCandidates: string[]; + isPublicIP: boolean; + }; + headers: Record; + nextRequestIP: string | undefined; + env: Record; + allHeaders: Record; +} + +export default function DebugIPPage() { + const [ipInfo, setIpInfo] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + const [recommendedIP, setRecommendedIP] = useState(''); + + // 獲取 IP 調試信息 + const fetchIPInfo = async () => { + try { + setLoading(true); + const response = await fetch('/api/debug-ip'); + const data = await response.json(); + + if (data.success) { + setIpInfo(data.data); + + // 提取智能偵測結果 + if (data.data.smartDetection) { + const smartDetection = data.data.smartDetection; + setRecommendedIP(smartDetection.detectedIP); + setMessage(`智能偵測結果: ${smartDetection.detectedIP} (可信度: ${smartDetection.confidence}, 來源: ${smartDetection.source})`); + } else { + setMessage('IP 調試信息更新成功'); + } + setError(''); + } else { + setError(data.error || '獲取 IP 調試信息失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 組件載入時獲取 IP 信息 + useEffect(() => { + fetchIPInfo(); + }, []); + + // 獲取推薦的 IP + const getRecommendedIP = () => { + if (!ipInfo) return 'unknown'; + + // 優先使用智能偵測結果 + if (ipInfo.smartDetection) { + return ipInfo.smartDetection.detectedIP; + } + + // 回退到舊的邏輯 + const { headers, nextRequestIP } = ipInfo; + + // 優先順序檢查 + if (headers['cf-connecting-ip']) return headers['cf-connecting-ip']; + if (headers['x-real-ip']) return headers['x-real-ip']; + if (headers['x-client-ip']) return headers['x-client-ip']; + if (headers['x-cluster-client-ip']) return headers['x-cluster-client-ip']; + if (headers['x-forwarded']) return headers['x-forwarded']; + if (headers['x-forwarded-for']) { + const ips = headers['x-forwarded-for']!.split(',').map(ip => ip.trim()); + const publicIPs = ips.filter(ip => + ip && + ip !== '127.0.0.1' && + ip !== '::1' && + !ip.startsWith('192.168.') && + !ip.startsWith('10.') && + !ip.startsWith('172.') + ); + if (publicIPs.length > 0) return publicIPs[0]; + return ips[0]; + } + if (nextRequestIP && nextRequestIP !== '::1' && nextRequestIP !== '127.0.0.1') { + return nextRequestIP; + } + + return 'unknown'; + }; + + return ( +
+
+
+

IP 調試工具

+

+ 調試和檢查客戶端 IP 地址獲取 +

+
+ +
+ + {/* 智能偵測結果 */} + {ipInfo?.smartDetection && ( + + + + + 智能 IP 偵測結果 + + + 使用智能算法偵測的客戶端 IP 地址 + + + +
+ + {ipInfo.smartDetection.detectedIP} + + + 可信度: {ipInfo.smartDetection.confidence} + + + 來源: {ipInfo.smartDetection.source} + +
+ + {ipInfo.smartDetection.allCandidates.length > 1 && ( +
+

所有候選 IP:

+
+ {ipInfo.smartDetection.allCandidates.map((ip, index) => ( + + {ip} + + ))} +
+
+ )} +
+
+ )} + + {/* 推薦的 IP (回退顯示) */} + {ipInfo && !ipInfo.smartDetection && ( + + + + + 推薦的 IP 地址 + + + 系統推薦使用的客戶端 IP 地址 + + + +
+ + {getRecommendedIP()} + + + 狀態: {getRecommendedIP() === 'unknown' ? "無法識別" : "已識別"} + +
+
+
+ )} + + {/* 請求標頭 */} + {ipInfo && ( + + + 請求標頭 + + 從 HTTP 請求標頭中獲取的 IP 相關信息 + + + +
+ {Object.entries(ipInfo.headers).map(([key, value]) => ( +
+ {key}: + + {value || 'null'} + +
+ ))} +
+
+
+ )} + + {/* NextRequest IP */} + {ipInfo && ( + + + NextRequest IP + + Next.js 框架提供的 IP 地址 + + + +
+ + {ipInfo.nextRequestIP || 'undefined'} + +
+
+
+ )} + + {/* 環境信息 */} + {ipInfo && ( + + + 環境信息 + + 部署環境相關信息 + + + +
+ {Object.entries(ipInfo.env).map(([key, value]) => ( +
+ {key}: + + {value || 'undefined'} + +
+ ))} +
+
+
+ )} + + {/* 所有標頭(用於調試) */} + {ipInfo && ( + + + 所有請求標頭 + + 完整的 HTTP 請求標頭信息 + + + +
+ {Object.entries(ipInfo.allHeaders).map(([key, value]) => ( +
+ {key}: + + {value} + +
+ ))} +
+
+
+ )} + + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} + + {/* 使用說明 */} + + + 使用說明 + + +

推薦的 IP: 系統根據優先順序推薦使用的 IP 地址

+

請求標頭: 檢查各種代理和負載均衡器設置的 IP 標頭

+

NextRequest IP: Next.js 框架直接提供的 IP 地址

+

環境信息: 部署環境相關的配置信息

+

• 如果推薦的 IP 是 "unknown",請檢查代理設置或網路配置

+
+
+
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx index 527f7c3..45ae2be 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -5,6 +5,8 @@ import { AuthProvider } from "@/contexts/auth-context" import { CompetitionProvider } from "@/contexts/competition-context" import { Toaster } from "@/components/ui/toaster" import { ChatBot } from "@/components/chat-bot" +import { ClientConnectionCleanup } from "@/components/client-connection-cleanup" +import "@/lib/app-initializer" // 自動初始化應用程式 const inter = Inter({ subsets: ["latin"] }) @@ -28,6 +30,7 @@ export default function RootLayout({ {children} + diff --git a/app/set-ip/page.tsx b/app/set-ip/page.tsx new file mode 100644 index 0000000..443f1b4 --- /dev/null +++ b/app/set-ip/page.tsx @@ -0,0 +1,174 @@ +'use client'; + +import React, { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Loader2, Database, CheckCircle, AlertTriangle } from 'lucide-react'; + +export default function SetIPPage() { + const [clientIP, setClientIP] = useState('61-227-253-171'); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + // 設置客戶端 IP + const setClientIP = async () => { + if (!clientIP.trim()) { + setError('請輸入客戶端 IP 地址'); + return; + } + + try { + setLoading(true); + const response = await fetch('/api/set-client-ip', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ clientIP: clientIP.trim() }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage(data.message || '客戶端 IP 設置成功'); + setError(''); + } else { + setError(data.error || '設置客戶端 IP 失敗'); + } + } catch (err) { + setError('設置錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 快速設置你的 IP + const setYourIP = () => { + setClientIP('61-227-253-171'); + }; + + // 測試清理功能 + const testCleanup = async () => { + try { + setLoading(true); + const response = await fetch('/api/ip-cleanup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'cleanup-current' }), + }); + + const data = await response.json(); + + if (data.success) { + setMessage(`清理完成: ${data.message}`); + setError(''); + } else { + setError(data.error || '清理失敗'); + } + } catch (err) { + setError('清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

設置客戶端 IP

+

+ 手動設置你的真實 IP 地址,用於連線清理 +

+
+ + {/* IP 設置 */} + + + + + 設置客戶端 IP + + + 輸入你的真實 IP 地址,系統將使用此 IP 來清理連線 + + + +
+ +
+ setClientIP(e.target.value)} + placeholder="例如: 61-227-253-171" + className="flex-1" + /> + +
+
+ +
+ + + +
+
+
+ + {/* 使用說明 */} + + + 使用說明 + + +

你的 IP: 61-227-253-171(從資料庫連線列表中獲取)

+

設置 IP: 點擊「設置 IP」按鈕來設置你的客戶端 IP

+

測試清理: 點擊「測試清理」按鈕來清理你的 IP 連線

+

自動清理: 設置後,關閉頁面時會自動清理你的 IP 連線

+
+
+ + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} +
+ ); +} diff --git a/app/test-ip-cleanup/page.tsx b/app/test-ip-cleanup/page.tsx new file mode 100644 index 0000000..4d5dac9 --- /dev/null +++ b/app/test-ip-cleanup/page.tsx @@ -0,0 +1,226 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Loader2, Database, Trash2, Eye, Zap } from 'lucide-react'; +import { clientCleanup } from '@/lib/client-connection-cleanup'; + +export default function TestIPCleanupPage() { + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + // 獲取連線狀態 + const fetchStatus = async () => { + try { + setLoading(true); + const status = await clientCleanup.getConnectionStatus(); + setStatus(status); + setMessage('連線狀態更新成功'); + setError(''); + } catch (err) { + setError('獲取狀態失敗: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 手動清理 + const manualCleanup = async () => { + try { + setLoading(true); + const success = await clientCleanup.manualCleanup(); + + if (success) { + setMessage('手動清理完成'); + setError(''); + await fetchStatus(); // 重新獲取狀態 + } else { + setError('手動清理失敗'); + } + } catch (err) { + setError('手動清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 模擬關閉頁面 + const simulatePageClose = () => { + if (confirm('確定要模擬關閉頁面嗎?這會觸發自動清理機制。')) { + // 觸發 beforeunload 事件 + window.dispatchEvent(new Event('beforeunload')); + + // 等待一下再觸發 unload 事件 + setTimeout(() => { + window.dispatchEvent(new Event('unload')); + }, 100); + } + }; + + // 組件載入時獲取狀態 + useEffect(() => { + fetchStatus(); + }, []); + + return ( +
+
+
+

IP 連線清理測試

+

+ 測試基於 IP 的智能連線清理功能 +

+
+ +
+ + {/* 客戶端信息 */} + + + + + 客戶端信息 + + + +
+

客戶端 ID: {clientCleanup.getClientId()}

+

用戶代理: {navigator.userAgent}

+

當前時間: {new Date().toLocaleString()}

+
+
+
+ + {/* 連線狀態 */} + {status && ( + + + + + 連線狀態 + + + IP: {status.ip} | 連線數: {status.connectionCount} + + + +
+ + 連線數: {status.connectionCount} + + 5 ? "destructive" : "default"} + className="text-lg px-3 py-1" + > + 狀態: {status.connectionCount > 5 ? "異常" : "正常"} + +
+ + {status.connections && status.connections.length > 0 && ( +
+

連線詳情:

+ {status.connections.slice(0, 5).map((conn: any, index: number) => ( +
+

ID: {conn.ID} | HOST: {conn.HOST} | 時間: {conn.TIME}s

+
+ ))} + {status.connections.length > 5 && ( +

+ ... 還有 {status.connections.length - 5} 個連線 +

+ )} +
+ )} +
+
+ )} + + {/* 測試操作 */} + + + + + 測試操作 + + + 測試各種連線清理功能 + + + +
+ + + + + +
+
+
+ + {/* 自動清理說明 */} + + + 自動清理機制 + + +

頁面關閉前: 自動清理當前 IP 的所有連線

+

頁面隱藏時: 當切換到其他標籤頁時清理連線

+

定期清理: 每5分鐘檢查並清理多餘連線

+

手動清理: 可以隨時手動觸發清理

+

智能識別: 只清理當前 IP 的連線,不影響其他用戶

+
+
+ + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} +
+ ); +} diff --git a/components/client-connection-cleanup.tsx b/components/client-connection-cleanup.tsx new file mode 100644 index 0000000..8448399 --- /dev/null +++ b/components/client-connection-cleanup.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { useEffect } from 'react'; +import { clientCleanup } from '@/lib/client-connection-cleanup'; + +// 客戶端連線清理組件 +export function ClientConnectionCleanup() { + useEffect(() => { + // 確保在瀏覽器環境中執行 + if (typeof window === 'undefined') return; + + // 初始化客戶端清理 + console.log('🖥️ 客戶端連線清理組件已載入'); + + // 可以在這裡添加額外的初始化邏輯 + const clientId = clientCleanup.getClientId(); + console.log('🆔 客戶端 ID:', clientId); + + // 清理函數(組件卸載時) + return () => { + console.log('🔄 客戶端連線清理組件卸載'); + // 可以在這裡添加清理邏輯 + }; + }, []); + + // 這個組件不渲染任何內容 + return null; +} diff --git a/lib/app-initializer.ts b/lib/app-initializer.ts new file mode 100644 index 0000000..dc9eb98 --- /dev/null +++ b/lib/app-initializer.ts @@ -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(); +} diff --git a/lib/client-connection-cleanup.ts b/lib/client-connection-cleanup.ts new file mode 100644 index 0000000..e0562ae --- /dev/null +++ b/lib/client-connection-cleanup.ts @@ -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 { + // 確保在瀏覽器環境中執行 + 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; + } +} diff --git a/lib/connection-lifecycle-manager.ts b/lib/connection-lifecycle-manager.ts new file mode 100644 index 0000000..1f48ee4 --- /dev/null +++ b/lib/connection-lifecycle-manager.ts @@ -0,0 +1,178 @@ +// ===================================================== +// 連線生命週期管理器 +// ===================================================== + +import { db } from './database'; +import { dbFailover } from './database-failover'; + +export class ConnectionLifecycleManager { + private static instance: ConnectionLifecycleManager; + private activeConnections = new Map(); + 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(); diff --git a/lib/database-middleware.ts b/lib/database-middleware.ts index 9a53733..ed270b1 100644 --- a/lib/database-middleware.ts +++ b/lib/database-middleware.ts @@ -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(); } diff --git a/lib/database-monitor.ts b/lib/database-monitor.ts index 6d6c6de..917fdbe 100644 --- a/lib/database-monitor.ts +++ b/lib/database-monitor.ts @@ -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); diff --git a/lib/database-service-smart.ts b/lib/database-service-smart.ts new file mode 100644 index 0000000..8fbb1e8 --- /dev/null +++ b/lib/database-service-smart.ts @@ -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 }); + } +} +*/ diff --git a/lib/database-shutdown-manager.ts b/lib/database-shutdown-manager.ts new file mode 100644 index 0000000..acef194 --- /dev/null +++ b/lib/database-shutdown-manager.ts @@ -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)[] = []; + + 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) { + 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(); diff --git a/lib/emergency-connection-cleanup.ts b/lib/emergency-connection-cleanup.ts new file mode 100644 index 0000000..476fd88 --- /dev/null +++ b/lib/emergency-connection-cleanup.ts @@ -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(); diff --git a/lib/ip-based-connection-manager.ts b/lib/ip-based-connection-manager.ts new file mode 100644 index 0000000..dcfcd92 --- /dev/null +++ b/lib/ip-based-connection-manager.ts @@ -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(); + + 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(); diff --git a/lib/smart-connection-pool.ts b/lib/smart-connection-pool.ts new file mode 100644 index 0000000..e0a0c39 --- /dev/null +++ b/lib/smart-connection-pool.ts @@ -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( + sql: string, + params?: any[], + metadata?: { + userId?: string; + sessionId?: string; + requestId?: string; + } + ): Promise { + 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( + sql: string, + params?: any[], + metadata?: { + userId?: string; + sessionId?: string; + requestId?: string; + } + ): Promise { + const results = await this.executeQuery(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 { + 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 { + 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 { + 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(); diff --git a/lib/smart-ip-connection-pool.ts b/lib/smart-ip-connection-pool.ts new file mode 100644 index 0000000..c90dfcc --- /dev/null +++ b/lib/smart-ip-connection-pool.ts @@ -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( + sql: string, + params?: any[], + metadata?: { + userAgent?: string; + requestId?: string; + clientIP?: string; + } + ): Promise { + 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( + sql: string, + params?: any[], + metadata?: { + userAgent?: string; + requestId?: string; + clientIP?: string; + } + ): Promise { + const results = await this.executeQuery(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(); diff --git a/lib/smart-ip-detector.ts b/lib/smart-ip-detector.ts new file mode 100644 index 0000000..c85fbba --- /dev/null +++ b/lib/smart-ip-detector.ts @@ -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(); diff --git a/lib/ultimate-connection-killer.ts b/lib/ultimate-connection-killer.ts new file mode 100644 index 0000000..4726bd6 --- /dev/null +++ b/lib/ultimate-connection-killer.ts @@ -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(); diff --git a/scripts/insert-team-members.sql b/scripts/insert-team-members.sql deleted file mode 100644 index 5fdf5ad..0000000 --- a/scripts/insert-team-members.sql +++ /dev/null @@ -1,50 +0,0 @@ --- ===================================================== --- 插入團隊成員測試數據 --- ===================================================== - --- 首先查看現有的團隊 -SELECT '=== 現有團隊 ===' as info; -SELECT id, name, leader_id FROM teams WHERE is_active = 1; - --- 查看現有用戶 -SELECT '=== 現有用戶 ===' as info; -SELECT id, name, department FROM users WHERE status = 'active' LIMIT 10; - --- 為每個團隊插入成員數據 --- 團隊 1: aaa (ID: t1757702332911zcl6iafq1) -INSERT IGNORE INTO team_members (id, team_id, user_id, role, joined_at) VALUES -('tm_1_1', 't1757702332911zcl6iafq1', 'db19b491-8f63-44b5-a28a-1f8eeb4fdd3c', '隊長', NOW()), -('tm_1_2', 't1757702332911zcl6iafq1', 'db19b491-8f63-44b5-a28a-1f8eeb4fdd3c', '成員', NOW()); - --- 如果有其他團隊,請按照相同格式添加 --- 團隊 2: (如果有的話) --- INSERT IGNORE INTO team_members (id, team_id, user_id, role, joined_at) VALUES --- ('tm_2_1', 'team_id_2', 'user_id_2', '隊長', NOW()), --- ('tm_2_2', 'team_id_2', 'user_id_3', '成員', NOW()); - --- 驗證插入結果 -SELECT '=== 團隊成員插入結果 ===' as info; -SELECT - tm.id, - tm.team_id, - t.name as team_name, - tm.user_id, - u.name as user_name, - tm.role, - tm.joined_at -FROM team_members tm -LEFT JOIN teams t ON tm.team_id = t.id -LEFT JOIN users u ON tm.user_id = u.id -ORDER BY tm.team_id, tm.role; - --- 檢查團隊成員統計 -SELECT '=== 團隊成員統計 ===' as info; -SELECT - t.id, - t.name as team_name, - COUNT(tm.id) as member_count -FROM teams t -LEFT JOIN team_members tm ON t.id = tm.team_id -WHERE t.is_active = 1 -GROUP BY t.id, t.name -ORDER BY t.name; diff --git a/scripts/insert-test-team-members.js b/scripts/insert-test-team-members.js deleted file mode 100644 index 97097a7..0000000 --- a/scripts/insert-test-team-members.js +++ /dev/null @@ -1,82 +0,0 @@ -const mysql = require('mysql2/promise'); - -async function insertTeamMembers() { - const connection = await mysql.createConnection({ - host: 'mysql.theaken.com', - port: 33306, - user: 'AI_Platform', - password: 'Aa123456', - database: 'db_AI_Platform', - charset: 'utf8mb4', - timezone: '+08:00' - }); - - try { - console.log('🔍 開始插入團隊成員測試數據...'); - - // 查看現有團隊 - const [teams] = await connection.execute('SELECT id, name, leader_id FROM teams WHERE is_active = 1 LIMIT 5'); - console.log('現有團隊:', teams); - - // 查看現有用戶 - const [users] = await connection.execute('SELECT id, name FROM users WHERE status = "active" LIMIT 5'); - console.log('現有用戶:', users); - - if (teams.length > 0 && users.length > 0) { - // 為每個團隊插入成員 - for (let i = 0; i < Math.min(teams.length, 3); i++) { - const team = teams[i]; - const teamId = team.id; - - // 插入隊長(使用團隊的 leader_id) - await connection.execute( - 'INSERT IGNORE INTO team_members (id, team_id, user_id, role, joined_at) VALUES (?, ?, ?, ?, NOW())', - [`tm_${Date.now()}_${i}_1`, teamId, team.leader_id, '隊長'] - ); - - // 插入成員(使用其他用戶) - for (let j = 1; j < Math.min(users.length, 3); j++) { - if (users[j].id !== team.leader_id) { - await connection.execute( - 'INSERT IGNORE INTO team_members (id, team_id, user_id, role, joined_at) VALUES (?, ?, ?, ?, NOW())', - [`tm_${Date.now()}_${i}_${j+1}`, teamId, users[j].id, '成員'] - ); - } - } - - console.log(`✅ 團隊 ${team.name} 成員插入成功`); - } - } - - // 驗證結果 - const [members] = await connection.execute(` - SELECT tm.*, t.name as team_name, u.name as user_name - FROM team_members tm - LEFT JOIN teams t ON tm.team_id = t.id - LEFT JOIN users u ON tm.user_id = u.id - ORDER BY tm.team_id - `); - console.log('📊 團隊成員統計:', members); - - // 檢查團隊成員數量 - const [counts] = await connection.execute(` - SELECT - t.id, - t.name as team_name, - COUNT(tm.id) as member_count - FROM teams t - LEFT JOIN team_members tm ON t.id = tm.team_id - WHERE t.is_active = 1 - GROUP BY t.id, t.name - ORDER BY t.name - `); - console.log('📈 團隊成員數量:', counts); - - } catch (error) { - console.error('❌ 錯誤:', error); - } finally { - await connection.end(); - } -} - -insertTeamMembers(); diff --git a/scripts/test-team-data.js b/scripts/test-team-data.js deleted file mode 100644 index cb2bc85..0000000 --- a/scripts/test-team-data.js +++ /dev/null @@ -1,70 +0,0 @@ -const mysql = require('mysql2/promise'); - -async function testTeamData() { - const connection = await mysql.createConnection({ - host: 'mysql.theaken.com', - port: 33306, - user: 'AI_Platform', - password: 'Aa123456', - database: 'db_AI_Platform', - charset: 'utf8mb4', - timezone: '+08:00' - }); - - try { - console.log('🔍 測試團隊數據查詢...'); - - // 使用與 getAllTeams 相同的查詢 - const [results] = await connection.execute(` - SELECT t.*, - u.name as leader_name, - u.phone as leader_phone, - t.leader_id as leader, - COUNT(DISTINCT tm.id) as member_count, - COUNT(DISTINCT a.id) as app_count, - t.created_at as submissionDate - FROM teams t - LEFT JOIN users u ON t.leader_id = u.id - LEFT JOIN team_members tm ON t.id = tm.team_id - LEFT JOIN apps a ON t.id = a.team_id AND a.is_active = 1 - WHERE t.is_active = 1 - GROUP BY t.id, t.name, t.leader_id, t.department, t.contact_email, t.total_likes, t.is_active, t.created_at, t.updated_at, u.name, u.phone - ORDER BY t.created_at DESC - LIMIT 5 - `); - - console.log('📊 查詢結果:'); - results.forEach((team, index) => { - console.log(`\n團隊 ${index + 1}:`); - console.log(` 名稱: ${team.name}`); - console.log(` 隊長ID: ${team.leader}`); - console.log(` 隊長姓名: ${team.leader_name || 'NULL'}`); - console.log(` 成員數量: ${team.member_count || 0}`); - console.log(` 提交日期: ${team.submissionDate || 'NULL'}`); - console.log(` 部門: ${team.department}`); - }); - - // 檢查特定團隊的成員 - if (results.length > 0) { - const teamId = results[0].id; - const [members] = await connection.execute(` - SELECT tm.*, u.name as user_name - FROM team_members tm - LEFT JOIN users u ON tm.user_id = u.id - WHERE tm.team_id = ? - `, [teamId]); - - console.log(`\n🔍 團隊 ${results[0].name} 的成員:`); - members.forEach(member => { - console.log(` - ${member.user_name} (${member.role})`); - }); - } - - } catch (error) { - console.error('❌ 錯誤:', error); - } finally { - await connection.end(); - } -} - -testTeamData(); diff --git a/scripts/ultimate-kill.js b/scripts/ultimate-kill.js new file mode 100644 index 0000000..3b1b1da --- /dev/null +++ b/scripts/ultimate-kill.js @@ -0,0 +1,93 @@ +// ===================================================== +// 終極連線清理腳本 +// ===================================================== + +const mysql = require('mysql2/promise'); + +async function ultimateKill() { + console.log('💀 執行終極連線清理腳本...'); + + let connection = null; + + try { + // 建立連線 + connection = 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', + }); + + console.log('✅ 已連接到資料庫'); + + // 獲取所有連線 + 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']); + + console.log(`🔍 找到 ${connections.length} 個連線需要清理`); + + // 顯示連線詳情 + connections.forEach((conn, index) => { + console.log(`${index + 1}. ID: ${conn.ID}, 用戶: ${conn.USER}, 時間: ${conn.TIME}s, 狀態: ${conn.STATE}`); + }); + + // 殺死所有連線(除了當前連線) + let killedCount = 0; + for (const conn of connections) { + if (conn.ID !== connection.threadId) { + try { + await connection.execute(`KILL ${conn.ID}`); + console.log(`💀 已殺死連線 ${conn.ID}`); + killedCount++; + } catch (error) { + console.log(`⚠️ 無法殺死連線 ${conn.ID}: ${error.message}`); + } + } + } + + console.log(`✅ 已殺死 ${killedCount} 個連線`); + + // 等待連線關閉 + console.log('⏳ 等待連線完全關閉...'); + await new Promise(resolve => setTimeout(resolve, 3000)); + + // 檢查最終狀態 + const [finalConnections] = await connection.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} 個連線未清理`); + } + + } catch (error) { + console.error('❌ 終極清理失敗:', error); + } finally { + if (connection) { + await connection.end(); + console.log('🔌 連線已關閉'); + } + } +} + +// 執行清理 +ultimateKill().then(() => { + console.log('✅ 腳本執行完成'); + process.exit(0); +}).catch((error) => { + console.error('❌ 腳本執行失敗:', error); + process.exit(1); +});