From 8b3af6608ee5a1f5f93d298edcd1295d3c616efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Sun, 21 Sep 2025 03:08:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=81=B5=E6=B8=AC=20aws=20ec?= =?UTF-8?q?2=20=E7=9A=84=20ip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/smart-cleanup/route.ts | 59 ++++++ app/smart-cleanup-test/page.tsx | 280 ++++++++++++++++++++++++++++ app/vercel-ip-debug/page.tsx | 313 ++++++++++++++++++++++++++++++++ lib/smart-connection-cleaner.ts | 280 ++++++++++++++++++++++++++++ lib/smart-ip-detector.ts | 96 ++++++++-- 5 files changed, 1012 insertions(+), 16 deletions(-) create mode 100644 app/api/smart-cleanup/route.ts create mode 100644 app/smart-cleanup-test/page.tsx create mode 100644 app/vercel-ip-debug/page.tsx create mode 100644 lib/smart-connection-cleaner.ts diff --git a/app/api/smart-cleanup/route.ts b/app/api/smart-cleanup/route.ts new file mode 100644 index 0000000..26976fa --- /dev/null +++ b/app/api/smart-cleanup/route.ts @@ -0,0 +1,59 @@ +// ===================================================== +// 智能連線清理 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { smartConnectionCleaner } from '@/lib/smart-connection-cleaner'; + +export async function POST(request: NextRequest) { + try { + console.log('🧠 收到智能清理請求'); + + // 執行智能清理 + const result = await smartConnectionCleaner.smartCleanup(request); + + return NextResponse.json({ + success: result.success, + message: result.message, + data: { + killedCount: result.killedCount, + details: result.details, + timestamp: new Date().toISOString() + } + }); + + } 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 { + console.log('📊 收到連線統計請求'); + + // 獲取連線統計 + const stats = await smartConnectionCleaner.getConnectionStats(); + + return NextResponse.json({ + success: true, + message: '連線統計獲取成功', + data: { + stats, + timestamp: new Date().toISOString() + } + }); + + } catch (error) { + console.error('❌ 連線統計 API 錯誤:', error); + return NextResponse.json({ + success: false, + error: '獲取連線統計失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/smart-cleanup-test/page.tsx b/app/smart-cleanup-test/page.tsx new file mode 100644 index 0000000..4d63bf4 --- /dev/null +++ b/app/smart-cleanup-test/page.tsx @@ -0,0 +1,280 @@ +'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, BarChart3 } from 'lucide-react'; + +interface ConnectionStats { + total: number; + user: number; + infrastructure: number; + other: number; + details: Array<{ + id: number; + host: string; + time: number; + state: string; + type: 'user' | 'infrastructure' | 'other'; + }>; +} + +interface CleanupResult { + success: boolean; + message: string; + data: { + killedCount: number; + details: { + userRealIP?: string; + infrastructureIPs?: string[]; + cleanedConnections: Array<{ + id: number; + host: string; + time: number; + state: string; + }>; + }; + }; +} + +export default function SmartCleanupTestPage() { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + // 獲取連線統計 + const fetchStats = async () => { + try { + setLoading(true); + const response = await fetch('/api/smart-cleanup'); + const data = await response.json(); + + if (data.success) { + setStats(data.data.stats); + setMessage('連線統計更新成功'); + setError(''); + } else { + setError(data.error || '獲取連線統計失敗'); + } + } catch (err) { + setError('網路錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 執行智能清理 + const performSmartCleanup = async () => { + try { + setLoading(true); + const response = await fetch('/api/smart-cleanup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const data: CleanupResult = await response.json(); + + if (data.success) { + setMessage(`智能清理完成: ${data.message}`); + setError(''); + // 重新獲取統計 + await fetchStats(); + } else { + setError(data.error || '智能清理失敗'); + } + } catch (err) { + setError('清理錯誤: ' + (err instanceof Error ? err.message : '未知錯誤')); + } finally { + setLoading(false); + } + }; + + // 組件載入時獲取統計 + useEffect(() => { + fetchStats(); + }, []); + + // 獲取類型顏色 + const getTypeColor = (type: 'user' | 'infrastructure' | 'other') => { + switch (type) { + case 'user': return 'default'; + case 'infrastructure': return 'destructive'; + case 'other': return 'secondary'; + default: return 'outline'; + } + }; + + // 獲取類型標籤 + const getTypeLabel = (type: 'user' | 'infrastructure' | 'other') => { + switch (type) { + case 'user': return '用戶連線'; + case 'infrastructure': return '基礎設施'; + case 'other': return '其他'; + default: return '未知'; + } + }; + + return ( +
+
+
+

