修復 too many connection 問題

This commit is contained in:
2025-09-21 02:46:16 +08:00
parent a36ab3c98d
commit 808d5bb52c
36 changed files with 5582 additions and 249 deletions

View File

@@ -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<ConnectionStats | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
const [maxIdleTime, setMaxIdleTime] = useState<number>(300000); // 5分鐘
const [maxConnectionAge, setMaxConnectionAge] = useState<number>(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 (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold"></h1>
<p className="text-muted-foreground">
</p>
</div>
<Button
onClick={fetchStats}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Activity className="h-4 w-4" />}
</Button>
</div>
{/* 統計概覽 */}
{stats && (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Database className="h-5 w-5 text-blue-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">{stats.totalConnections}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Clock className="h-5 w-5 text-yellow-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">{stats.idleConnections}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-red-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">{stats.oldConnections}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Activity className="h-5 w-5 text-green-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">
{stats.idleConnections + stats.oldConnections === 0 ? '良好' : '需清理'}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
)}
{/* 操作按鈕 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="h-5 w-5" />
</CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Button
onClick={testConnection}
disabled={loading}
variant="outline"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />}
</Button>
<Button
onClick={forceCleanup}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
</Button>
<Button
onClick={updateConfig}
disabled={loading}
variant="secondary"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Settings className="h-4 w-4" />}
</Button>
</div>
{/* 配置設定 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4 border-t">
<div className="space-y-2">
<Label htmlFor="maxIdleTime"> ()</Label>
<Input
id="maxIdleTime"
type="number"
value={maxIdleTime}
onChange={(e) => setMaxIdleTime(Number(e.target.value))}
placeholder="300000 (5分鐘)"
/>
</div>
<div className="space-y-2">
<Label htmlFor="maxConnectionAge"> ()</Label>
<Input
id="maxConnectionAge"
type="number"
value={maxConnectionAge}
onChange={(e) => setMaxConnectionAge(Number(e.target.value))}
placeholder="1800000 (30分鐘)"
/>
</div>
</div>
</CardContent>
</Card>
{/* 連線詳情 */}
{stats && stats.connections.length > 0 && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
{stats.connections.map((conn, index) => (
<div key={index} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-4">
<Badge variant="outline">#{index + 1}</Badge>
<div>
<p className="text-sm font-medium">
: {new Date(conn.createdAt).toLocaleString()}
</p>
<p className="text-sm text-muted-foreground">
使: {new Date(conn.lastUsed).toLocaleString()}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Badge variant={conn.idleTime > stats.maxIdleTime ? "destructive" : "default"}>
: {formatTime(conn.idleTime)}
</Badge>
<Badge variant={conn.age > stats.maxConnectionAge ? "destructive" : "default"}>
: {formatTime(conn.age)}
</Badge>
{conn.userId && (
<Badge variant="secondary">: {conn.userId}</Badge>
)}
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 訊息顯示 */}
{message && (
<Alert>
<Activity className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<Database className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 說明資訊 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong>:</strong> 30</p>
<p> <strong>:</strong> 使</p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -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<ShutdownStatus | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
// 獲取關閉狀態
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 (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold"></h1>
<p className="text-muted-foreground">
</p>
</div>
<Button
onClick={fetchStatus}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />}
</Button>
</div>
{/* 狀態顯示 */}
{status && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
</CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">:</span>
<Badge variant={status.isShuttingDown ? "destructive" : "default"}>
{status.isShuttingDown ? "關閉中" : "正常"}
</Badge>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">:</span>
<Badge variant="outline">{status.handlerCount}</Badge>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">:</span>
<Badge variant={status.isShuttingDown ? "destructive" : "default"}>
{status.isShuttingDown ? "異常" : "正常"}
</Badge>
</div>
</div>
<div>
<span className="text-sm font-medium">:</span>
<div className="mt-2 flex flex-wrap gap-2">
{status.registeredHandlers.map((handler, index) => (
<Badge key={index} variant="secondary">
{handler}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
)}
{/* 操作按鈕 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Power className="h-5 w-5" />
</CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Button
onClick={testShutdown}
disabled={loading}
variant="outline"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <CheckCircle className="h-4 w-4" />}
</Button>
<Button
onClick={forceShutdown}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <AlertTriangle className="h-4 w-4" />}
</Button>
<Button
onClick={gracefulShutdown}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Power className="h-4 w-4" />}
</Button>
</div>
</CardContent>
</Card>
{/* 訊息顯示 */}
{message && (
<Alert>
<CheckCircle className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 說明資訊 */}
<Card>
<CardHeader>
<CardTitle>使</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
<p> SIGINT SIGTERM </p>
<p> </p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -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<ConnectionData | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
// 獲取連線詳情
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 (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-red-600"></h1>
<p className="text-muted-foreground">
</p>
</div>
<Button
onClick={fetchConnectionDetails}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
{/* 警告提示 */}
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<strong></strong> 使
</AlertDescription>
</Alert>
{/* 連線統計 */}
{connectionData && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
</CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Badge variant="outline" className="text-lg px-3 py-1">
: {connectionData.connectionCount}
</Badge>
<Badge
variant={connectionData.connectionCount > 10 ? "destructive" : "default"}
className="text-lg px-3 py-1"
>
: {connectionData.connectionCount > 10 ? "異常" : "正常"}
</Badge>
</div>
</CardContent>
</Card>
)}
{/* 緊急操作 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-red-600">
<Zap className="h-5 w-5" />
</CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Button
onClick={executeEmergencyCleanup}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
</Button>
<Button
onClick={forceKillAllConnections}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <AlertTriangle className="h-4 w-4" />}
</Button>
</div>
</CardContent>
</Card>
{/* 連線詳情列表 */}
{connectionData && connectionData.connections.length > 0 && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-96 overflow-y-auto">
{connectionData.connections.map((conn, index) => (
<div key={conn.ID} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-4">
<Badge variant="outline">#{conn.ID}</Badge>
<div>
<p className="text-sm font-medium">
: {conn.USER} | : {conn.HOST}
</p>
<p className="text-sm text-muted-foreground">
: {conn.DB} | : {conn.COMMAND}
</p>
<p className="text-sm text-muted-foreground">
: {conn.TIME}s | : {conn.STATE}
</p>
{conn.INFO && (
<p className="text-xs text-muted-foreground mt-1">
: {conn.INFO.length > 100 ? conn.INFO.substring(0, 100) + '...' : conn.INFO}
</p>
)}
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 訊息顯示 */}
{message && (
<Alert>
<Database className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 使用說明 */}
<Card>
<CardHeader>
<CardTitle>使</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
<p> 使</p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -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<ConnectionStatus | null>(null);
const [localStats, setLocalStats] = useState<LocalStats | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
const [targetIP, setTargetIP] = useState<string>('');
// 獲取當前 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 (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">IP </h1>
<p className="text-muted-foreground">
IP
</p>
</div>
<div className="flex gap-2">
<Button
onClick={fetchCurrentIPStatus}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Eye className="h-4 w-4" />}
IP
</Button>
<Button
onClick={fetchLocalStats}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />}
</Button>
</div>
</div>
{/* 當前 IP 連線狀態 */}
{connectionStatus && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Globe className="h-5 w-5" />
IP
</CardTitle>
<CardDescription>
IP: {connectionStatus.ip} | : {connectionStatus.connectionCount}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Badge variant="outline" className="text-lg px-3 py-1">
: {connectionStatus.connectionCount}
</Badge>
<Badge
variant={connectionStatus.connectionCount > 5 ? "destructive" : "default"}
className="text-lg px-3 py-1"
>
: {connectionStatus.connectionCount > 5 ? "異常" : "正常"}
</Badge>
</div>
</CardContent>
</Card>
)}
{/* 本地連線統計 */}
{localStats && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
</CardTitle>
<CardDescription>
IP: {localStats.clientIP} | : {localStats.trackedConnections}
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
{localStats.connections.map((conn, index) => (
<div key={index} className="flex items-center justify-between p-2 border rounded">
<div>
<p className="text-sm font-medium"> ID: {conn.connectionId}</p>
<p className="text-xs text-muted-foreground">
: {new Date(conn.createdAt).toLocaleString()} |
使: {new Date(conn.lastUsed).toLocaleString()}
</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 操作按鈕 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Trash2 className="h-5 w-5" />
IP
</CardTitle>
<CardDescription>
IP
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Button
onClick={cleanupCurrentIP}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
IP
</Button>
<Button
onClick={cleanupLocalConnections}
disabled={loading}
variant="outline"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />}
</Button>
</div>
{/* 指定 IP 清理 */}
<div className="space-y-2">
<Label htmlFor="targetIP"> IP </Label>
<div className="flex gap-2">
<Input
id="targetIP"
value={targetIP}
onChange={(e) => setTargetIP(e.target.value)}
placeholder="例如: 192.168.1.100"
className="flex-1"
/>
<Button
onClick={fetchSpecificIPStatus}
disabled={loading || !targetIP.trim()}
variant="outline"
>
<Eye className="h-4 w-4" />
</Button>
<Button
onClick={cleanupSpecificIP}
disabled={loading || !targetIP.trim()}
variant="destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
{/* 連線詳情列表 */}
{connectionStatus && connectionStatus.connections.length > 0 && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
IP {connectionStatus.ip}
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-96 overflow-y-auto">
{connectionStatus.connections.map((conn, index) => (
<div key={conn.ID} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-4">
<Badge variant="outline">#{conn.ID}</Badge>
<div>
<p className="text-sm font-medium">
: {conn.USER} | : {conn.HOST}
</p>
<p className="text-sm text-muted-foreground">
: {conn.DB} | : {conn.COMMAND}
</p>
<p className="text-sm text-muted-foreground">
: {conn.TIME}s | : {conn.STATE}
</p>
{conn.INFO && (
<p className="text-xs text-muted-foreground mt-1">
: {conn.INFO.length > 100 ? conn.INFO.substring(0, 100) + '...' : conn.INFO}
</p>
)}
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 訊息顯示 */}
{message && (
<Alert>
<Database className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 使用說明 */}
<Card>
<CardHeader>
<CardTitle>IP </CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong> IP :</strong> </p>
<p> <strong> IP :</strong> IP IP </p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> IP </p>
<p> <strong>:</strong> IP </p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -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<ConnectionStatus | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
// 獲取連線狀態
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 (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-red-600"></h1>
<p className="text-muted-foreground">
-
</p>
</div>
<Button
onClick={fetchStatus}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
{/* 嚴重警告 */}
<Alert variant="destructive">
<Skull className="h-4 w-4" />
<AlertDescription>
<strong></strong>
</AlertDescription>
</Alert>
{/* 連線狀態概覽 */}
{status && (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Database className="h-5 w-5 text-blue-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">{status.currentConnections}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-yellow-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">{status.maxConnections}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Zap className="h-5 w-5 text-red-500" />
<div>
<p className="text-sm font-medium text-muted-foreground">使</p>
<p className="text-2xl font-bold">{status.usagePercentage.toFixed(1)}%</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-2">
<Trash2 className="h-5 w-5 text-green-500" />
<div>
<p className="text-sm font-medium text-muted-foreground"></p>
<p className="text-2xl font-bold">
{status.connectionCount <= 1 ? '正常' : '異常'}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
)}
{/* 終極操作 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-red-600">
<Skull className="h-5 w-5" />
</CardTitle>
<CardDescription>
-
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Button
onClick={ultimateKill}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Skull className="h-4 w-4" />}
-
</Button>
<Button
onClick={forceRestart}
disabled={loading}
variant="destructive"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Zap className="h-4 w-4" />}
</Button>
</div>
</CardContent>
</Card>
{/* 連線詳情列表 */}
{status && status.connectionDetails.length > 0 && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-96 overflow-y-auto">
{status.connectionDetails.map((conn, index) => (
<div key={conn.ID} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center gap-4">
<Badge variant="outline">#{conn.ID}</Badge>
<div>
<p className="text-sm font-medium">
: {conn.USER} | : {conn.HOST}
</p>
<p className="text-sm text-muted-foreground">
: {conn.DB} | : {conn.COMMAND}
</p>
<p className="text-sm text-muted-foreground">
: {conn.TIME}s | : {conn.STATE}
</p>
{conn.INFO && (
<p className="text-xs text-muted-foreground mt-1">
: {conn.INFO.length > 100 ? conn.INFO.substring(0, 100) + '...' : conn.INFO}
</p>
)}
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 訊息顯示 */}
{message && (
<Alert>
<Database className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 使用說明 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong>:</strong> </p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> 10</p>
<p> <strong>:</strong> </p>
<p> 使</p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

59
app/api/debug-ip/route.ts Normal file
View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

177
app/api/ip-cleanup/route.ts Normal file
View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

297
app/auto-ip-test/page.tsx Normal file
View File

@@ -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<IPDetectionResult | null>(null);
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
// 自動偵測 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 (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold"> IP </h1>
<p className="text-muted-foreground">
IP
</p>
</div>
<div className="flex gap-2">
<Button
onClick={detectIP}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
IP
</Button>
<Button
onClick={getConnectionStatus}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />}
</Button>
</div>
</div>
{/* IP 偵測結果 */}
{ipDetection && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CheckCircle className="h-5 w-5" />
IP
</CardTitle>
<CardDescription>
IP
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Badge
variant={ipDetection.isPublicIP ? "default" : "secondary"}
className="text-lg px-3 py-1"
>
{ipDetection.detectedIP}
</Badge>
<Badge
variant={
ipDetection.confidence === 'high' ? "default" :
ipDetection.confidence === 'medium' ? "secondary" : "destructive"
}
className="text-lg px-3 py-1"
>
: {ipDetection.confidence}
</Badge>
<Badge variant="outline" className="text-lg px-3 py-1">
: {ipDetection.source}
</Badge>
</div>
{ipDetection.allCandidates.length > 1 && (
<div>
<p className="text-sm font-medium mb-2"> IP:</p>
<div className="flex flex-wrap gap-2">
{ipDetection.allCandidates.map((ip, index) => (
<Badge
key={index}
variant={ip === ipDetection.detectedIP ? "default" : "outline"}
className="text-sm"
>
{ip}
</Badge>
))}
</div>
</div>
)}
</CardContent>
</Card>
)}
{/* 連線狀態 */}
{connectionStatus && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
</CardTitle>
<CardDescription>
IP
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Badge variant="default" className="text-lg px-3 py-1">
IP: {connectionStatus.ip}
</Badge>
<Badge
variant={connectionStatus.connectionCount > 0 ? "destructive" : "default"}
className="text-lg px-3 py-1"
>
: {connectionStatus.connectionCount}
</Badge>
</div>
{connectionStatus.connections.length > 0 && (
<div>
<p className="text-sm font-medium mb-2">:</p>
<div className="space-y-2 max-h-48 overflow-y-auto">
{connectionStatus.connections.map((conn, index) => (
<div key={index} className="flex items-center justify-between p-2 border rounded text-sm">
<span>ID: {conn.ID}</span>
<span>HOST: {conn.HOST}</span>
<span>: {conn.TIME}s</span>
<span>: {conn.STATE || 'Sleep'}</span>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
)}
{/* 操作按鈕 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
IP
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex gap-4">
<Button
onClick={cleanupConnections}
disabled={loading || !ipDetection}
variant="destructive"
className="flex-1"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
</Button>
<Button
onClick={getConnectionStatus}
disabled={loading}
variant="outline"
className="flex-1"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
</Button>
</div>
</CardContent>
</Card>
{/* 使用說明 */}
<Card>
<CardHeader>
<CardTitle>使</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong>:</strong> IP </p>
<p> <strong>:</strong> 使 IP </p>
<p> <strong>:</strong> IP </p>
<p> <strong>:</strong> IP </p>
<p> <strong>:</strong> </p>
</CardContent>
</Card>
{/* 訊息顯示 */}
{message && (
<Alert>
<CheckCircle className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
</div>
);
}

325
app/debug-ip/page.tsx Normal file
View File

@@ -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<string, string | null>;
nextRequestIP: string | undefined;
env: Record<string, string | undefined>;
allHeaders: Record<string, string>;
}
export default function DebugIPPage() {
const [ipInfo, setIpInfo] = useState<IPInfo | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
const [recommendedIP, setRecommendedIP] = useState<string>('');
// 獲取 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 (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">IP 調</h1>
<p className="text-muted-foreground">
調 IP
</p>
</div>
<Button
onClick={fetchIPInfo}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
</Button>
</div>
{/* 智能偵測結果 */}
{ipInfo?.smartDetection && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Eye className="h-5 w-5" />
IP
</CardTitle>
<CardDescription>
使 IP
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Badge
variant={ipInfo.smartDetection.isPublicIP ? "default" : "secondary"}
className="text-lg px-3 py-1"
>
{ipInfo.smartDetection.detectedIP}
</Badge>
<Badge
variant={
ipInfo.smartDetection.confidence === 'high' ? "default" :
ipInfo.smartDetection.confidence === 'medium' ? "secondary" : "destructive"
}
className="text-lg px-3 py-1"
>
: {ipInfo.smartDetection.confidence}
</Badge>
<Badge variant="outline" className="text-lg px-3 py-1">
: {ipInfo.smartDetection.source}
</Badge>
</div>
{ipInfo.smartDetection.allCandidates.length > 1 && (
<div>
<p className="text-sm font-medium mb-2"> IP:</p>
<div className="flex flex-wrap gap-2">
{ipInfo.smartDetection.allCandidates.map((ip, index) => (
<Badge
key={index}
variant={ip === ipInfo.smartDetection.detectedIP ? "default" : "outline"}
className="text-sm"
>
{ip}
</Badge>
))}
</div>
</div>
)}
</CardContent>
</Card>
)}
{/* 推薦的 IP (回退顯示) */}
{ipInfo && !ipInfo.smartDetection && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Eye className="h-5 w-5" />
IP
</CardTitle>
<CardDescription>
使 IP
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center gap-4">
<Badge variant="default" className="text-lg px-3 py-1">
{getRecommendedIP()}
</Badge>
<Badge
variant={getRecommendedIP() === 'unknown' ? "destructive" : "default"}
className="text-lg px-3 py-1"
>
: {getRecommendedIP() === 'unknown' ? "無法識別" : "已識別"}
</Badge>
</div>
</CardContent>
</Card>
)}
{/* 請求標頭 */}
{ipInfo && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
HTTP IP
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
{Object.entries(ipInfo.headers).map(([key, value]) => (
<div key={key} className="flex items-center justify-between p-2 border rounded">
<span className="font-medium text-sm">{key}:</span>
<span className="text-sm text-muted-foreground">
{value || 'null'}
</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* NextRequest IP */}
{ipInfo && (
<Card>
<CardHeader>
<CardTitle>NextRequest IP</CardTitle>
<CardDescription>
Next.js IP
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center gap-4">
<Badge variant="outline" className="text-lg px-3 py-1">
{ipInfo.nextRequestIP || 'undefined'}
</Badge>
</div>
</CardContent>
</Card>
)}
{/* 環境信息 */}
{ipInfo && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
{Object.entries(ipInfo.env).map(([key, value]) => (
<div key={key} className="flex items-center justify-between p-2 border rounded">
<span className="font-medium text-sm">{key}:</span>
<span className="text-sm text-muted-foreground">
{value || 'undefined'}
</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 所有標頭(用於調試) */}
{ipInfo && (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
HTTP
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-96 overflow-y-auto">
{Object.entries(ipInfo.allHeaders).map(([key, value]) => (
<div key={key} className="flex items-center justify-between p-2 border rounded text-xs">
<span className="font-medium">{key}:</span>
<span className="text-muted-foreground break-all">
{value}
</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 訊息顯示 */}
{message && (
<Alert>
<Database className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<Eye className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 使用說明 */}
<Card>
<CardHeader>
<CardTitle>使</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong> IP:</strong> 使 IP </p>
<p> <strong>:</strong> IP </p>
<p> <strong>NextRequest IP:</strong> Next.js IP </p>
<p> <strong>:</strong> </p>
<p> IP "unknown"</p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -5,6 +5,8 @@ import { AuthProvider } from "@/contexts/auth-context"
import { CompetitionProvider } from "@/contexts/competition-context" import { CompetitionProvider } from "@/contexts/competition-context"
import { Toaster } from "@/components/ui/toaster" import { Toaster } from "@/components/ui/toaster"
import { ChatBot } from "@/components/chat-bot" import { ChatBot } from "@/components/chat-bot"
import { ClientConnectionCleanup } from "@/components/client-connection-cleanup"
import "@/lib/app-initializer" // 自動初始化應用程式
const inter = Inter({ subsets: ["latin"] }) const inter = Inter({ subsets: ["latin"] })
@@ -28,6 +30,7 @@ export default function RootLayout({
{children} {children}
<Toaster /> <Toaster />
<ChatBot /> <ChatBot />
<ClientConnectionCleanup />
</CompetitionProvider> </CompetitionProvider>
</AuthProvider> </AuthProvider>
</body> </body>

174
app/set-ip/page.tsx Normal file
View File

@@ -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<string>('');
const [error, setError] = useState<string>('');
// 設置客戶端 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 (
<div className="container mx-auto p-6 space-y-6">
<div>
<h1 className="text-3xl font-bold"> IP</h1>
<p className="text-muted-foreground">
IP
</p>
</div>
{/* IP 設置 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
IP
</CardTitle>
<CardDescription>
IP 使 IP
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="clientIP"> IP </Label>
<div className="flex gap-2">
<Input
id="clientIP"
value={clientIP}
onChange={(e) => setClientIP(e.target.value)}
placeholder="例如: 61-227-253-171"
className="flex-1"
/>
<Button
onClick={setYourIP}
disabled={loading}
variant="outline"
>
使 IP
</Button>
</div>
</div>
<div className="flex gap-2">
<Button
onClick={setClientIP}
disabled={loading || !clientIP.trim()}
className="flex-1"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <CheckCircle className="h-4 w-4" />}
IP
</Button>
<Button
onClick={testCleanup}
disabled={loading}
variant="destructive"
className="flex-1"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <AlertTriangle className="h-4 w-4" />}
</Button>
</div>
</CardContent>
</Card>
{/* 使用說明 */}
<Card>
<CardHeader>
<CardTitle>使</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong> IP:</strong> 61-227-253-171</p>
<p> <strong> IP:</strong> IP IP</p>
<p> <strong>:</strong> IP </p>
<p> <strong>:</strong> IP </p>
</CardContent>
</Card>
{/* 訊息顯示 */}
{message && (
<Alert>
<CheckCircle className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
</div>
);
}

View File

@@ -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<any>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string>('');
const [error, setError] = useState<string>('');
// 獲取連線狀態
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 (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">IP </h1>
<p className="text-muted-foreground">
IP
</p>
</div>
<Button
onClick={fetchStatus}
disabled={loading}
variant="outline"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
{/* 客戶端信息 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<p><strong> ID:</strong> {clientCleanup.getClientId()}</p>
<p><strong>:</strong> {navigator.userAgent}</p>
<p><strong>:</strong> {new Date().toLocaleString()}</p>
</div>
</CardContent>
</Card>
{/* 連線狀態 */}
{status && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Eye className="h-5 w-5" />
</CardTitle>
<CardDescription>
IP: {status.ip} | : {status.connectionCount}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Badge variant="outline" className="text-lg px-3 py-1">
: {status.connectionCount}
</Badge>
<Badge
variant={status.connectionCount > 5 ? "destructive" : "default"}
className="text-lg px-3 py-1"
>
: {status.connectionCount > 5 ? "異常" : "正常"}
</Badge>
</div>
{status.connections && status.connections.length > 0 && (
<div className="space-y-2">
<h4 className="font-medium">:</h4>
{status.connections.slice(0, 5).map((conn: any, index: number) => (
<div key={index} className="p-2 border rounded text-sm">
<p>ID: {conn.ID} | HOST: {conn.HOST} | : {conn.TIME}s</p>
</div>
))}
{status.connections.length > 5 && (
<p className="text-sm text-muted-foreground">
... {status.connections.length - 5}
</p>
)}
</div>
)}
</CardContent>
</Card>
)}
{/* 測試操作 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="h-5 w-5" />
</CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Button
onClick={manualCleanup}
disabled={loading}
variant="outline"
className="w-full"
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
</Button>
<Button
onClick={simulatePageClose}
disabled={loading}
variant="destructive"
className="w-full"
>
<Zap className="h-4 w-4" />
</Button>
<Button
onClick={() => window.location.reload()}
disabled={loading}
variant="secondary"
className="w-full"
>
<Database className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
{/* 自動清理說明 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p> <strong>:</strong> IP </p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> 5</p>
<p> <strong>:</strong> </p>
<p> <strong>:</strong> IP </p>
</CardContent>
</Card>
{/* 訊息顯示 */}
{message && (
<Alert>
<Database className="h-4 w-4" />
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<Trash2 className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
</div>
);
}

View File

@@ -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;
}

39
lib/app-initializer.ts Normal file
View File

@@ -0,0 +1,39 @@
// =====================================================
// 應用程式初始化器
// =====================================================
import { dbShutdownManager } from './database-shutdown-manager';
import { startConnectionMonitoring } from './database-middleware';
import { smartPool } from './smart-connection-pool';
// 應用程式初始化
export function initializeApp() {
console.log('🚀 正在初始化應用程式...');
try {
// 初始化資料庫關閉管理器
console.log('📋 初始化資料庫關閉管理器...');
const shutdownStatus = dbShutdownManager.getShutdownStatus();
console.log('✅ 資料庫關閉管理器已初始化:', shutdownStatus);
// 啟動資料庫連線監控
console.log('📊 啟動資料庫連線監控...');
startConnectionMonitoring();
// 初始化智能連線池
console.log('🧠 初始化智能連線池...');
const poolStats = smartPool.getConnectionStats();
console.log('✅ 智能連線池已初始化:', poolStats);
console.log('✅ 應用程式初始化完成');
} catch (error) {
console.error('❌ 應用程式初始化失敗:', error);
}
}
// 在模組載入時自動初始化
if (typeof window === 'undefined') {
// 只在伺服器端執行
initializeApp();
}

View File

@@ -0,0 +1,231 @@
// =====================================================
// 客戶端連線清理腳本
// =====================================================
export class ClientConnectionCleanup {
private static instance: ClientConnectionCleanup;
private isCleaning = false;
private clientId: string;
private constructor() {
this.clientId = this.generateClientId();
this.setupEventListeners();
}
public static getInstance(): ClientConnectionCleanup {
if (!ClientConnectionCleanup.instance) {
ClientConnectionCleanup.instance = new ClientConnectionCleanup();
}
return ClientConnectionCleanup.instance;
}
// 生成客戶端唯一標識符
private generateClientId(): string {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2);
return `client_${timestamp}_${random}`;
}
// 設置事件監聽器
private setupEventListeners() {
// 確保在瀏覽器環境中執行
if (typeof window === 'undefined') {
console.log('🖥️ 客戶端連線清理跳過(服務端環境)');
return;
}
// 頁面關閉前清理
window.addEventListener('beforeunload', () => {
this.cleanupOnUnload();
});
// 頁面隱藏時清理
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.cleanupOnHidden();
}
});
// 頁面卸載時清理
window.addEventListener('unload', () => {
this.cleanupOnUnload();
});
// 定期清理每5分鐘
setInterval(() => {
this.periodicCleanup();
}, 5 * 60 * 1000);
console.log('🖥️ 客戶端連線清理已啟動');
}
// 頁面關閉前清理
private async cleanupOnUnload() {
if (this.isCleaning) return;
// 確保在瀏覽器環境中執行
if (typeof window === 'undefined') return;
this.isCleaning = true;
console.log('🔄 頁面關閉前清理連線...');
try {
// 使用 sendBeacon 確保請求能夠發送
const data = JSON.stringify({
action: 'cleanup-current',
clientId: this.clientId,
timestamp: Date.now()
});
// 嘗試使用 sendBeacon
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/ip-cleanup', data);
console.log('✅ 使用 sendBeacon 發送清理請求');
} else {
// 備用方案:使用 fetch
await fetch('/api/ip-cleanup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: data,
keepalive: true
});
console.log('✅ 使用 fetch 發送清理請求');
}
} catch (error) {
console.error('❌ 清理請求發送失敗:', error);
}
}
// 頁面隱藏時清理
private async cleanupOnHidden() {
// 確保在瀏覽器環境中執行
if (typeof window === 'undefined') return;
console.log('🔄 頁面隱藏時清理連線...');
try {
await fetch('/api/ip-cleanup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'cleanup-current',
clientId: this.clientId,
reason: 'page-hidden'
})
});
console.log('✅ 頁面隱藏清理完成');
} catch (error) {
console.error('❌ 頁面隱藏清理失敗:', error);
}
}
// 定期清理
private async periodicCleanup() {
// 確保在瀏覽器環境中執行
if (typeof window === 'undefined') return;
console.log('🔄 定期清理連線...');
try {
const response = await fetch('/api/ip-cleanup?action=local-stats');
const data = await response.json();
if (data.success && data.data.trackedConnections > 10) {
// 如果追蹤的連線數超過10個執行清理
await fetch('/api/ip-cleanup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'cleanup-local',
clientId: this.clientId,
reason: 'periodic-cleanup'
})
});
console.log('✅ 定期清理完成');
}
} catch (error) {
console.error('❌ 定期清理失敗:', error);
}
}
// 手動清理
public async manualCleanup(): Promise<boolean> {
// 確保在瀏覽器環境中執行
if (typeof window === 'undefined') return false;
try {
const response = await fetch('/api/ip-cleanup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'cleanup-current',
clientId: this.clientId,
reason: 'manual-cleanup'
})
});
const data = await response.json();
if (data.success) {
console.log('✅ 手動清理完成:', data.message);
return true;
} else {
console.error('❌ 手動清理失敗:', data.error);
return false;
}
} catch (error) {
console.error('❌ 手動清理錯誤:', error);
return false;
}
}
// 獲取連線狀態
public async getConnectionStatus() {
// 確保在瀏覽器環境中執行
if (typeof window === 'undefined') return null;
try {
const response = await fetch('/api/ip-cleanup?action=status');
const data = await response.json();
if (data.success) {
console.log('📊 連線狀態:', data.data);
return data.data;
} else {
console.error('❌ 獲取連線狀態失敗:', data.error);
return null;
}
} catch (error) {
console.error('❌ 獲取連線狀態錯誤:', error);
return null;
}
}
// 獲取客戶端 ID
public getClientId(): string {
return this.clientId;
}
}
// 導出單例實例
export const clientCleanup = ClientConnectionCleanup.getInstance();
// 在模組載入時自動初始化(只在瀏覽器環境中)
if (typeof window !== 'undefined') {
// 延遲初始化,確保 DOM 已載入
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
clientCleanup;
});
} else {
clientCleanup;
}
}

View File

@@ -0,0 +1,178 @@
// =====================================================
// 連線生命週期管理器
// =====================================================
import { db } from './database';
import { dbFailover } from './database-failover';
export class ConnectionLifecycleManager {
private static instance: ConnectionLifecycleManager;
private activeConnections = new Map<string, {
connection: any;
createdAt: number;
lastUsed: number;
userId?: string;
sessionId?: string;
}>();
private cleanupInterval: NodeJS.Timeout | null = null;
private maxIdleTime = 5 * 60 * 1000; // 5分鐘空閒超時
private maxConnectionAge = 30 * 60 * 1000; // 30分鐘最大連線時間
private constructor() {
this.startCleanupProcess();
}
public static getInstance(): ConnectionLifecycleManager {
if (!ConnectionLifecycleManager.instance) {
ConnectionLifecycleManager.instance = new ConnectionLifecycleManager();
}
return ConnectionLifecycleManager.instance;
}
// 註冊連線
public registerConnection(connectionId: string, connection: any, metadata?: {
userId?: string;
sessionId?: string;
}) {
this.activeConnections.set(connectionId, {
connection,
createdAt: Date.now(),
lastUsed: Date.now(),
userId: metadata?.userId,
sessionId: metadata?.sessionId
});
console.log(`📝 註冊連線: ${connectionId} (總數: ${this.activeConnections.size})`);
}
// 更新連線使用時間
public updateConnectionUsage(connectionId: string) {
const conn = this.activeConnections.get(connectionId);
if (conn) {
conn.lastUsed = Date.now();
}
}
// 釋放連線
public releaseConnection(connectionId: string) {
const conn = this.activeConnections.get(connectionId);
if (conn) {
try {
if (conn.connection && typeof conn.connection.release === 'function') {
conn.connection.release();
}
this.activeConnections.delete(connectionId);
console.log(`🗑️ 釋放連線: ${connectionId} (剩餘: ${this.activeConnections.size})`);
} catch (error) {
console.error(`❌ 釋放連線失敗: ${connectionId}`, error);
}
}
}
// 開始清理程序
private startCleanupProcess() {
// 每30秒檢查一次空閒連線
this.cleanupInterval = setInterval(() => {
this.cleanupIdleConnections();
}, 30 * 1000);
console.log('🧹 連線生命週期管理器已啟動');
}
// 清理空閒連線
private cleanupIdleConnections() {
const now = Date.now();
const toRemove: string[] = [];
for (const [connectionId, conn] of this.activeConnections) {
const idleTime = now - conn.lastUsed;
const connectionAge = now - conn.createdAt;
// 檢查空閒時間
if (idleTime > this.maxIdleTime) {
console.log(`⏰ 連線 ${connectionId} 空閒時間過長 (${Math.round(idleTime / 1000)}s),準備釋放`);
toRemove.push(connectionId);
}
// 檢查連線年齡
else if (connectionAge > this.maxConnectionAge) {
console.log(`⏰ 連線 ${connectionId} 存在時間過長 (${Math.round(connectionAge / 1000)}s),準備釋放`);
toRemove.push(connectionId);
}
}
// 釋放需要清理的連線
toRemove.forEach(connectionId => {
this.releaseConnection(connectionId);
});
if (toRemove.length > 0) {
console.log(`🧹 清理了 ${toRemove.length} 個空閒連線`);
}
}
// 強制清理所有連線
public forceCleanupAll() {
console.log('🚨 強制清理所有連線...');
const connectionIds = Array.from(this.activeConnections.keys());
connectionIds.forEach(connectionId => {
this.releaseConnection(connectionId);
});
console.log(`✅ 已清理 ${connectionIds.length} 個連線`);
}
// 獲取連線統計
public getConnectionStats() {
const now = Date.now();
const connections = Array.from(this.activeConnections.values());
const idleConnections = connections.filter(conn =>
now - conn.lastUsed > this.maxIdleTime
).length;
const oldConnections = connections.filter(conn =>
now - conn.createdAt > this.maxConnectionAge
).length;
return {
totalConnections: this.activeConnections.size,
idleConnections,
oldConnections,
maxIdleTime: this.maxIdleTime,
maxConnectionAge: this.maxConnectionAge,
connections: connections.map(conn => ({
createdAt: new Date(conn.createdAt).toISOString(),
lastUsed: new Date(conn.lastUsed).toISOString(),
idleTime: now - conn.lastUsed,
age: now - conn.createdAt,
userId: conn.userId,
sessionId: conn.sessionId
}))
};
}
// 停止清理程序
public stop() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
// 清理所有連線
this.forceCleanupAll();
console.log('⏹️ 連線生命週期管理器已停止');
}
// 設置清理參數
public setCleanupParams(maxIdleTime?: number, maxConnectionAge?: number) {
if (maxIdleTime) this.maxIdleTime = maxIdleTime;
if (maxConnectionAge) this.maxConnectionAge = maxConnectionAge;
console.log(`⚙️ 更新清理參數: 空閒時間=${this.maxIdleTime}ms, 最大年齡=${this.maxConnectionAge}ms`);
}
}
// 導出單例實例
export const connectionLifecycleManager = ConnectionLifecycleManager.getInstance();

View File

@@ -5,6 +5,7 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { db } from './database'; import { db } from './database';
import { dbMonitor } from './database-monitor'; import { dbMonitor } from './database-monitor';
import { dbShutdownManager } from './database-shutdown-manager';
// 連線池狀態追蹤 // 連線池狀態追蹤
let connectionCount = 0; let connectionCount = 0;
@@ -135,22 +136,24 @@ export function startConnectionMonitoring() {
console.log('🔍 資料庫連線監控已啟動'); console.log('🔍 資料庫連線監控已啟動');
} }
// 優雅的關閉 // 優雅的關閉(使用新的關閉管理器)
export async function gracefulShutdown() { export async function gracefulShutdown() {
console.log('🔄 正在優雅關閉資料庫連線...'); console.log('🔄 使用資料庫關閉管理器進行優雅關閉...');
await dbShutdownManager.gracefulShutdown();
try {
dbMonitor.stopMonitoring();
await db.close();
console.log('✅ 資料庫連線已關閉');
} catch (error) {
console.error('❌ 關閉資料庫連線時發生錯誤:', error);
}
} }
// 處理程序退出事件 // 強制關閉
if (typeof process !== 'undefined') { export function forceShutdown() {
process.on('SIGINT', gracefulShutdown); console.log('🚨 強制關閉資料庫連線...');
process.on('SIGTERM', gracefulShutdown); dbShutdownManager.forceShutdown();
process.on('exit', gracefulShutdown); }
// 獲取關閉狀態
export function getShutdownStatus() {
return dbShutdownManager.getShutdownStatus();
}
// 測試關閉機制
export async function testShutdown() {
return await dbShutdownManager.testShutdown();
} }

View File

@@ -50,21 +50,25 @@ export class DatabaseMonitor {
// 檢查連線狀態 // 檢查連線狀態
private async checkConnectionStatus() { private async checkConnectionStatus() {
let connection = null;
try { try {
// 使用單一連線來檢查狀態,避免建立多個連線
connection = await db.getConnection();
// 檢查當前連線數 // 檢查當前連線數
const statusResult = await db.queryOne(` const [statusResult] = await connection.execute(`
SHOW STATUS LIKE 'Threads_connected' SHOW STATUS LIKE 'Threads_connected'
`); `);
const currentConnections = statusResult?.Value || 0; const currentConnections = statusResult[0]?.Value || 0;
this.connectionCount = parseInt(currentConnections); this.connectionCount = parseInt(currentConnections);
// 檢查最大連線數 // 檢查最大連線數
const maxConnResult = await db.queryOne(` const [maxConnResult] = await connection.execute(`
SHOW VARIABLES LIKE 'max_connections' 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; const usagePercentage = (this.connectionCount / this.maxConnections) * 100;
@@ -80,43 +84,68 @@ export class DatabaseMonitor {
} }
// 檢查長時間運行的查詢 // 檢查長時間運行的查詢
await this.checkLongRunningQueries(); await this.checkLongRunningQueries(connection);
} catch (error) { } catch (error) {
console.error('❌ 資料庫監控錯誤:', error); console.error('❌ 資料庫監控錯誤:', error);
} finally {
// 確保釋放連線
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('❌ 釋放監控連線失敗:', releaseError);
}
}
} }
} }
// 檢查長時間運行的查詢 // 檢查長時間運行的查詢
private async checkLongRunningQueries() { private async checkLongRunningQueries(connection?: any) {
try { try {
const longQueries = await db.query(` // 如果沒有傳入連線,則建立一個新的
SELECT let tempConnection = connection;
ID, let shouldRelease = false;
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) { if (!tempConnection) {
console.warn(`⚠️ 發現 ${longQueries.length} 個長時間運行的查詢:`); tempConnection = await db.getConnection();
longQueries.forEach((query, index) => { shouldRelease = true;
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)}...`); try {
} else if (query.INFO) { const [longQueries] = await tempConnection.execute(`
console.warn(` 查詢: ${query.INFO}`); 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: 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) { } catch (error) {
console.error('❌ 檢查長時間查詢時發生錯誤:', error); console.error('❌ 檢查長時間查詢時發生錯誤:', error);

View File

@@ -0,0 +1,89 @@
// =====================================================
// 智能資料庫服務範例
// =====================================================
import { smartPool } from './smart-connection-pool';
// 這是一個範例,展示如何使用智能連線池
// 你可以將現有的資料庫服務改為使用這個方式
export class SmartDatabaseService {
// 智能查詢範例
static async getUserById(userId: string, requestId?: string) {
return await smartPool.executeQueryOne(
'SELECT * FROM users WHERE id = ?',
[userId],
{
userId,
sessionId: requestId,
requestId: `getUserById_${Date.now()}`
}
);
}
// 智能插入範例
static async createUser(userData: any, requestId?: string) {
return await smartPool.executeInsert(
'INSERT INTO users (name, email, department) VALUES (?, ?, ?)',
[userData.name, userData.email, userData.department],
{
userId: userData.id,
sessionId: requestId,
requestId: `createUser_${Date.now()}`
}
);
}
// 智能更新範例
static async updateUser(userId: string, userData: any, requestId?: string) {
return await smartPool.executeUpdate(
'UPDATE users SET name = ?, email = ?, department = ? WHERE id = ?',
[userData.name, userData.email, userData.department, userId],
{
userId,
sessionId: requestId,
requestId: `updateUser_${Date.now()}`
}
);
}
// 智能刪除範例
static async deleteUser(userId: string, requestId?: string) {
return await smartPool.executeDelete(
'DELETE FROM users WHERE id = ?',
[userId],
{
userId,
sessionId: requestId,
requestId: `deleteUser_${Date.now()}`
}
);
}
// 獲取連線統計
static getConnectionStats() {
return smartPool.getConnectionStats();
}
// 強制清理連線
static forceCleanup() {
return smartPool.forceCleanup();
}
}
// 使用範例:
/*
// 在 API 路由中使用
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const userId = searchParams.get('userId');
const requestId = request.headers.get('x-request-id') || 'unknown';
try {
const user = await SmartDatabaseService.getUserById(userId, requestId);
return NextResponse.json({ success: true, data: user });
} catch (error) {
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}
*/

View File

@@ -0,0 +1,197 @@
// =====================================================
// 資料庫關閉管理器
// =====================================================
import { db } from './database';
import { dbFailover } from './database-failover';
import { dbMonitor } from './database-monitor';
import { smartPool } from './smart-connection-pool';
export class DatabaseShutdownManager {
private static instance: DatabaseShutdownManager;
private isShuttingDown = false;
private shutdownHandlers: (() => Promise<void>)[] = [];
private constructor() {
this.registerShutdownHandlers();
}
public static getInstance(): DatabaseShutdownManager {
if (!DatabaseShutdownManager.instance) {
DatabaseShutdownManager.instance = new DatabaseShutdownManager();
}
return DatabaseShutdownManager.instance;
}
// 註冊關閉處理器
private registerShutdownHandlers() {
// 添加資料庫關閉處理器
this.addShutdownHandler('database', async () => {
console.log('🔄 正在關閉主要資料庫連線池...');
try {
await db.close();
console.log('✅ 主要資料庫連線池已關閉');
} catch (error) {
console.error('❌ 關閉主要資料庫連線池時發生錯誤:', error);
}
});
// 添加備援資料庫關閉處理器
this.addShutdownHandler('failover', async () => {
console.log('🔄 正在關閉備援資料庫連線池...');
try {
await dbFailover.close();
console.log('✅ 備援資料庫連線池已關閉');
} catch (error) {
console.error('❌ 關閉備援資料庫連線池時發生錯誤:', error);
}
});
// 添加監控服務關閉處理器
this.addShutdownHandler('monitor', async () => {
console.log('🔄 正在停止資料庫監控服務...');
try {
dbMonitor.stopMonitoring();
console.log('✅ 資料庫監控服務已停止');
} catch (error) {
console.error('❌ 停止資料庫監控服務時發生錯誤:', error);
}
});
// 添加智能連線池關閉處理器
this.addShutdownHandler('smart-pool', async () => {
console.log('🔄 正在清理智能連線池...');
try {
smartPool.forceCleanup();
console.log('✅ 智能連線池已清理');
} catch (error) {
console.error('❌ 清理智能連線池時發生錯誤:', error);
}
});
// 註冊系統信號處理器
this.registerSystemHandlers();
}
// 添加關閉處理器
public addShutdownHandler(name: string, handler: () => Promise<void>) {
this.shutdownHandlers.push(async () => {
try {
console.log(`🔄 執行關閉處理器: ${name}`);
await handler();
console.log(`✅ 關閉處理器完成: ${name}`);
} catch (error) {
console.error(`❌ 關閉處理器失敗: ${name}`, error);
}
});
}
// 註冊系統信號處理器
private registerSystemHandlers() {
if (typeof process === 'undefined') return;
// SIGINT (Ctrl+C)
process.on('SIGINT', () => {
console.log('\n🛑 收到 SIGINT 信號,開始優雅關閉...');
this.gracefulShutdown();
});
// SIGTERM (終止信號)
process.on('SIGTERM', () => {
console.log('\n🛑 收到 SIGTERM 信號,開始優雅關閉...');
this.gracefulShutdown();
});
// 未捕獲的異常
process.on('uncaughtException', (error) => {
console.error('❌ 未捕獲的異常:', error);
this.gracefulShutdown();
});
// 未處理的 Promise 拒絕
process.on('unhandledRejection', (reason, promise) => {
console.error('❌ 未處理的 Promise 拒絕:', reason);
this.gracefulShutdown();
});
// 程序退出
process.on('exit', () => {
if (!this.isShuttingDown) {
console.log('🛑 程序即將退出,強制關閉資料庫連線...');
this.forceShutdown();
}
});
console.log('✅ 系統信號處理器已註冊');
}
// 優雅關閉
public async gracefulShutdown() {
if (this.isShuttingDown) {
console.log('⚠️ 關閉程序已在進行中,跳過重複請求');
return;
}
this.isShuttingDown = true;
console.log('🔄 開始優雅關閉資料庫連線...');
const startTime = Date.now();
try {
// 並行執行所有關閉處理器
await Promise.allSettled(
this.shutdownHandlers.map(handler => handler())
);
const duration = Date.now() - startTime;
console.log(`✅ 資料庫連線關閉完成,耗時: ${duration}ms`);
} catch (error) {
console.error('❌ 優雅關閉過程中發生錯誤:', error);
} finally {
// 強制退出程序
setTimeout(() => {
console.log('🛑 強制退出程序');
process.exit(0);
}, 5000); // 5秒後強制退出
}
}
// 強制關閉
public forceShutdown() {
console.log('🚨 強制關閉所有資料庫連線...');
// 同步執行關閉處理器(不等待)
this.shutdownHandlers.forEach(handler => {
try {
handler();
} catch (error) {
console.error('❌ 強制關閉時發生錯誤:', error);
}
});
console.log('✅ 強制關閉完成');
}
// 獲取關閉狀態
public getShutdownStatus() {
return {
isShuttingDown: this.isShuttingDown,
handlerCount: this.shutdownHandlers.length,
registeredHandlers: this.shutdownHandlers.map((_, index) => `handler-${index}`)
};
}
// 測試關閉機制
public async testShutdown() {
console.log('🧪 測試資料庫關閉機制...');
await this.gracefulShutdown();
}
}
// 導出單例實例
export const dbShutdownManager = DatabaseShutdownManager.getInstance();
// 導出便捷函數
export const gracefulShutdown = () => dbShutdownManager.gracefulShutdown();
export const forceShutdown = () => dbShutdownManager.forceShutdown();

View File

@@ -0,0 +1,175 @@
// =====================================================
// 緊急連線清理工具
// =====================================================
import { db } from './database';
import { dbFailover } from './database-failover';
import { dbMonitor } from './database-monitor';
export class EmergencyConnectionCleanup {
private static instance: EmergencyConnectionCleanup;
private constructor() {}
public static getInstance(): EmergencyConnectionCleanup {
if (!EmergencyConnectionCleanup.instance) {
EmergencyConnectionCleanup.instance = new EmergencyConnectionCleanup();
}
return EmergencyConnectionCleanup.instance;
}
// 立即停止所有監控和清理所有連線
public async emergencyCleanup() {
console.log('🚨 執行緊急連線清理...');
try {
// 1. 立即停止所有監控
console.log('⏹️ 停止資料庫監控...');
dbMonitor.stopMonitoring();
// 2. 強制關閉所有連線池
console.log('🔌 關閉主要資料庫連線池...');
await db.close();
console.log('🔌 關閉備援資料庫連線池...');
await dbFailover.close();
// 3. 等待一段時間讓連線完全關閉
console.log('⏳ 等待連線關閉...');
await new Promise(resolve => setTimeout(resolve, 2000));
// 4. 檢查連線狀態
await this.checkConnectionStatus();
console.log('✅ 緊急清理完成');
} catch (error) {
console.error('❌ 緊急清理失敗:', error);
}
}
// 檢查連線狀態(不建立新連線)
private async checkConnectionStatus() {
try {
// 使用一個臨時連線來檢查狀態
const tempConnection = await db.getConnection();
try {
const [statusResult] = await tempConnection.execute(`
SHOW STATUS LIKE 'Threads_connected'
`);
const [maxConnResult] = await tempConnection.execute(`
SHOW VARIABLES LIKE 'max_connections'
`);
const currentConnections = statusResult[0]?.Value || 0;
const maxConnections = maxConnResult[0]?.Value || 100;
const usagePercentage = (currentConnections / maxConnections) * 100;
console.log(`📊 清理後連線狀態: ${currentConnections}/${maxConnections} (${usagePercentage.toFixed(1)}%)`);
if (currentConnections > 5) {
console.warn(`⚠️ 仍有 ${currentConnections} 個連線未關閉`);
} else {
console.log('✅ 連線已成功清理');
}
} finally {
tempConnection.release();
}
} catch (error) {
console.error('❌ 檢查連線狀態失敗:', error);
}
}
// 強制殺死所有資料庫連線
public async forceKillAllConnections() {
console.log('💀 強制殺死所有資料庫連線...');
try {
const tempConnection = await db.getConnection();
try {
// 獲取所有連線ID
const [connections] = await tempConnection.execute(`
SELECT ID FROM information_schema.PROCESSLIST
WHERE USER = ? AND COMMAND != 'Sleep'
`, [process.env.DB_USER || 'A999']);
console.log(`🔍 找到 ${connections.length} 個活躍連線`);
// 殺死所有連線(除了當前連線)
for (const conn of connections) {
if (conn.ID !== tempConnection.threadId) {
try {
await tempConnection.execute(`KILL ${conn.ID}`);
console.log(`💀 已殺死連線 ${conn.ID}`);
} catch (error) {
console.log(`⚠️ 無法殺死連線 ${conn.ID}:`, error.message);
}
}
}
// 等待連線關閉
await new Promise(resolve => setTimeout(resolve, 1000));
// 再次檢查狀態
await this.checkConnectionStatus();
} finally {
tempConnection.release();
}
} catch (error) {
console.error('❌ 強制殺死連線失敗:', error);
}
}
// 獲取連線詳情
public async getConnectionDetails() {
try {
const tempConnection = await db.getConnection();
try {
const [connections] = await tempConnection.execute(`
SELECT
ID,
USER,
HOST,
DB,
COMMAND,
TIME,
STATE,
INFO
FROM information_schema.PROCESSLIST
WHERE USER = ?
ORDER BY TIME DESC
`, [process.env.DB_USER || 'A999']);
console.log('📋 當前資料庫連線詳情:');
connections.forEach((conn, index) => {
console.log(`${index + 1}. ID: ${conn.ID}, 用戶: ${conn.USER}, 時間: ${conn.TIME}s, 狀態: ${conn.STATE}`);
if (conn.INFO && conn.INFO.length > 50) {
console.log(` 查詢: ${conn.INFO.substring(0, 50)}...`);
} else if (conn.INFO) {
console.log(` 查詢: ${conn.INFO}`);
}
});
return connections;
} finally {
tempConnection.release();
}
} catch (error) {
console.error('❌ 獲取連線詳情失敗:', error);
return [];
}
}
}
// 導出單例實例
export const emergencyCleanup = EmergencyConnectionCleanup.getInstance();

View File

@@ -0,0 +1,251 @@
// =====================================================
// 基於 IP 的連線管理器
// =====================================================
import mysql from 'mysql2/promise';
import { db } from './database';
export class IPBasedConnectionManager {
private static instance: IPBasedConnectionManager;
private clientIP: string | null = null;
private connectionTracker = new Map<string, {
connectionId: string;
createdAt: number;
lastUsed: number;
userAgent?: string;
}>();
private constructor() {
this.detectClientIP();
}
public static getInstance(): IPBasedConnectionManager {
if (!IPBasedConnectionManager.instance) {
IPBasedConnectionManager.instance = new IPBasedConnectionManager();
}
return IPBasedConnectionManager.instance;
}
// 檢測客戶端 IP
private detectClientIP() {
// 在瀏覽器環境中,我們無法直接獲取真實 IP
// 但我們可以通過其他方式來識別連線
if (typeof window !== 'undefined') {
// 客戶端:生成一個唯一標識符
this.clientIP = this.generateClientId();
console.log('🖥️ 客戶端標識符:', this.clientIP);
} else {
// 服務端:嘗試從環境變數或請求中獲取
this.clientIP = process.env.CLIENT_IP || 'server-side';
console.log('🖥️ 服務端標識符:', this.clientIP);
}
}
// 生成客戶端唯一標識符
private generateClientId(): string {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2);
return `client_${timestamp}_${random}`;
}
// 設置客戶端 IP從請求中獲取
public setClientIP(ip: string) {
this.clientIP = ip;
console.log('🖥️ 設置客戶端 IP:', ip);
}
// 獲取客戶端 IP
public getClientIP(): string | null {
return this.clientIP;
}
// 註冊連線(帶 IP 標識)
public registerConnection(connectionId: string, metadata?: {
userAgent?: string;
requestId?: string;
}) {
if (!this.clientIP) return;
this.connectionTracker.set(connectionId, {
connectionId,
createdAt: Date.now(),
lastUsed: Date.now(),
userAgent: metadata?.userAgent,
});
console.log(`📝 註冊連線: ${connectionId} (IP: ${this.clientIP})`);
}
// 更新連線使用時間
public updateConnectionUsage(connectionId: string) {
const conn = this.connectionTracker.get(connectionId);
if (conn) {
conn.lastUsed = Date.now();
}
}
// 釋放連線
public releaseConnection(connectionId: string) {
const conn = this.connectionTracker.get(connectionId);
if (conn) {
this.connectionTracker.delete(connectionId);
console.log(`🗑️ 釋放連線: ${connectionId} (IP: ${this.clientIP})`);
}
}
// 根據 IP 清理連線
public async cleanupConnectionsByIP(targetIP?: string): Promise<{
success: boolean;
killedCount: number;
message: string;
}> {
const ipToClean = targetIP || this.clientIP;
if (!ipToClean) {
return {
success: false,
killedCount: 0,
message: '無法識別客戶端 IP'
};
}
console.log(`🧹 開始清理 IP ${ipToClean} 的連線...`);
let connection = null;
let killedCount = 0;
try {
// 建立臨時連線
connection = await db.getConnection();
// 獲取指定 IP 的所有連線(使用更寬鬆的匹配)
const [connections] = await connection.execute(`
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
FROM information_schema.PROCESSLIST
WHERE USER = ? AND (HOST LIKE ? OR HOST LIKE ? OR HOST LIKE ?)
ORDER BY TIME DESC
`, [
process.env.DB_USER || 'A999',
`%${ipToClean}%`, // 包含 IP 的 HOST
`%${ipToClean}.%`, // IP 開頭的 HOST
`%${ipToClean}-%` // IP 帶連字符的 HOST
]);
console.log(`🔍 找到 ${connections.length} 個來自 ${ipToClean} 的連線`);
// 顯示連線詳情
connections.forEach((conn: any, index: number) => {
console.log(`${index + 1}. ID: ${conn.ID}, HOST: ${conn.HOST}, 時間: ${conn.TIME}s, 狀態: ${conn.STATE}`);
});
// 殺死指定 IP 的連線(除了當前連線)
for (const conn of connections) {
if (conn.ID !== connection.threadId) {
try {
await connection.execute(`KILL ${conn.ID}`);
console.log(`💀 已殺死連線 ${conn.ID} (HOST: ${conn.HOST})`);
killedCount++;
} catch (error: any) {
console.log(`⚠️ 無法殺死連線 ${conn.ID}: ${error.message}`);
}
}
}
// 清理本地追蹤的連線
this.connectionTracker.clear();
console.log(`✅ 已清理 ${killedCount} 個來自 ${ipToClean} 的連線`);
return {
success: true,
killedCount,
message: `已清理 ${killedCount} 個來自 ${ipToClean} 的連線`
};
} catch (error) {
console.error('❌ 清理連線失敗:', error);
return {
success: false,
killedCount: 0,
message: `清理失敗: ${error instanceof Error ? error.message : '未知錯誤'}`
};
} finally {
if (connection) {
connection.release();
}
}
}
// 獲取指定 IP 的連線狀態
public async getConnectionsByIP(targetIP?: string): Promise<{
ip: string;
connectionCount: number;
connections: any[];
}> {
const ipToCheck = targetIP || this.clientIP;
if (!ipToCheck) {
return {
ip: 'unknown',
connectionCount: 0,
connections: []
};
}
let connection = null;
try {
connection = await db.getConnection();
const [connections] = await connection.execute(`
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
FROM information_schema.PROCESSLIST
WHERE USER = ? AND HOST LIKE ?
ORDER BY TIME DESC
`, [process.env.DB_USER || 'A999', `%${ipToCheck}%`]);
return {
ip: ipToCheck,
connectionCount: connections.length,
connections: connections
};
} catch (error) {
console.error('❌ 獲取連線狀態失敗:', error);
return {
ip: ipToCheck,
connectionCount: 0,
connections: []
};
} finally {
if (connection) {
connection.release();
}
}
}
// 清理所有本地追蹤的連線
public cleanupLocalConnections() {
const count = this.connectionTracker.size;
this.connectionTracker.clear();
console.log(`🧹 已清理 ${count} 個本地追蹤的連線`);
return count;
}
// 獲取本地連線統計
public getLocalConnectionStats() {
return {
clientIP: this.clientIP,
trackedConnections: this.connectionTracker.size,
connections: Array.from(this.connectionTracker.values()).map(conn => ({
connectionId: conn.connectionId,
createdAt: new Date(conn.createdAt).toISOString(),
lastUsed: new Date(conn.lastUsed).toISOString(),
userAgent: conn.userAgent
}))
};
}
}
// 導出單例實例
export const ipConnectionManager = IPBasedConnectionManager.getInstance();

View File

@@ -0,0 +1,202 @@
// =====================================================
// 智能連線池包裝器
// =====================================================
import { db } from './database';
import { dbFailover } from './database-failover';
import { connectionLifecycleManager } from './connection-lifecycle-manager';
export class SmartConnectionPool {
private static instance: SmartConnectionPool;
private connectionCounter = 0;
private constructor() {}
public static getInstance(): SmartConnectionPool {
if (!SmartConnectionPool.instance) {
SmartConnectionPool.instance = new SmartConnectionPool();
}
return SmartConnectionPool.instance;
}
// 獲取智能連線
public async getSmartConnection(metadata?: {
userId?: string;
sessionId?: string;
requestId?: string;
}): Promise<{
connection: any;
connectionId: string;
release: () => void;
}> {
const connectionId = `conn_${++this.connectionCounter}_${Date.now()}`;
try {
// 獲取實際連線
const connection = await db.getConnection();
// 註冊到生命週期管理器
connectionLifecycleManager.registerConnection(connectionId, connection, metadata);
// 創建包裝的釋放函數
const release = () => {
connectionLifecycleManager.releaseConnection(connectionId);
};
console.log(`🔗 獲取智能連線: ${connectionId}`);
return {
connection,
connectionId,
release
};
} catch (error) {
console.error(`❌ 獲取智能連線失敗: ${connectionId}`, error);
throw error;
}
}
// 執行智能查詢
public async executeQuery<T = any>(
sql: string,
params?: any[],
metadata?: {
userId?: string;
sessionId?: string;
requestId?: string;
}
): Promise<T[]> {
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
try {
// 更新連線使用時間
connectionLifecycleManager.updateConnectionUsage(connectionId);
// 執行查詢
const [rows] = await connection.execute(sql, params);
return rows as T[];
} catch (error) {
console.error(`❌ 智能查詢失敗: ${connectionId}`, error);
throw error;
} finally {
// 確保釋放連線
release();
}
}
// 執行智能單一查詢
public async executeQueryOne<T = any>(
sql: string,
params?: any[],
metadata?: {
userId?: string;
sessionId?: string;
requestId?: string;
}
): Promise<T | null> {
const results = await this.executeQuery<T>(sql, params, metadata);
return results.length > 0 ? results[0] : null;
}
// 執行智能插入
public async executeInsert(
sql: string,
params?: any[],
metadata?: {
userId?: string;
sessionId?: string;
requestId?: string;
}
): Promise<any> {
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
try {
// 更新連線使用時間
connectionLifecycleManager.updateConnectionUsage(connectionId);
// 執行插入
const [result] = await connection.execute(sql, params);
return result;
} catch (error) {
console.error(`❌ 智能插入失敗: ${connectionId}`, error);
throw error;
} finally {
// 確保釋放連線
release();
}
}
// 執行智能更新
public async executeUpdate(
sql: string,
params?: any[],
metadata?: {
userId?: string;
sessionId?: string;
requestId?: string;
}
): Promise<any> {
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
try {
// 更新連線使用時間
connectionLifecycleManager.updateConnectionUsage(connectionId);
// 執行更新
const [result] = await connection.execute(sql, params);
return result;
} catch (error) {
console.error(`❌ 智能更新失敗: ${connectionId}`, error);
throw error;
} finally {
// 確保釋放連線
release();
}
}
// 執行智能刪除
public async executeDelete(
sql: string,
params?: any[],
metadata?: {
userId?: string;
sessionId?: string;
requestId?: string;
}
): Promise<any> {
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
try {
// 更新連線使用時間
connectionLifecycleManager.updateConnectionUsage(connectionId);
// 執行刪除
const [result] = await connection.execute(sql, params);
return result;
} catch (error) {
console.error(`❌ 智能刪除失敗: ${connectionId}`, error);
throw error;
} finally {
// 確保釋放連線
release();
}
}
// 獲取連線統計
public getConnectionStats() {
return connectionLifecycleManager.getConnectionStats();
}
// 強制清理所有連線
public forceCleanup() {
connectionLifecycleManager.forceCleanupAll();
}
// 設置清理參數
public setCleanupParams(maxIdleTime?: number, maxConnectionAge?: number) {
connectionLifecycleManager.setCleanupParams(maxIdleTime, maxConnectionAge);
}
}
// 導出單例實例
export const smartPool = SmartConnectionPool.getInstance();

View File

@@ -0,0 +1,153 @@
// =====================================================
// 智能 IP 連線池
// =====================================================
import { db } from './database';
import { ipConnectionManager } from './ip-based-connection-manager';
export class SmartIPConnectionPool {
private static instance: SmartIPConnectionPool;
private connectionCounter = 0;
private constructor() {}
public static getInstance(): SmartIPConnectionPool {
if (!SmartIPConnectionPool.instance) {
SmartIPConnectionPool.instance = new SmartIPConnectionPool();
}
return SmartIPConnectionPool.instance;
}
// 設置客戶端 IP
public setClientIP(ip: string) {
ipConnectionManager.setClientIP(ip);
}
// 獲取智能連線(帶 IP 追蹤)
public async getSmartConnection(metadata?: {
userAgent?: string;
requestId?: string;
clientIP?: string;
}): Promise<{
connection: any;
connectionId: string;
release: () => void;
}> {
const connectionId = `conn_${++this.connectionCounter}_${Date.now()}`;
// 設置客戶端 IP
if (metadata?.clientIP) {
ipConnectionManager.setClientIP(metadata.clientIP);
}
try {
// 獲取實際連線
const connection = await db.getConnection();
// 註冊到 IP 連線管理器
ipConnectionManager.registerConnection(connectionId, {
userAgent: metadata?.userAgent,
requestId: metadata?.requestId
});
// 創建包裝的釋放函數
const release = () => {
ipConnectionManager.releaseConnection(connectionId);
};
console.log(`🔗 獲取智能 IP 連線: ${connectionId}`);
return {
connection,
connectionId,
release
};
} catch (error) {
console.error(`❌ 獲取智能 IP 連線失敗: ${connectionId}`, error);
throw error;
}
}
// 執行智能查詢(帶 IP 追蹤)
public async executeQuery<T = any>(
sql: string,
params?: any[],
metadata?: {
userAgent?: string;
requestId?: string;
clientIP?: string;
}
): Promise<T[]> {
const { connection, connectionId, release } = await this.getSmartConnection(metadata);
try {
// 更新連線使用時間
ipConnectionManager.updateConnectionUsage(connectionId);
// 執行查詢
const [rows] = await connection.execute(sql, params);
return rows as T[];
} catch (error) {
console.error(`❌ 智能 IP 查詢失敗: ${connectionId}`, error);
throw error;
} finally {
// 確保釋放連線
release();
}
}
// 執行智能單一查詢
public async executeQueryOne<T = any>(
sql: string,
params?: any[],
metadata?: {
userAgent?: string;
requestId?: string;
clientIP?: string;
}
): Promise<T | null> {
const results = await this.executeQuery<T>(sql, params, metadata);
return results.length > 0 ? results[0] : null;
}
// 清理當前 IP 的所有連線
public async cleanupCurrentIPConnections(): Promise<{
success: boolean;
killedCount: number;
message: string;
}> {
return await ipConnectionManager.cleanupConnectionsByIP();
}
// 清理指定 IP 的所有連線
public async cleanupIPConnections(targetIP: string): Promise<{
success: boolean;
killedCount: number;
message: string;
}> {
return await ipConnectionManager.cleanupConnectionsByIP(targetIP);
}
// 獲取當前 IP 的連線狀態
public async getCurrentIPConnections() {
return await ipConnectionManager.getConnectionsByIP();
}
// 獲取指定 IP 的連線狀態
public async getIPConnections(targetIP: string) {
return await ipConnectionManager.getConnectionsByIP(targetIP);
}
// 獲取本地連線統計
public getLocalConnectionStats() {
return ipConnectionManager.getLocalConnectionStats();
}
// 清理本地追蹤的連線
public cleanupLocalConnections() {
return ipConnectionManager.cleanupLocalConnections();
}
}
// 導出單例實例
export const smartIPPool = SmartIPConnectionPool.getInstance();

208
lib/smart-ip-detector.ts Normal file
View File

@@ -0,0 +1,208 @@
// =====================================================
// 智能 IP 偵測器
// =====================================================
import { NextRequest } from 'next/server';
export interface IPDetectionResult {
detectedIP: string;
confidence: 'high' | 'medium' | 'low';
source: string;
allCandidates: string[];
isPublicIP: boolean;
}
export class SmartIPDetector {
private static instance: SmartIPDetector;
public static getInstance(): SmartIPDetector {
if (!SmartIPDetector.instance) {
SmartIPDetector.instance = new SmartIPDetector();
}
return SmartIPDetector.instance;
}
// 檢查是否為公網 IP
private isPublicIP(ip: string): boolean {
if (!ip || ip === 'unknown') return false;
// 本地地址
if (ip === '127.0.0.1' || ip === '::1' || ip === 'localhost') return false;
// 私有地址範圍
if (ip.startsWith('192.168.') ||
ip.startsWith('10.') ||
ip.startsWith('172.') ||
ip.startsWith('169.254.')) return false;
// IPv6 本地地址
if (ip.startsWith('fe80:') ||
ip.startsWith('::1') ||
ip.startsWith('::ffff:127.0.0.1')) return false;
return true;
}
// 從 x-forwarded-for 解析 IP
private parseForwardedFor(forwarded: string): string[] {
if (!forwarded) return [];
return forwarded
.split(',')
.map(ip => ip.trim())
.filter(ip => ip && ip !== 'unknown');
}
// 智能偵測客戶端 IP
public detectClientIP(request: NextRequest): IPDetectionResult {
const candidates: string[] = [];
const sources: { [key: string]: string } = {};
// 1. 收集所有可能的 IP 來源
const headers = {
'x-forwarded-for': request.headers.get('x-forwarded-for'),
'x-real-ip': request.headers.get('x-real-ip'),
'cf-connecting-ip': request.headers.get('cf-connecting-ip'),
'x-client-ip': request.headers.get('x-client-ip'),
'x-forwarded': request.headers.get('x-forwarded'),
'x-cluster-client-ip': request.headers.get('x-cluster-client-ip'),
'x-original-forwarded-for': request.headers.get('x-original-forwarded-for'),
'x-remote-addr': request.headers.get('x-remote-addr'),
'remote-addr': request.headers.get('remote-addr'),
'client-ip': request.headers.get('client-ip'),
};
// 2. 從各個標頭提取 IP
Object.entries(headers).forEach(([header, value]) => {
if (value) {
if (header === 'x-forwarded-for') {
const ips = this.parseForwardedFor(value);
ips.forEach(ip => {
candidates.push(ip);
sources[ip] = header;
});
} else {
candidates.push(value);
sources[value] = header;
}
}
});
// 3. 添加 NextRequest 的 IP (如果可用)
const nextIP = (request as any).ip;
if (nextIP) {
candidates.push(nextIP);
sources[nextIP] = 'next-request';
}
// 4. 去重並過濾
const uniqueCandidates = [...new Set(candidates)].filter(ip =>
ip && ip !== 'unknown' && ip !== '::1' && ip !== '127.0.0.1'
);
console.log('🔍 IP 偵測候選:', {
candidates: uniqueCandidates,
sources: sources,
allHeaders: Object.fromEntries(request.headers.entries())
});
// 5. 智能選擇最佳 IP
let selectedIP = 'unknown';
let confidence: 'high' | 'medium' | 'low' = 'low';
let source = 'unknown';
// 優先級 1: Cloudflare IP (最高可信度)
const cfIP = uniqueCandidates.find(ip => sources[ip] === 'cf-connecting-ip');
if (cfIP && this.isPublicIP(cfIP)) {
selectedIP = cfIP;
confidence = 'high';
source = 'cf-connecting-ip';
}
// 優先級 2: 其他代理標頭中的公網 IP
if (selectedIP === 'unknown') {
const proxyHeaders = ['x-real-ip', 'x-client-ip', 'x-cluster-client-ip'];
for (const header of proxyHeaders) {
const ip = uniqueCandidates.find(candidate => sources[candidate] === header);
if (ip && this.isPublicIP(ip)) {
selectedIP = ip;
confidence = 'high';
source = header;
break;
}
}
}
// 優先級 3: x-forwarded-for 中的第一個公網 IP
if (selectedIP === 'unknown') {
const forwardedIPs = uniqueCandidates.filter(ip => sources[ip] === 'x-forwarded-for');
const publicForwardedIP = forwardedIPs.find(ip => this.isPublicIP(ip));
if (publicForwardedIP) {
selectedIP = publicForwardedIP;
confidence = 'medium';
source = 'x-forwarded-for';
}
}
// 優先級 4: 任何公網 IP
if (selectedIP === 'unknown') {
const publicIP = uniqueCandidates.find(ip => this.isPublicIP(ip));
if (publicIP) {
selectedIP = publicIP;
confidence = 'medium';
source = sources[publicIP] || 'unknown';
}
}
// 優先級 5: 任何非本地 IP
if (selectedIP === 'unknown') {
const nonLocalIP = uniqueCandidates.find(ip =>
ip !== '::1' && ip !== '127.0.0.1' && !ip.startsWith('192.168.') &&
!ip.startsWith('10.') && !ip.startsWith('172.')
);
if (nonLocalIP) {
selectedIP = nonLocalIP;
confidence = 'low';
source = sources[nonLocalIP] || 'unknown';
}
}
// 優先級 6: 第一個候選 IP
if (selectedIP === 'unknown' && uniqueCandidates.length > 0) {
selectedIP = uniqueCandidates[0];
confidence = 'low';
source = sources[selectedIP] || 'unknown';
}
const result: IPDetectionResult = {
detectedIP: selectedIP,
confidence,
source,
allCandidates: uniqueCandidates,
isPublicIP: this.isPublicIP(selectedIP)
};
console.log('🎯 IP 偵測結果:', result);
return result;
}
// 驗證 IP 格式
public isValidIP(ip: string): boolean {
if (!ip || ip === 'unknown') return false;
// IPv4 格式檢查
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Regex.test(ip)) {
const parts = ip.split('.').map(Number);
return parts.every(part => part >= 0 && part <= 255);
}
// IPv6 格式檢查(簡化)
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
return ipv6Regex.test(ip);
}
}
// 導出單例實例
export const smartIPDetector = SmartIPDetector.getInstance();

View File

@@ -0,0 +1,186 @@
// =====================================================
// 終極連線殺手 - 強制清理所有連線
// =====================================================
import mysql from 'mysql2/promise';
export class UltimateConnectionKiller {
private static instance: UltimateConnectionKiller;
private constructor() {}
public static getInstance(): UltimateConnectionKiller {
if (!UltimateConnectionKiller.instance) {
UltimateConnectionKiller.instance = new UltimateConnectionKiller();
}
return UltimateConnectionKiller.instance;
}
// 終極清理 - 殺死所有連線
public async ultimateKill() {
console.log('💀 執行終極連線清理...');
try {
// 1. 建立一個臨時連線來執行清理
const tempConnection = await mysql.createConnection({
host: process.env.DB_HOST || '122.100.99.161',
port: parseInt(process.env.DB_PORT || '43306'),
user: process.env.DB_USER || 'A999',
password: process.env.DB_PASSWORD || '1023',
database: process.env.DB_NAME || 'db_AI_Platform',
charset: 'utf8mb4',
timezone: '+08:00',
});
try {
// 2. 獲取所有連線
const [connections] = await tempConnection.execute(`
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
FROM information_schema.PROCESSLIST
WHERE USER = ?
ORDER BY TIME DESC
`, [process.env.DB_USER || 'A999']);
console.log(`🔍 找到 ${connections.length} 個連線需要清理`);
// 3. 殺死所有連線(除了當前連線)
let killedCount = 0;
for (const conn of connections) {
if (conn.ID !== tempConnection.threadId) {
try {
await tempConnection.execute(`KILL ${conn.ID}`);
console.log(`💀 已殺死連線 ${conn.ID} (用戶: ${conn.USER}, 時間: ${conn.TIME}s)`);
killedCount++;
} catch (error: any) {
console.log(`⚠️ 無法殺死連線 ${conn.ID}: ${error.message}`);
}
}
}
console.log(`✅ 已殺死 ${killedCount} 個連線`);
// 4. 等待連線完全關閉
console.log('⏳ 等待連線完全關閉...');
await new Promise(resolve => setTimeout(resolve, 3000));
// 5. 再次檢查狀態
const [finalConnections] = await tempConnection.execute(`
SELECT COUNT(*) as count FROM information_schema.PROCESSLIST
WHERE USER = ?
`, [process.env.DB_USER || 'A999']);
const remainingConnections = finalConnections[0].count;
console.log(`📊 清理後剩餘連線: ${remainingConnections}`);
if (remainingConnections <= 1) {
console.log('🎉 連線清理成功!');
} else {
console.warn(`⚠️ 仍有 ${remainingConnections - 1} 個連線未清理`);
}
return {
success: true,
killedCount,
remainingConnections: remainingConnections - 1, // 減去當前連線
message: `已殺死 ${killedCount} 個連線,剩餘 ${remainingConnections - 1}`
};
} finally {
// 6. 關閉臨時連線
await tempConnection.end();
console.log('🔌 臨時連線已關閉');
}
} catch (error) {
console.error('❌ 終極清理失敗:', error);
return {
success: false,
error: error instanceof Error ? error.message : '未知錯誤'
};
}
}
// 檢查連線狀態
public async checkStatus() {
try {
const tempConnection = await mysql.createConnection({
host: process.env.DB_HOST || '122.100.99.161',
port: parseInt(process.env.DB_PORT || '43306'),
user: process.env.DB_USER || 'A999',
password: process.env.DB_PASSWORD || '1023',
database: process.env.DB_NAME || 'db_AI_Platform',
charset: 'utf8mb4',
timezone: '+08:00',
});
try {
// 獲取連線統計
const [statusResult] = await tempConnection.execute(`
SHOW STATUS LIKE 'Threads_connected'
`);
const [maxConnResult] = await tempConnection.execute(`
SHOW VARIABLES LIKE 'max_connections'
`);
const [connections] = await tempConnection.execute(`
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO
FROM information_schema.PROCESSLIST
WHERE USER = ?
ORDER BY TIME DESC
`, [process.env.DB_USER || 'A999']);
const currentConnections = statusResult[0]?.Value || 0;
const maxConnections = maxConnResult[0]?.Value || 100;
const usagePercentage = (currentConnections / maxConnections) * 100;
return {
currentConnections: parseInt(currentConnections),
maxConnections: parseInt(maxConnections),
usagePercentage,
connectionDetails: connections,
connectionCount: connections.length
};
} finally {
await tempConnection.end();
}
} catch (error) {
console.error('❌ 檢查狀態失敗:', error);
return null;
}
}
// 強制重啟資料庫連線
public async forceRestart() {
console.log('🔄 強制重啟資料庫連線...');
try {
// 1. 先殺死所有連線
await this.ultimateKill();
// 2. 等待一段時間
console.log('⏳ 等待系統穩定...');
await new Promise(resolve => setTimeout(resolve, 5000));
// 3. 檢查狀態
const status = await this.checkStatus();
if (status && status.connectionCount <= 1) {
console.log('✅ 資料庫連線重啟成功');
return { success: true, message: '資料庫連線重啟成功' };
} else {
console.warn('⚠️ 資料庫連線重啟後仍有連線存在');
return { success: false, message: '重啟後仍有連線存在' };
}
} catch (error) {
console.error('❌ 強制重啟失敗:', error);
return { success: false, error: error instanceof Error ? error.message : '未知錯誤' };
}
}
}
// 導出單例實例
export const ultimateKiller = UltimateConnectionKiller.getInstance();

View File

@@ -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;

View File

@@ -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();

View File

@@ -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();

93
scripts/ultimate-kill.js Normal file
View File

@@ -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);
});