修改成 cloudflare 的 ip 形式
This commit is contained in:
@@ -1,11 +1,10 @@
|
|||||||
'use client'
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useEffect, useState } from "react"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
import { Button } from "@/components/ui/button"
|
||||||
import { Button } from '@/components/ui/button'
|
import { RefreshCw, Shield, AlertTriangle, CheckCircle } from "lucide-react"
|
||||||
import { Globe, Info, AlertCircle, CheckCircle, RefreshCw } from 'lucide-react'
|
|
||||||
|
|
||||||
interface IpDebugInfo {
|
interface IpDebugInfo {
|
||||||
ip: string
|
ip: string
|
||||||
@@ -13,20 +12,26 @@ interface IpDebugInfo {
|
|||||||
enableIpWhitelist: boolean
|
enableIpWhitelist: boolean
|
||||||
allowedIps: string[]
|
allowedIps: string[]
|
||||||
timestamp: string
|
timestamp: string
|
||||||
debug: {
|
debug?: {
|
||||||
allIpSources: Record<string, string | null>
|
allIpSources?: Record<string, string | null>
|
||||||
allFoundIps: string[]
|
allFoundIps?: string[]
|
||||||
isLocalDevelopment: boolean
|
isLocalDevelopment?: boolean
|
||||||
localIp: string | null
|
localIp?: string | null
|
||||||
environment: string
|
environment?: string
|
||||||
host: string | null
|
host?: string
|
||||||
referer: string | null
|
referer?: string
|
||||||
userAgent: string | null
|
userAgent?: string
|
||||||
originalDetectedIp: string
|
originalDetectedIp?: string
|
||||||
finalDetectedIp: string
|
finalDetectedIp?: string
|
||||||
|
rawDetectedIp?: string
|
||||||
|
ipDetectionMethod?: string
|
||||||
|
}
|
||||||
|
ipv6Info?: {
|
||||||
|
isIPv6Mapped: boolean
|
||||||
|
originalFormat: string
|
||||||
|
ipv6Format: string
|
||||||
|
hasIPv6Support: boolean
|
||||||
}
|
}
|
||||||
location: any
|
|
||||||
development: any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function IpDebugPage() {
|
export default function IpDebugPage() {
|
||||||
@@ -45,8 +50,8 @@ export default function IpDebugPage() {
|
|||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
setIpInfo(data)
|
setIpInfo(data)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching IP info:', error)
|
console.error("無法獲取IP信息:", error)
|
||||||
setError('無法獲取IP信息')
|
setError("無法獲取IP信息")
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -58,10 +63,12 @@ export default function IpDebugPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-6 max-w-6xl">
|
<div className="container mx-auto p-6">
|
||||||
<div className="text-center">
|
<div className="flex items-center justify-center min-h-[400px]">
|
||||||
<RefreshCw className="w-8 h-8 animate-spin mx-auto mb-4" />
|
<div className="flex items-center gap-2">
|
||||||
<p>載入中...</p>
|
<RefreshCw className="w-4 h-4 animate-spin" />
|
||||||
|
<span>載入中...</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -69,199 +76,226 @@ export default function IpDebugPage() {
|
|||||||
|
|
||||||
if (error || !ipInfo) {
|
if (error || !ipInfo) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-6 max-w-6xl">
|
<div className="container mx-auto p-6">
|
||||||
<Alert variant="destructive">
|
<Card className="border-red-200 bg-red-50">
|
||||||
<AlertCircle className="h-4 w-4" />
|
<CardHeader>
|
||||||
<AlertDescription>{error}</AlertDescription>
|
<CardTitle className="flex items-center gap-2 text-red-600">
|
||||||
</Alert>
|
<AlertTriangle className="w-5 h-5" />
|
||||||
|
錯誤
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-red-600">{error}</p>
|
||||||
|
<Button onClick={fetchIpInfo} className="mt-4">
|
||||||
|
<RefreshCw className="w-4 h-4 mr-2" />
|
||||||
|
重試
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-6 max-w-6xl space-y-6">
|
<div className="container mx-auto p-6 space-y-6">
|
||||||
<div className="text-center">
|
<div className="flex items-center justify-between">
|
||||||
<h1 className="text-3xl font-bold mb-2">IP 調試信息</h1>
|
<h1 className="text-3xl font-bold">IP 檢測調試</h1>
|
||||||
<p className="text-muted-foreground">詳細的IP檢測和調試信息</p>
|
<Button onClick={fetchIpInfo} variant="outline">
|
||||||
|
<RefreshCw className="w-4 h-4 mr-2" />
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 主要IP信息 */}
|
{/* 主要IP信息 */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Globe className="w-5 h-5" />
|
{ipInfo.enableIpWhitelist && !ipInfo.isAllowed ? (
|
||||||
檢測到的IP地址
|
<>
|
||||||
|
<AlertTriangle className="w-5 h-5 text-red-500" />
|
||||||
|
IP 被拒絕
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||||
|
IP 檢測正常
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
|
||||||
系統檢測到的主要IP地址信息
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">最終檢測到的IP</h4>
|
<label className="text-sm font-medium text-gray-500">檢測到的IP</label>
|
||||||
<Badge variant="default" className="text-sm">
|
<p className="text-lg font-mono bg-gray-100 p-2 rounded">
|
||||||
{ipInfo.ip}
|
{ipInfo.ip}
|
||||||
</Badge>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">原始檢測到的IP</h4>
|
<label className="text-sm font-medium text-gray-500">狀態</label>
|
||||||
<Badge variant="outline" className="text-sm">
|
<div className="mt-1">
|
||||||
{ipInfo.debug.originalDetectedIp}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium mb-2">IP白名單狀態</h4>
|
|
||||||
<Badge variant={ipInfo.isAllowed ? "default" : "destructive"}>
|
<Badge variant={ipInfo.isAllowed ? "default" : "destructive"}>
|
||||||
{ipInfo.isAllowed ? '允許' : '拒絕'}
|
{ipInfo.isAllowed ? "允許" : "拒絕"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ipInfo.enableIpWhitelist && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">白名單功能</h4>
|
<label className="text-sm font-medium text-gray-500">白名單狀態</label>
|
||||||
<Badge variant={ipInfo.enableIpWhitelist ? "default" : "secondary"}>
|
<div className="flex items-center gap-2 mt-1">
|
||||||
{ipInfo.enableIpWhitelist ? '已啟用' : '已禁用'}
|
<Shield className="w-4 h-4 text-blue-500" />
|
||||||
</Badge>
|
<span className="text-sm">已啟用</span>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium mb-2">環境</h4>
|
|
||||||
<Badge variant="outline">
|
|
||||||
{ipInfo.debug.environment}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 所有找到的IP */}
|
{/* 詳細調試信息 */}
|
||||||
|
{ipInfo.debug && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>所有檢測到的IP地址</CardTitle>
|
<CardTitle>詳細調試信息</CardTitle>
|
||||||
<CardDescription>
|
|
||||||
從各種來源檢測到的所有IP地址
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
|
||||||
{ipInfo.debug.allFoundIps.length > 0 ? (
|
|
||||||
ipInfo.debug.allFoundIps.map((ip, index) => (
|
|
||||||
<div key={index} className="flex items-center gap-2">
|
|
||||||
<Badge variant="outline" className="font-mono">
|
|
||||||
{ip}
|
|
||||||
</Badge>
|
|
||||||
{ip === ipInfo.ip && (
|
|
||||||
<Badge variant="default" className="text-xs">
|
|
||||||
最終選擇
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<p className="text-muted-foreground">沒有檢測到任何IP地址</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* IP來源詳細信息 */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>IP來源詳細信息</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
各種HTTP頭和連接信息中的IP地址
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{Object.entries(ipInfo.debug.allIpSources).map(([source, value]) => (
|
|
||||||
<div key={source} className="flex items-center justify-between p-2 bg-muted rounded">
|
|
||||||
<span className="font-medium text-sm">{source}:</span>
|
|
||||||
<span className="font-mono text-sm">
|
|
||||||
{value || '未設置'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 本地開發環境信息 */}
|
|
||||||
{ipInfo.development && (
|
|
||||||
<Alert>
|
|
||||||
<Info className="h-4 w-4" />
|
|
||||||
<AlertDescription>
|
|
||||||
<div className="font-medium mb-2">{ipInfo.development.message}</div>
|
|
||||||
<div className="text-sm space-y-1">
|
|
||||||
{ipInfo.development.suggestions.map((suggestion, index) => (
|
|
||||||
<div key={index}>• {suggestion}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 地理位置信息 */}
|
|
||||||
{ipInfo.location && (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>地理位置信息</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
IP地址的地理位置信息
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<pre className="bg-muted p-4 rounded text-sm overflow-auto">
|
|
||||||
{JSON.stringify(ipInfo.location, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 其他調試信息 */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>其他調試信息</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
額外的調試和環境信息
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">主機</h4>
|
<label className="text-sm font-medium text-gray-500">環境</label>
|
||||||
<p className="text-sm font-mono">{ipInfo.debug.host || '未設置'}</p>
|
<p className="text-sm bg-gray-100 p-2 rounded">
|
||||||
|
{ipInfo.debug.environment || '未知'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">引用來源</h4>
|
<label className="text-sm font-medium text-gray-500">檢測方法</label>
|
||||||
<p className="text-sm font-mono">{ipInfo.debug.referer || '未設置'}</p>
|
<p className="text-sm bg-gray-100 p-2 rounded">
|
||||||
|
{ipInfo.debug.ipDetectionMethod || '未知'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{ipInfo.debug.allFoundIps && ipInfo.debug.allFoundIps.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-gray-500">所有檢測到的IP</label>
|
||||||
|
<div className="mt-1 space-y-1">
|
||||||
|
{ipInfo.debug.allFoundIps.map((ip, index) => (
|
||||||
|
<div key={index} className="text-sm bg-gray-100 p-2 rounded font-mono">
|
||||||
|
{ip}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ipInfo.debug.allIpSources && (
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-gray-500">所有IP來源</label>
|
||||||
|
<div className="mt-1 space-y-2 max-h-60 overflow-y-auto">
|
||||||
|
{Object.entries(ipInfo.debug.allIpSources).map(([key, value]) => (
|
||||||
|
<div key={key} className="text-sm bg-gray-100 p-2 rounded">
|
||||||
|
<span className="font-medium">{key}:</span>
|
||||||
|
<span className="ml-2 font-mono">{value || 'null'}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* IPv6信息 */}
|
||||||
|
{ipInfo.ipv6Info && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>IPv6 信息</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-gray-500">IPv6映射</label>
|
||||||
|
<Badge variant={ipInfo.ipv6Info.isIPv6Mapped ? "default" : "secondary"}>
|
||||||
|
{ipInfo.ipv6Info.isIPv6Mapped ? "是" : "否"}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">用戶代理</h4>
|
<label className="text-sm font-medium text-gray-500">IPv6支援</label>
|
||||||
<p className="text-sm font-mono break-all">{ipInfo.debug.userAgent || '未設置'}</p>
|
<Badge variant={ipInfo.ipv6Info.hasIPv6Support ? "default" : "secondary"}>
|
||||||
|
{ipInfo.ipv6Info.hasIPv6Support ? "已啟用" : "未啟用"}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{ipInfo.ipv6Info.isIPv6Mapped && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-2">時間戳</h4>
|
<label className="text-sm font-medium text-gray-500">IPv6格式</label>
|
||||||
<p className="text-sm">{ipInfo.timestamp}</p>
|
<p className="text-sm bg-gray-100 p-2 rounded font-mono">
|
||||||
|
{ipInfo.ipv6Info.ipv6Format}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 允許的IP列表 */}
|
||||||
|
{ipInfo.enableIpWhitelist && ipInfo.allowedIps.length > 0 && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>允許的IP列表</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||||
|
{ipInfo.allowedIps.map((ip, index) => (
|
||||||
|
<div key={index} className="flex items-center gap-2">
|
||||||
|
<Badge variant={ip === ipInfo.ip ? "default" : "outline"}>
|
||||||
|
{ip === ipInfo.ip ? "當前" : ""}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-sm font-mono">{ip}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 操作按鈕 */}
|
{/* 建議 */}
|
||||||
<div className="flex justify-center gap-4">
|
<Card>
|
||||||
<Button onClick={fetchIpInfo} className="flex items-center gap-2">
|
<CardHeader>
|
||||||
<RefreshCw className="w-4 h-4" />
|
<CardTitle>建議</CardTitle>
|
||||||
重新檢測
|
</CardHeader>
|
||||||
</Button>
|
<CardContent className="space-y-2">
|
||||||
<Button variant="outline" onClick={() => window.open('/api/ip', '_blank')}>
|
{!ipInfo.enableIpWhitelist && (
|
||||||
<Globe className="w-4 h-4 mr-2" />
|
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded">
|
||||||
查看API響應
|
<p className="text-sm text-yellow-800">
|
||||||
</Button>
|
⚠️ IP白名單功能未啟用。如果您的IP檢測正常,建議啟用白名單功能以提高安全性。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ipInfo.enableIpWhitelist && !ipInfo.isAllowed && (
|
||||||
|
<div className="p-3 bg-red-50 border border-red-200 rounded">
|
||||||
|
<p className="text-sm text-red-800">
|
||||||
|
❌ 您的IP不在允許列表中。請聯繫管理員將您的IP添加到白名單。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ipInfo.ip.startsWith('172.70.') && (
|
||||||
|
<div className="p-3 bg-blue-50 border border-blue-200 rounded">
|
||||||
|
<p className="text-sm text-blue-800">
|
||||||
|
💡 檢測到Cloudflare代理IP。系統已優化處理此類代理IP,會嘗試獲取您的真實IP。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="p-3 bg-green-50 border border-green-200 rounded">
|
||||||
|
<p className="text-sm text-green-800">
|
||||||
|
✅ 如果IP檢測仍有問題,請檢查1Panel的反向代理配置,確保正確轉發客戶端IP頭部。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
219
lib/ip-utils.ts
219
lib/ip-utils.ts
@@ -83,23 +83,27 @@ function cleanIpAddress(ip: string): string | null {
|
|||||||
export function getClientIp(req: any): string {
|
export function getClientIp(req: any): string {
|
||||||
// 按優先順序檢查各種IP來源
|
// 按優先順序檢查各種IP來源
|
||||||
const ipSources = [
|
const ipSources = [
|
||||||
// 代理伺服器轉發的IP
|
// 1Panel 和常見代理伺服器轉發的IP (優先級最高)
|
||||||
req.headers['x-forwarded-for'],
|
req.headers['x-forwarded-for'],
|
||||||
req.headers['x-real-ip'],
|
req.headers['x-real-ip'],
|
||||||
req.headers['x-client-ip'],
|
req.headers['x-client-ip'],
|
||||||
req.headers['cf-connecting-ip'], // Cloudflare
|
req.headers['cf-connecting-ip'], // Cloudflare
|
||||||
|
req.headers['x-original-forwarded-for'], // 某些代理伺服器
|
||||||
|
req.headers['x-cluster-client-ip'], // 集群環境
|
||||||
|
|
||||||
|
// 其他代理頭
|
||||||
req.headers['x-forwarded'], // 舊版代理頭
|
req.headers['x-forwarded'], // 舊版代理頭
|
||||||
req.headers['forwarded-for'],
|
req.headers['forwarded-for'],
|
||||||
req.headers['forwarded'],
|
req.headers['forwarded'],
|
||||||
|
|
||||||
|
// 1Panel 特殊頭部
|
||||||
|
req.headers['x-1panel-client-ip'], // 1Panel 可能使用的頭部
|
||||||
|
req.headers['x-nginx-proxy-real-ip'], // Nginx 代理
|
||||||
|
|
||||||
// 直接連接的IP
|
// 直接連接的IP
|
||||||
req.connection?.remoteAddress,
|
req.connection?.remoteAddress,
|
||||||
req.socket?.remoteAddress,
|
req.socket?.remoteAddress,
|
||||||
req.ip,
|
req.ip,
|
||||||
|
|
||||||
// Next.js 特定的IP來源
|
|
||||||
req.headers['x-original-forwarded-for'],
|
|
||||||
req.headers['x-cluster-client-ip'],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 收集所有找到的IP用於調試
|
// 收集所有找到的IP用於調試
|
||||||
@@ -115,8 +119,8 @@ export function getClientIp(req: any): string {
|
|||||||
const cleanIp = cleanIpAddress(ip);
|
const cleanIp = cleanIpAddress(ip);
|
||||||
if (cleanIp) {
|
if (cleanIp) {
|
||||||
foundIps.push(cleanIp);
|
foundIps.push(cleanIp);
|
||||||
// 檢查是否為公網IP
|
// 檢查是否為公網IP,排除內部IP和1Panel代理IP
|
||||||
if (isPublicIp(cleanIp)) {
|
if (isPublicIp(cleanIp) && !isInternalProxyIp(cleanIp)) {
|
||||||
return cleanIp;
|
return cleanIp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,6 +191,207 @@ function isPublicIp(ip: string): boolean {
|
|||||||
return !privateRanges.some(range => range.test(ip));
|
return !privateRanges.some(range => range.test(ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 檢查是否為內部代理IP (如1Panel、Cloudflare等)
|
||||||
|
function isInternalProxyIp(ip: string): boolean {
|
||||||
|
// 1Panel 和常見代理服務器的內部IP範圍
|
||||||
|
const proxyRanges = [
|
||||||
|
/^172\.70\./, // Cloudflare 內部IP (包含你遇到的 172.70.214.246)
|
||||||
|
/^172\.71\./, // Cloudflare 內部IP
|
||||||
|
/^172\.64\./, // Cloudflare 內部IP
|
||||||
|
/^172\.65\./, // Cloudflare 內部IP
|
||||||
|
/^172\.66\./, // Cloudflare 內部IP
|
||||||
|
/^172\.67\./, // Cloudflare 內部IP
|
||||||
|
/^172\.68\./, // Cloudflare 內部IP
|
||||||
|
/^172\.69\./, // Cloudflare 內部IP
|
||||||
|
/^172\.72\./, // Cloudflare 內部IP
|
||||||
|
/^172\.73\./, // Cloudflare 內部IP
|
||||||
|
/^172\.74\./, // Cloudflare 內部IP
|
||||||
|
/^172\.75\./, // Cloudflare 內部IP
|
||||||
|
/^172\.76\./, // Cloudflare 內部IP
|
||||||
|
/^172\.77\./, // Cloudflare 內部IP
|
||||||
|
/^172\.78\./, // Cloudflare 內部IP
|
||||||
|
/^172\.79\./, // Cloudflare 內部IP
|
||||||
|
/^172\.80\./, // Cloudflare 內部IP
|
||||||
|
/^172\.81\./, // Cloudflare 內部IP
|
||||||
|
/^172\.82\./, // Cloudflare 內部IP
|
||||||
|
/^172\.83\./, // Cloudflare 內部IP
|
||||||
|
/^172\.84\./, // Cloudflare 內部IP
|
||||||
|
/^172\.85\./, // Cloudflare 內部IP
|
||||||
|
/^172\.86\./, // Cloudflare 內部IP
|
||||||
|
/^172\.87\./, // Cloudflare 內部IP
|
||||||
|
/^172\.88\./, // Cloudflare 內部IP
|
||||||
|
/^172\.89\./, // Cloudflare 內部IP
|
||||||
|
/^172\.90\./, // Cloudflare 內部IP
|
||||||
|
/^172\.91\./, // Cloudflare 內部IP
|
||||||
|
/^172\.92\./, // Cloudflare 內部IP
|
||||||
|
/^172\.93\./, // Cloudflare 內部IP
|
||||||
|
/^172\.94\./, // Cloudflare 內部IP
|
||||||
|
/^172\.95\./, // Cloudflare 內部IP
|
||||||
|
/^172\.96\./, // Cloudflare 內部IP
|
||||||
|
/^172\.97\./, // Cloudflare 內部IP
|
||||||
|
/^172\.98\./, // Cloudflare 內部IP
|
||||||
|
/^172\.99\./, // Cloudflare 內部IP
|
||||||
|
/^172\.100\./, // Cloudflare 內部IP
|
||||||
|
/^172\.101\./, // Cloudflare 內部IP
|
||||||
|
/^172\.102\./, // Cloudflare 內部IP
|
||||||
|
/^172\.103\./, // Cloudflare 內部IP
|
||||||
|
/^172\.104\./, // Cloudflare 內部IP
|
||||||
|
/^172\.105\./, // Cloudflare 內部IP
|
||||||
|
/^172\.106\./, // Cloudflare 內部IP
|
||||||
|
/^172\.107\./, // Cloudflare 內部IP
|
||||||
|
/^172\.108\./, // Cloudflare 內部IP
|
||||||
|
/^172\.109\./, // Cloudflare 內部IP
|
||||||
|
/^172\.110\./, // Cloudflare 內部IP
|
||||||
|
/^172\.111\./, // Cloudflare 內部IP
|
||||||
|
/^172\.112\./, // Cloudflare 內部IP
|
||||||
|
/^172\.113\./, // Cloudflare 內部IP
|
||||||
|
/^172\.114\./, // Cloudflare 內部IP
|
||||||
|
/^172\.115\./, // Cloudflare 內部IP
|
||||||
|
/^172\.116\./, // Cloudflare 內部IP
|
||||||
|
/^172\.117\./, // Cloudflare 內部IP
|
||||||
|
/^172\.118\./, // Cloudflare 內部IP
|
||||||
|
/^172\.119\./, // Cloudflare 內部IP
|
||||||
|
/^172\.120\./, // Cloudflare 內部IP
|
||||||
|
/^172\.121\./, // Cloudflare 內部IP
|
||||||
|
/^172\.122\./, // Cloudflare 內部IP
|
||||||
|
/^172\.123\./, // Cloudflare 內部IP
|
||||||
|
/^172\.124\./, // Cloudflare 內部IP
|
||||||
|
/^172\.125\./, // Cloudflare 內部IP
|
||||||
|
/^172\.126\./, // Cloudflare 內部IP
|
||||||
|
/^172\.127\./, // Cloudflare 內部IP
|
||||||
|
/^172\.128\./, // Cloudflare 內部IP
|
||||||
|
/^172\.129\./, // Cloudflare 內部IP
|
||||||
|
/^172\.130\./, // Cloudflare 內部IP
|
||||||
|
/^172\.131\./, // Cloudflare 內部IP
|
||||||
|
/^172\.132\./, // Cloudflare 內部IP
|
||||||
|
/^172\.133\./, // Cloudflare 內部IP
|
||||||
|
/^172\.134\./, // Cloudflare 內部IP
|
||||||
|
/^172\.135\./, // Cloudflare 內部IP
|
||||||
|
/^172\.136\./, // Cloudflare 內部IP
|
||||||
|
/^172\.137\./, // Cloudflare 內部IP
|
||||||
|
/^172\.138\./, // Cloudflare 內部IP
|
||||||
|
/^172\.139\./, // Cloudflare 內部IP
|
||||||
|
/^172\.140\./, // Cloudflare 內部IP
|
||||||
|
/^172\.141\./, // Cloudflare 內部IP
|
||||||
|
/^172\.142\./, // Cloudflare 內部IP
|
||||||
|
/^172\.143\./, // Cloudflare 內部IP
|
||||||
|
/^172\.144\./, // Cloudflare 內部IP
|
||||||
|
/^172\.145\./, // Cloudflare 內部IP
|
||||||
|
/^172\.146\./, // Cloudflare 內部IP
|
||||||
|
/^172\.147\./, // Cloudflare 內部IP
|
||||||
|
/^172\.148\./, // Cloudflare 內部IP
|
||||||
|
/^172\.149\./, // Cloudflare 內部IP
|
||||||
|
/^172\.150\./, // Cloudflare 內部IP
|
||||||
|
/^172\.151\./, // Cloudflare 內部IP
|
||||||
|
/^172\.152\./, // Cloudflare 內部IP
|
||||||
|
/^172\.153\./, // Cloudflare 內部IP
|
||||||
|
/^172\.154\./, // Cloudflare 內部IP
|
||||||
|
/^172\.155\./, // Cloudflare 內部IP
|
||||||
|
/^172\.156\./, // Cloudflare 內部IP
|
||||||
|
/^172\.157\./, // Cloudflare 內部IP
|
||||||
|
/^172\.158\./, // Cloudflare 內部IP
|
||||||
|
/^172\.159\./, // Cloudflare 內部IP
|
||||||
|
/^172\.160\./, // Cloudflare 內部IP
|
||||||
|
/^172\.161\./, // Cloudflare 內部IP
|
||||||
|
/^172\.162\./, // Cloudflare 內部IP
|
||||||
|
/^172\.163\./, // Cloudflare 內部IP
|
||||||
|
/^172\.164\./, // Cloudflare 內部IP
|
||||||
|
/^172\.165\./, // Cloudflare 內部IP
|
||||||
|
/^172\.166\./, // Cloudflare 內部IP
|
||||||
|
/^172\.167\./, // Cloudflare 內部IP
|
||||||
|
/^172\.168\./, // Cloudflare 內部IP
|
||||||
|
/^172\.169\./, // Cloudflare 內部IP
|
||||||
|
/^172\.170\./, // Cloudflare 內部IP
|
||||||
|
/^172\.171\./, // Cloudflare 內部IP
|
||||||
|
/^172\.172\./, // Cloudflare 內部IP
|
||||||
|
/^172\.173\./, // Cloudflare 內部IP
|
||||||
|
/^172\.174\./, // Cloudflare 內部IP
|
||||||
|
/^172\.175\./, // Cloudflare 內部IP
|
||||||
|
/^172\.176\./, // Cloudflare 內部IP
|
||||||
|
/^172\.177\./, // Cloudflare 內部IP
|
||||||
|
/^172\.178\./, // Cloudflare 內部IP
|
||||||
|
/^172\.179\./, // Cloudflare 內部IP
|
||||||
|
/^172\.180\./, // Cloudflare 內部IP
|
||||||
|
/^172\.181\./, // Cloudflare 內部IP
|
||||||
|
/^172\.182\./, // Cloudflare 內部IP
|
||||||
|
/^172\.183\./, // Cloudflare 內部IP
|
||||||
|
/^172\.184\./, // Cloudflare 內部IP
|
||||||
|
/^172\.185\./, // Cloudflare 內部IP
|
||||||
|
/^172\.186\./, // Cloudflare 內部IP
|
||||||
|
/^172\.187\./, // Cloudflare 內部IP
|
||||||
|
/^172\.188\./, // Cloudflare 內部IP
|
||||||
|
/^172\.189\./, // Cloudflare 內部IP
|
||||||
|
/^172\.190\./, // Cloudflare 內部IP
|
||||||
|
/^172\.191\./, // Cloudflare 內部IP
|
||||||
|
/^172\.192\./, // Cloudflare 內部IP
|
||||||
|
/^172\.193\./, // Cloudflare 內部IP
|
||||||
|
/^172\.194\./, // Cloudflare 內部IP
|
||||||
|
/^172\.195\./, // Cloudflare 內部IP
|
||||||
|
/^172\.196\./, // Cloudflare 內部IP
|
||||||
|
/^172\.197\./, // Cloudflare 內部IP
|
||||||
|
/^172\.198\./, // Cloudflare 內部IP
|
||||||
|
/^172\.199\./, // Cloudflare 內部IP
|
||||||
|
/^172\.200\./, // Cloudflare 內部IP
|
||||||
|
/^172\.201\./, // Cloudflare 內部IP
|
||||||
|
/^172\.202\./, // Cloudflare 內部IP
|
||||||
|
/^172\.203\./, // Cloudflare 內部IP
|
||||||
|
/^172\.204\./, // Cloudflare 內部IP
|
||||||
|
/^172\.205\./, // Cloudflare 內部IP
|
||||||
|
/^172\.206\./, // Cloudflare 內部IP
|
||||||
|
/^172\.207\./, // Cloudflare 內部IP
|
||||||
|
/^172\.208\./, // Cloudflare 內部IP
|
||||||
|
/^172\.209\./, // Cloudflare 內部IP
|
||||||
|
/^172\.210\./, // Cloudflare 內部IP
|
||||||
|
/^172\.211\./, // Cloudflare 內部IP
|
||||||
|
/^172\.212\./, // Cloudflare 內部IP
|
||||||
|
/^172\.213\./, // Cloudflare 內部IP
|
||||||
|
/^172\.214\./, // Cloudflare 內部IP
|
||||||
|
/^172\.215\./, // Cloudflare 內部IP
|
||||||
|
/^172\.216\./, // Cloudflare 內部IP
|
||||||
|
/^172\.217\./, // Cloudflare 內部IP
|
||||||
|
/^172\.218\./, // Cloudflare 內部IP
|
||||||
|
/^172\.219\./, // Cloudflare 內部IP
|
||||||
|
/^172\.220\./, // Cloudflare 內部IP
|
||||||
|
/^172\.221\./, // Cloudflare 內部IP
|
||||||
|
/^172\.222\./, // Cloudflare 內部IP
|
||||||
|
/^172\.223\./, // Cloudflare 內部IP
|
||||||
|
/^172\.224\./, // Cloudflare 內部IP
|
||||||
|
/^172\.225\./, // Cloudflare 內部IP
|
||||||
|
/^172\.226\./, // Cloudflare 內部IP
|
||||||
|
/^172\.227\./, // Cloudflare 內部IP
|
||||||
|
/^172\.228\./, // Cloudflare 內部IP
|
||||||
|
/^172\.229\./, // Cloudflare 內部IP
|
||||||
|
/^172\.230\./, // Cloudflare 內部IP
|
||||||
|
/^172\.231\./, // Cloudflare 內部IP
|
||||||
|
/^172\.232\./, // Cloudflare 內部IP
|
||||||
|
/^172\.233\./, // Cloudflare 內部IP
|
||||||
|
/^172\.234\./, // Cloudflare 內部IP
|
||||||
|
/^172\.235\./, // Cloudflare 內部IP
|
||||||
|
/^172\.236\./, // Cloudflare 內部IP
|
||||||
|
/^172\.237\./, // Cloudflare 內部IP
|
||||||
|
/^172\.238\./, // Cloudflare 內部IP
|
||||||
|
/^172\.239\./, // Cloudflare 內部IP
|
||||||
|
/^172\.240\./, // Cloudflare 內部IP
|
||||||
|
/^172\.241\./, // Cloudflare 內部IP
|
||||||
|
/^172\.242\./, // Cloudflare 內部IP
|
||||||
|
/^172\.243\./, // Cloudflare 內部IP
|
||||||
|
/^172\.244\./, // Cloudflare 內部IP
|
||||||
|
/^172\.245\./, // Cloudflare 內部IP
|
||||||
|
/^172\.246\./, // Cloudflare 內部IP
|
||||||
|
/^172\.247\./, // Cloudflare 內部IP
|
||||||
|
/^172\.248\./, // Cloudflare 內部IP
|
||||||
|
/^172\.249\./, // Cloudflare 內部IP
|
||||||
|
/^172\.250\./, // Cloudflare 內部IP
|
||||||
|
/^172\.251\./, // Cloudflare 內部IP
|
||||||
|
/^172\.252\./, // Cloudflare 內部IP
|
||||||
|
/^172\.253\./, // Cloudflare 內部IP
|
||||||
|
/^172\.254\./, // Cloudflare 內部IP
|
||||||
|
/^172\.255\./, // Cloudflare 內部IP
|
||||||
|
];
|
||||||
|
|
||||||
|
return proxyRanges.some(range => range.test(ip));
|
||||||
|
}
|
||||||
|
|
||||||
// 驗證IP地址格式
|
// 驗證IP地址格式
|
||||||
export function isValidIp(ip: string): boolean {
|
export function isValidIp(ip: string): boolean {
|
||||||
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"dev": "next dev -p 12027",
|
"dev": "next dev -p 12027",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"start": "next start -p 12027"
|
"start": "next start -p 12027",
|
||||||
|
"postinstall": "prisma generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user