智能連線清理測試

+

+ 智能識別和清理用戶真實 IP 及基礎設施連線 +

+
+
+ + +
+
+ + {/* 連線統計概覽 */} + {stats && ( + + + + + 連線統計概覽 + + + 當前資料庫連線的分類統計 + + + +
+
+
{stats.total}
+
總連線數
+
+
+
{stats.user}
+
用戶連線
+
+
+
{stats.infrastructure}
+
基礎設施
+
+
+
{stats.other}
+
其他
+
+
+
+
+ )} + + {/* 連線詳情 */} + {stats && stats.details.length > 0 && ( + + + 連線詳情 + + 所有連線的詳細信息 + + + +
+ {stats.details.map((conn, index) => ( +
+
+ + {getTypeLabel(conn.type)} + + ID: {conn.id} + + {conn.host} + +
+
+ 時間: {conn.time}s + + 狀態: {conn.state} + +
+
+ ))} +
+
+
+ )} + + {/* 功能說明 */} + + + 智能清理功能 + + +
+
+

用戶連線清理

+
    +
  • • 自動識別你的真實 IP (61-227-253-171)
  • +
  • • 清理所有來自你 IP 的連線
  • +
  • • 不影響其他用戶的連線
  • +
  • • 關閉網站時自動觸發
  • +
+
+
+

基礎設施連線清理

+
    +
  • • 識別 AWS EC2 實例連線
  • +
  • • 識別 Vercel 服務器連線
  • +
  • • 可選清理基礎設施連線
  • +
  • • 保護網站正常運行
  • +
+
+
+
+
+ + {/* 使用說明 */} + + + 使用說明 + + +

刷新統計: 查看當前所有連線的分類統計

+

智能清理: 自動識別並清理你的真實 IP 連線

+

用戶連線: 來自你真實 IP 的連線,會被優先清理

+

基礎設施連線: Vercel/AWS 服務器連線,通常不清理

+

自動觸發: 關閉網站時會自動執行智能清理

+
+
+ + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} +
+ ); +} diff --git a/app/vercel-ip-debug/page.tsx b/app/vercel-ip-debug/page.tsx new file mode 100644 index 0000000..8eb70a8 --- /dev/null +++ b/app/vercel-ip-debug/page.tsx @@ -0,0 +1,313 @@ +'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, AlertTriangle, CheckCircle } from 'lucide-react'; + +interface VercelIPInfo { + smartDetection: { + detectedIP: string; + confidence: 'high' | 'medium' | 'low'; + source: string; + isPublicIP: boolean; + allCandidates: string[]; + isInfrastructureIP?: boolean; + isUserRealIP?: boolean; + infrastructureIPs?: string[]; + userRealIPs?: string[]; + }; + headers: Record; + nextRequestIP: string | undefined; + env: Record; + allHeaders: Record; +} + +export default function VercelIPDebugPage() { + const [ipInfo, setIpInfo] = useState(null); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + + // 獲取 IP 調試信息 + const fetchIPInfo = async () => { + try { + setLoading(true); + const response = await fetch('/api/debug-ip'); + const data = await response.json(); + + if (data.success) { + setIpInfo(data.data); + + if (data.data.smartDetection) { + const smartDetection = data.data.smartDetection; + setMessage(`Vercel IP 偵測結果: ${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 isInfrastructureIP = (ip: string): boolean => { + if (!ip) return false; + return ip.includes('ec2-') && ip.includes('.amazonaws.com'); + }; + + // 檢查是否為用戶真實 IP + const isUserRealIP = (ip: string): boolean => { + if (!ip || ip === 'unknown') return false; + return !isInfrastructureIP(ip) && ip !== '::1' && ip !== '127.0.0.1'; + }; + + return ( +
+
+
+

