// ===================================================== // 智能 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; } // 檢查是否為基礎設施 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 []; 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 (最高可信度) - 但要是用戶真實 IP const cfIP = uniqueCandidates.find(ip => sources[ip] === 'cf-connecting-ip'); if (cfIP && this.isUserRealIP(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.isUserRealIP(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 userRealIP = forwardedIPs.find(ip => this.isUserRealIP(ip)); if (userRealIP) { selectedIP = userRealIP; confidence = 'medium'; source = 'x-forwarded-for'; } } // 優先級 4: 任何用戶真實 IP if (selectedIP === 'unknown') { 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 = 'low'; source = sources[publicIP] || 'unknown'; } } // 優先級 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.') && !this.isInfrastructureIP(ip) ); if (nonLocalIP) { selectedIP = nonLocalIP; confidence = 'low'; source = sources[nonLocalIP] || 'unknown'; } } // 優先級 7: 第一個候選 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, isInfrastructureIP: this.isInfrastructureIP(selectedIP), isUserRealIP: this.isUserRealIP(selectedIP), infrastructureIPs: uniqueCandidates.filter(ip => this.isInfrastructureIP(ip)), userRealIPs: uniqueCandidates.filter(ip => this.isUserRealIP(ip)) }); 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();