修復 too many connection 問題
This commit is contained in:
208
lib/smart-ip-detector.ts
Normal file
208
lib/smart-ip-detector.ts
Normal 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();
|
Reference in New Issue
Block a user