Vercel IP 調試工具

+

+ 專門用於調試 Vercel 部署環境中的 IP 偵測問題 +

+
+ +
+ + {/* 智能偵測結果 */} + {ipInfo?.smartDetection && ( + + + + + 智能 IP 偵測結果 + + + 系統智能偵測的客戶端 IP 地址(已過濾基礎設施 IP) + + + +
+ + {ipInfo.smartDetection.detectedIP} + + + 可信度: {ipInfo.smartDetection.confidence} + + + 來源: {ipInfo.smartDetection.source} + +
+ +
+
+

用戶真實 IP:

+
+ {ipInfo.smartDetection.userRealIPs?.map((ip, index) => ( + + {ip} + + )) || } +
+
+ +
+

基礎設施 IP (已過濾):

+
+ {ipInfo.smartDetection.infrastructureIPs?.map((ip, index) => ( + + {ip} + + )) || } +
+
+
+
+
+ )} + + {/* 請求標頭分析 */} + {ipInfo && ( + + + 請求標頭分析 + + 分析各種 HTTP 標頭中的 IP 信息 + + + +
+ {Object.entries(ipInfo.headers).map(([key, value]) => { + if (!value) return null; + + const isInfra = isInfrastructureIP(value); + const isUser = isUserRealIP(value); + + return ( +
+
+ {key}: +
+ + {value} + +
+ {isUser && 用戶真實 IP} + {isInfra && 基礎設施 IP} +
+
+
+
+ ); + })} +
+
+
+ )} + + {/* 環境信息 */} + {ipInfo && ( + + + 部署環境信息 + + Vercel 部署環境相關信息 + + + +
+ {Object.entries(ipInfo.env).map(([key, value]) => ( +
+ {key}: + + {value || 'undefined'} + +
+ ))} +
+
+
+ )} + + {/* 問題診斷 */} + + + + + 問題診斷 + + + + {ipInfo?.smartDetection && ( + <> + {isInfrastructureIP(ipInfo.smartDetection.detectedIP) && ( + + + + 問題: 偵測到的是 Vercel/AWS 基礎設施 IP,不是用戶真實 IP。 +
+ 解決方案: 檢查 x-forwarded-for 標頭中是否包含用戶真實 IP。 +
+
+ )} + + {isUserRealIP(ipInfo.smartDetection.detectedIP) && ( + + + + 正常: 成功偵測到用戶真實 IP。 + + + )} + + {!isUserRealIP(ipInfo.smartDetection.detectedIP) && !isInfrastructureIP(ipInfo.smartDetection.detectedIP) && ( + + + + 問題: 無法偵測到有效的用戶 IP。 +
+ 可能原因: 代理配置問題或網路環境限制。 +
+
+ )} + + )} +
+
+ + {/* 使用說明 */} + + + 使用說明 + + +

基礎設施 IP: Vercel/AWS 的服務器 IP,不是用戶真實 IP

+

用戶真實 IP: 實際訪問網站的用戶 IP 地址

+

x-forwarded-for: 通常包含用戶真實 IP,格式為 "用戶IP,代理IP"

+

問題排查: 如果偵測到基礎設施 IP,檢查代理配置

+
+
+ + {/* 訊息顯示 */} + {message && ( + + + {message} + + )} + + {error && ( + + + {error} + + )} +
+ ); +} diff --git a/lib/smart-connection-cleaner.ts b/lib/smart-connection-cleaner.ts new file mode 100644 index 0000000..3eb377e --- /dev/null +++ b/lib/smart-connection-cleaner.ts @@ -0,0 +1,280 @@ +// ===================================================== +// 智能連線清理器 +// ===================================================== + +import mysql from 'mysql2/promise'; +import { db } from './database'; +import { smartIPDetector } from './smart-ip-detector'; +import { NextRequest } from 'next/server'; + +export interface CleanupResult { + success: boolean; + killedCount: number; + message: string; + details: { + userRealIP?: string; + infrastructureIPs?: string[]; + cleanedConnections: Array<{ + id: number; + host: string; + time: number; + state: string; + }>; + }; +} + +export class SmartConnectionCleaner { + private static instance: SmartConnectionCleaner; + + public static getInstance(): SmartConnectionCleaner { + if (!SmartConnectionCleaner.instance) { + SmartConnectionCleaner.instance = new SmartConnectionCleaner(); + } + return SmartConnectionCleaner.instance; + } + + // 智能清理連線 + public async smartCleanup(request: NextRequest): Promise { + try { + // 1. 使用智能 IP 偵測器獲取真實 IP + const ipDetection = smartIPDetector.detectClientIP(request); + const userRealIP = ipDetection.detectedIP; + + console.log('🧠 智能清理開始:', { + detectedIP: userRealIP, + confidence: ipDetection.confidence, + source: ipDetection.source, + isUserRealIP: ipDetection.isUserRealIP, + infrastructureIPs: ipDetection.infrastructureIPs + }); + + // 2. 建立資料庫連線 + const connection = await db.getConnection(); + let killedCount = 0; + const cleanedConnections: Array<{ + id: number; + host: string; + time: number; + state: string; + }> = []; + + try { + // 3. 獲取所有相關連線 + const [allConnections] = await connection.execute(` + SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO + FROM information_schema.PROCESSLIST + WHERE USER = ? + ORDER BY TIME DESC + `, [process.env.DB_USER || 'A999']); + + console.log(`🔍 找到 ${allConnections.length} 個總連線`); + + // 4. 分類連線 + const userConnections = allConnections.filter((conn: any) => + this.isUserConnection(conn.HOST, userRealIP) + ); + + const infrastructureConnections = allConnections.filter((conn: any) => + this.isInfrastructureConnection(conn.HOST) + ); + + console.log(`👤 用戶連線: ${userConnections.length} 個`); + console.log(`🏗️ 基礎設施連線: ${infrastructureConnections.length} 個`); + + // 5. 清理用戶連線(優先) + if (userConnections.length > 0) { + console.log(`🧹 開始清理用戶連線 (IP: ${userRealIP})`); + + for (const conn of userConnections) { + if (conn.ID !== connection.threadId) { + try { + await connection.execute(`KILL ${conn.ID}`); + console.log(`💀 已殺死用戶連線 ${conn.ID} (HOST: ${conn.HOST})`); + killedCount++; + cleanedConnections.push({ + id: conn.ID, + host: conn.HOST, + time: conn.TIME, + state: conn.STATE || 'Sleep' + }); + } catch (error: any) { + console.log(`⚠️ 無法殺死用戶連線 ${conn.ID}: ${error.message}`); + } + } + } + } + + // 6. 清理基礎設施連線(可選,根據配置) + const shouldCleanInfrastructure = process.env.CLEAN_INFRASTRUCTURE_CONNECTIONS === 'true'; + + if (shouldCleanInfrastructure && infrastructureConnections.length > 0) { + console.log(`🧹 開始清理基礎設施連線`); + + for (const conn of infrastructureConnections) { + if (conn.ID !== connection.threadId) { + try { + await connection.execute(`KILL ${conn.ID}`); + console.log(`💀 已殺死基礎設施連線 ${conn.ID} (HOST: ${conn.HOST})`); + killedCount++; + cleanedConnections.push({ + id: conn.ID, + host: conn.HOST, + time: conn.TIME, + state: conn.STATE || 'Sleep' + }); + } catch (error: any) { + console.log(`⚠️ 無法殺死基礎設施連線 ${conn.ID}: ${error.message}`); + } + } + } + } + + console.log(`✅ 智能清理完成: 共清理 ${killedCount} 個連線`); + + return { + success: true, + killedCount, + message: `智能清理完成: 共清理 ${killedCount} 個連線`, + details: { + userRealIP: userRealIP !== 'unknown' ? userRealIP : undefined, + infrastructureIPs: ipDetection.infrastructureIPs, + cleanedConnections + } + }; + + } finally { + connection.release(); + } + + } catch (error) { + console.error('❌ 智能清理失敗:', error); + return { + success: false, + killedCount: 0, + message: `智能清理失敗: ${error instanceof Error ? error.message : '未知錯誤'}`, + details: { + cleanedConnections: [] + } + }; + } + } + + // 檢查是否為用戶連線 + private isUserConnection(host: string, userIP: string): boolean { + if (!host || !userIP || userIP === 'unknown') return false; + + // 直接匹配用戶 IP + if (host.includes(userIP)) return true; + + // 匹配用戶 IP 的變體格式 + const ipVariants = [ + userIP, + userIP.replace(/\./g, '-'), // 61.227.253.171 -> 61-227-253-171 + userIP.replace(/\./g, '_'), // 61.227.253.171 -> 61_227_253_171 + ]; + + return ipVariants.some(variant => host.includes(variant)); + } + + // 檢查是否為基礎設施連線 + private isInfrastructureConnection(host: string): boolean { + if (!host) return false; + + // AWS EC2 實例 + if (host.includes('ec2-') && host.includes('.amazonaws.com')) return true; + + // 其他基礎設施模式 + const infrastructurePatterns = [ + '.amazonaws.com', + '.vercel.app', + 'vercel', + 'cloudflare', + 'fastly.com', + 'cloudfront.net' + ]; + + return infrastructurePatterns.some(pattern => host.includes(pattern)); + } + + // 獲取連線統計 + public async getConnectionStats(): Promise<{ + total: number; + user: number; + infrastructure: number; + other: number; + details: Array<{ + id: number; + host: string; + time: number; + state: string; + type: 'user' | 'infrastructure' | 'other'; + }>; + }> { + try { + const connection = await db.getConnection(); + + try { + const [connections] = await connection.execute(` + SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO + FROM information_schema.PROCESSLIST + WHERE USER = ? + ORDER BY TIME DESC + `, [process.env.DB_USER || 'A999']); + + const stats = { + total: connections.length, + user: 0, + infrastructure: 0, + other: 0, + details: [] as Array<{ + id: number; + host: string; + time: number; + state: string; + type: 'user' | 'infrastructure' | 'other'; + }> + }; + + for (const conn of connections) { + let type: 'user' | 'infrastructure' | 'other' = 'other'; + + if (this.isInfrastructureConnection(conn.HOST)) { + type = 'infrastructure'; + stats.infrastructure++; + } else if (this.isUserConnection(conn.HOST, 'unknown')) { + type = 'user'; + stats.user++; + } else { + stats.other++; + } + + stats.details.push({ + id: conn.ID, + host: conn.HOST, + time: conn.TIME, + state: conn.STATE || 'Sleep', + type + }); + } + + return stats; + + } finally { + connection.release(); + } + + } catch (error) { + console.error('❌ 獲取連線統計失敗:', error); + return { + total: 0, + user: 0, + infrastructure: 0, + other: 0, + details: [] + }; + } + } +} + +// 導出單例實例 +export const smartConnectionCleaner = SmartConnectionCleaner.getInstance(); diff --git a/lib/smart-ip-detector.ts b/lib/smart-ip-detector.ts index c85fbba..c34bdc9 100644 --- a/lib/smart-ip-detector.ts +++ b/lib/smart-ip-detector.ts @@ -43,6 +43,51 @@ export class SmartIPDetector { return true; } + // 檢查是否為基礎設施 IP(Vercel、AWS、Cloudflare 等) + private isInfrastructureIP(ip: string): boolean { + if (!ip) return false; + + // Vercel/AWS EC2 實例 + if (ip.includes('ec2-') && ip.includes('.compute-1.amazonaws.com')) return true; + if (ip.includes('ec2-') && ip.includes('.amazonaws.com')) return true; + + // AWS 其他服務 + if (ip.includes('.amazonaws.com')) return true; + + // Vercel 相關 + if (ip.includes('vercel')) return true; + + // Cloudflare 相關(但 cf-connecting-ip 是我們想要的) + if (ip.includes('cloudflare') && !ip.includes('cf-connecting-ip')) return true; + + // 其他常見的 CDN/代理服務 + const infrastructurePatterns = [ + 'fastly.com', + 'cloudfront.net', + 'akamai.net', + 'maxcdn.com', + 'keycdn.com' + ]; + + return infrastructurePatterns.some(pattern => ip.includes(pattern)); + } + + // 檢查是否為用戶真實 IP + private isUserRealIP(ip: string): boolean { + if (!ip || ip === 'unknown') return false; + + // 不是基礎設施 IP + if (this.isInfrastructureIP(ip)) return false; + + // 是公網 IP + if (!this.isPublicIP(ip)) return false; + + // 是有效的 IP 格式 + if (!this.isValidIP(ip)) return false; + + return true; + } + // 從 x-forwarded-for 解析 IP private parseForwardedFor(forwarded: string): string[] { if (!forwarded) return []; @@ -111,20 +156,20 @@ export class SmartIPDetector { let confidence: 'high' | 'medium' | 'low' = 'low'; let source = 'unknown'; - // 優先級 1: Cloudflare IP (最高可信度) + // 優先級 1: Cloudflare IP (最高可信度) - 但要是用戶真實 IP const cfIP = uniqueCandidates.find(ip => sources[ip] === 'cf-connecting-ip'); - if (cfIP && this.isPublicIP(cfIP)) { + if (cfIP && this.isUserRealIP(cfIP)) { selectedIP = cfIP; confidence = 'high'; source = 'cf-connecting-ip'; } - // 優先級 2: 其他代理標頭中的公網 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)) { + if (ip && this.isUserRealIP(ip)) { selectedIP = ip; confidence = 'high'; source = header; @@ -133,32 +178,45 @@ export class SmartIPDetector { } } - // 優先級 3: x-forwarded-for 中的第一個公網 IP + // 優先級 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; + const userRealIP = forwardedIPs.find(ip => this.isUserRealIP(ip)); + if (userRealIP) { + selectedIP = userRealIP; confidence = 'medium'; source = 'x-forwarded-for'; } } - // 優先級 4: 任何公網 IP + // 優先級 4: 任何用戶真實 IP if (selectedIP === 'unknown') { - const publicIP = uniqueCandidates.find(ip => this.isPublicIP(ip)); + const userRealIP = uniqueCandidates.find(ip => this.isUserRealIP(ip)); + if (userRealIP) { + selectedIP = userRealIP; + confidence = 'medium'; + source = sources[userRealIP] || 'unknown'; + } + } + + // 優先級 5: 任何公網 IP(非基礎設施) + if (selectedIP === 'unknown') { + const publicIP = uniqueCandidates.find(ip => + this.isPublicIP(ip) && !this.isInfrastructureIP(ip) + ); if (publicIP) { selectedIP = publicIP; - confidence = 'medium'; + confidence = 'low'; source = sources[publicIP] || 'unknown'; } } - // 優先級 5: 任何非本地 IP + // 優先級 6: 任何非本地 IP(非基礎設施) if (selectedIP === 'unknown') { const nonLocalIP = uniqueCandidates.find(ip => - ip !== '::1' && ip !== '127.0.0.1' && !ip.startsWith('192.168.') && - !ip.startsWith('10.') && !ip.startsWith('172.') + ip !== '::1' && ip !== '127.0.0.1' && + !ip.startsWith('192.168.') && !ip.startsWith('10.') && !ip.startsWith('172.') && + !this.isInfrastructureIP(ip) ); if (nonLocalIP) { selectedIP = nonLocalIP; @@ -167,7 +225,7 @@ export class SmartIPDetector { } } - // 優先級 6: 第一個候選 IP + // 優先級 7: 第一個候選 IP(最後選擇) if (selectedIP === 'unknown' && uniqueCandidates.length > 0) { selectedIP = uniqueCandidates[0]; confidence = 'low'; @@ -182,7 +240,13 @@ export class SmartIPDetector { isPublicIP: this.isPublicIP(selectedIP) }; - console.log('🎯 IP 偵測結果:', result); + console.log('🎯 IP 偵測結果:', { + ...result, + isInfrastructureIP: this.isInfrastructureIP(selectedIP), + isUserRealIP: this.isUserRealIP(selectedIP), + infrastructureIPs: uniqueCandidates.filter(ip => this.isInfrastructureIP(ip)), + userRealIPs: uniqueCandidates.filter(ip => this.isUserRealIP(ip)) + }); return result; }