Fix IPv4 display and IP detection logic

Improved IP detection and display logic to always show IPv4 format, converting IPv6-mapped IPv4 addresses (e.g., ::ffff:127.0.0.1) and IPv6 loopback (::1) to 127.0.0.1. Updated API endpoint, display components, and added dedicated test/debug pages for IP format and detection. Added documentation summarizing the fixes and new features.
This commit is contained in:
2025-08-01 15:35:15 +08:00
parent ad8676cac3
commit 2808852e9f
11 changed files with 1198 additions and 386 deletions

View File

@@ -10,6 +10,43 @@ export async function GET(request: NextRequest) {
const detailedInfo = getDetailedIpInfo(request);
let clientIp = detailedInfo.detectedIp;
// 確保返回IPv4格式的地址
function ensureIPv4Format(ip: string): string {
if (!ip) return '127.0.0.1';
// 移除空白字符
ip = ip.trim();
// 處理IPv6格式的IPv4地址
if (ip.startsWith('::ffff:')) {
return ip.substring(7);
}
// 處理純IPv6本地回環地址
if (ip === '::1') {
return '127.0.0.1';
}
// 驗證是否為有效的IPv4地址
const ipv4Regex = /^(?:(?: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]?)$/;
if (ipv4Regex.test(ip)) {
return ip;
}
// 如果不是有效的IPv4返回默認值
return '127.0.0.1';
}
// 檢查是否為IPv6格式的IPv4地址
function isIPv6MappedIPv4(ip: string): boolean {
return ip.startsWith('::ffff:');
}
// 獲取IPv6格式的IPv4地址
function getIPv6MappedFormat(ipv4: string): string {
return `::ffff:${ipv4}`;
}
// 如果檢測到的是127.0.0.1嘗試從請求頭獲取真實IP
if (clientIp === '127.0.0.1') {
// 檢查是否有代理轉發的真實IP
@@ -17,8 +54,13 @@ export async function GET(request: NextRequest) {
if (forwardedFor) {
const ips = forwardedFor.split(',').map(ip => ip.trim());
for (const ip of ips) {
if (ip && ip !== '127.0.0.1' && ip !== '::1' && ip !== 'localhost') {
clientIp = ip;
// 處理IPv6格式的IPv4地址
let cleanIp = ip;
if (ip.startsWith('::ffff:')) {
cleanIp = ip.substring(7);
}
if (cleanIp && cleanIp !== '127.0.0.1' && cleanIp !== '::1' && cleanIp !== 'localhost') {
clientIp = cleanIp;
break;
}
}
@@ -26,13 +68,25 @@ export async function GET(request: NextRequest) {
// 檢查其他可能的IP來源
const realIp = request.headers.get('x-real-ip');
if (realIp && realIp !== '127.0.0.1') {
clientIp = realIp;
if (realIp) {
let cleanRealIp = realIp;
if (realIp.startsWith('::ffff:')) {
cleanRealIp = realIp.substring(7);
}
if (cleanRealIp !== '127.0.0.1') {
clientIp = cleanRealIp;
}
}
const clientIpHeader = request.headers.get('x-client-ip');
if (clientIpHeader && clientIpHeader !== '127.0.0.1') {
clientIp = clientIpHeader;
if (clientIpHeader) {
let cleanClientIp = clientIpHeader;
if (clientIpHeader.startsWith('::ffff:')) {
cleanClientIp = clientIpHeader.substring(7);
}
if (cleanClientIp !== '127.0.0.1') {
clientIp = cleanClientIp;
}
}
}
@@ -51,12 +105,24 @@ export async function GET(request: NextRequest) {
}
}
// 確保最終返回的IP是IPv4格式
const finalIp = ensureIPv4Format(clientIp);
const originalIp = detailedInfo.detectedIp;
const isIPv6Mapped = isIPv6MappedIPv4(originalIp);
const ipv6Format = getIPv6MappedFormat(finalIp);
return NextResponse.json({
ip: clientIp,
ip: finalIp,
isAllowed,
enableIpWhitelist,
allowedIps: enableIpWhitelist ? allowedIps.split(',').map(ip => ip.trim()) : [],
timestamp: new Date().toISOString(),
ipv6Info: {
isIPv6Mapped,
originalFormat: originalIp,
ipv6Format,
hasIPv6Support: true
},
debug: {
allIpSources: detailedInfo.ipSources,
allFoundIps: detailedInfo.allFoundIps,
@@ -67,7 +133,9 @@ export async function GET(request: NextRequest) {
referer: request.headers.get('referer'),
userAgent: request.headers.get('user-agent'),
originalDetectedIp: detailedInfo.detectedIp,
finalDetectedIp: clientIp,
finalDetectedIp: finalIp,
rawDetectedIp: clientIp, // 保留原始檢測到的IP用於調試
ipDetectionMethod: isIPv6Mapped ? 'IPv6-Mapped-IPv4' : 'Standard-IPv4'
},
location: locationInfo,
// 本地開發環境的特殊信息

126
app/test-ipv6/page.tsx Normal file
View File

@@ -0,0 +1,126 @@
"use client"
import { useEffect, useState } from "react"
import IpDisplay from "@/components/ip-display"
interface IpInfo {
ip: string
ipv6Info?: {
isIPv6Mapped: boolean
originalFormat: string
ipv6Format: string
hasIPv6Support: boolean
}
debug?: {
originalDetectedIp?: string
finalDetectedIp?: string
rawDetectedIp?: string
allFoundIps?: string[]
ipDetectionMethod?: string
}
}
export default function TestIPv6Page() {
const [ipInfo, setIpInfo] = useState<IpInfo | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchIpInfo = async () => {
try {
const response = await fetch('/api/ip')
if (response.ok) {
const data = await response.json()
setIpInfo(data)
}
} catch (error) {
console.error("Error fetching IP info:", error)
} finally {
setLoading(false)
}
}
fetchIpInfo()
}, [])
return (
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-blue-900 to-indigo-900 p-8">
<div className="container mx-auto max-w-4xl">
<h1 className="text-3xl font-bold text-white mb-8 text-center">
IPv6格式IPv4地址測試頁面
</h1>
<div className="bg-slate-800/50 backdrop-blur-sm rounded-lg p-6 border border-blue-800/30">
<h2 className="text-xl font-semibold text-blue-200 mb-4">
IP顯示組件測試
</h2>
<div className="mb-6">
<h3 className="text-lg text-blue-300 mb-2">:</h3>
<IpDisplay />
</div>
<div className="mb-6">
<h3 className="text-lg text-blue-300 mb-2">:</h3>
<IpDisplay mobileSimplified />
</div>
</div>
{loading ? (
<div className="bg-slate-800/50 backdrop-blur-sm rounded-lg p-6 border border-blue-800/30 mt-6">
<p className="text-blue-200">...</p>
</div>
) : ipInfo ? (
<div className="bg-slate-800/50 backdrop-blur-sm rounded-lg p-6 border border-blue-800/30 mt-6">
<h2 className="text-xl font-semibold text-blue-200 mb-4">
API數據
</h2>
<div className="space-y-4">
<div>
<h3 className="text-lg text-blue-300 mb-2">:</h3>
<div className="bg-slate-900/50 p-4 rounded border border-slate-700">
<p className="text-white"><span className="text-blue-200">IPv4地址:</span> {ipInfo.ip}</p>
{ipInfo.ipv6Info && (
<>
<p className="text-white"><span className="text-blue-200">IPv6映射:</span> {ipInfo.ipv6Info.isIPv6Mapped ? '是' : '否'}</p>
<p className="text-white"><span className="text-blue-200">:</span> {ipInfo.ipv6Info.originalFormat}</p>
<p className="text-white"><span className="text-blue-200">IPv6格式:</span> {ipInfo.ipv6Info.ipv6Format}</p>
<p className="text-white"><span className="text-blue-200">IPv6支援:</span> {ipInfo.ipv6Info.hasIPv6Support ? '已啟用' : '未啟用'}</p>
</>
)}
</div>
</div>
{ipInfo.debug && (
<div>
<h3 className="text-lg text-blue-300 mb-2">調:</h3>
<div className="bg-slate-900/50 p-4 rounded border border-slate-700">
<p className="text-white"><span className="text-blue-200">:</span> {ipInfo.debug.ipDetectionMethod || '未知'}</p>
<p className="text-white"><span className="text-blue-200">IP:</span> {ipInfo.debug.originalDetectedIp || '無'}</p>
<p className="text-white"><span className="text-blue-200">IP:</span> {ipInfo.debug.finalDetectedIp || '無'}</p>
<p className="text-white"><span className="text-blue-200">IP:</span> {ipInfo.debug.rawDetectedIp || '無'}</p>
{ipInfo.debug.allFoundIps && ipInfo.debug.allFoundIps.length > 0 && (
<div className="mt-2">
<p className="text-blue-200">IP:</p>
<ul className="list-disc list-inside text-yellow-300 ml-4">
{ipInfo.debug.allFoundIps.map((ip, index) => (
<li key={index}>{ip}</li>
))}
</ul>
</div>
)}
</div>
</div>
)}
</div>
</div>
) : (
<div className="bg-red-900/50 backdrop-blur-sm rounded-lg p-6 border border-red-800/30 mt-6">
<p className="text-red-200">IP信息</p>
</div>
)}
</div>
</div>
)
}

View File

@@ -3,10 +3,9 @@
import { useState, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Globe, Shield, MapPin, RefreshCw, AlertCircle, CheckCircle, Info, Lightbulb } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Globe, Info, AlertCircle, CheckCircle, RefreshCw } from 'lucide-react'
interface IpDebugInfo {
ip: string
@@ -23,12 +22,11 @@ interface IpDebugInfo {
host: string | null
referer: string | null
userAgent: string | null
originalDetectedIp: string
finalDetectedIp: string
}
location: any
development: {
message: string
suggestions: string[]
} | null
development: any
}
export default function IpDebugPage() {
@@ -47,8 +45,8 @@ export default function IpDebugPage() {
const data = await response.json()
setIpInfo(data)
} catch (error) {
console.error("無法獲取IP信息:", error)
setError("無法獲取IP信息")
console.error('Error fetching IP info:', error)
setError('無法獲取IP信息')
} finally {
setLoading(false)
}
@@ -60,12 +58,10 @@ export default function IpDebugPage() {
if (loading) {
return (
<div className="container mx-auto p-6 max-w-4xl">
<div className="flex items-center justify-center min-h-[400px]">
<div className="flex items-center gap-2">
<RefreshCw className="w-5 h-5 animate-spin" />
<span>...</span>
</div>
<div className="container mx-auto p-6 max-w-6xl">
<div className="text-center">
<RefreshCw className="w-8 h-8 animate-spin mx-auto mb-4" />
<p>...</p>
</div>
</div>
)
@@ -73,262 +69,197 @@ export default function IpDebugPage() {
if (error || !ipInfo) {
return (
<div className="container mx-auto p-6 max-w-4xl">
<Card className="border-red-200 bg-red-50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-red-800">
<AlertCircle className="w-5 h-5" />
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-red-700">{error || '無法獲取IP信息'}</p>
<Button onClick={fetchIpInfo} className="mt-4">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
</CardContent>
</Card>
<div className="container mx-auto p-6 max-w-6xl">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
</div>
)
}
return (
<div className="container mx-auto p-6 max-w-4xl space-y-6">
<div className="container mx-auto p-6 max-w-6xl space-y-6">
<div className="text-center">
<h1 className="text-3xl font-bold mb-2">IP 調</h1>
<p className="text-muted-foreground">IP檢測信息和調試數據</p>
<h1 className="text-3xl font-bold mb-2">IP 調</h1>
<p className="text-muted-foreground">IP檢測和調試信息</p>
</div>
{/* 本地開發環境提示 */}
{ipInfo.development && (
<Alert className="border-blue-200 bg-blue-50">
<Info className="h-4 w-4 text-blue-600" />
<AlertDescription className="text-blue-800">
<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} className="flex items-start gap-2">
<Lightbulb className="w-3 h-3 mt-0.5 text-blue-500 flex-shrink-0" />
<span>{suggestion}</span>
</div>
))}
</div>
</AlertDescription>
</Alert>
)}
{/* 主要IP信息 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Globe className="w-5 h-5" />
IP
IP地址
</CardTitle>
<CardDescription>
IP地址信息
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-medium">IP:</span>
<Badge variant={ipInfo.ip === '127.0.0.1' ? 'destructive' : 'default'}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 className="font-medium mb-2">IP</h4>
<Badge variant="default" className="text-sm">
{ipInfo.ip}
</Badge>
</div>
<div className="flex items-center gap-2">
<span className="font-medium">:</span>
{ipInfo.isAllowed ? (
<Badge variant="default" className="flex items-center gap-1">
<CheckCircle className="w-3 h-3" />
</Badge>
) : (
<Badge variant="destructive" className="flex items-center gap-1">
<AlertCircle className="w-3 h-3" />
</Badge>
)}
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Shield className="w-4 h-4" />
<span className="text-sm">IP白名單:</span>
<Badge variant={ipInfo.enableIpWhitelist ? 'default' : 'secondary'}>
{ipInfo.enableIpWhitelist ? '已啟用' : '已停用'}
<div>
<h4 className="font-medium mb-2">IP</h4>
<Badge variant="outline" className="text-sm">
{ipInfo.debug.originalDetectedIp}
</Badge>
</div>
<div className="flex items-center gap-2">
<span className="text-sm">:</span>
<Badge variant="outline">{ipInfo.debug.environment}</Badge>
</div>
{ipInfo.debug.isLocalDevelopment && ipInfo.debug.localIp && (
<div className="flex items-center gap-2">
<span className="text-sm">IP:</span>
<Badge variant="outline">{ipInfo.debug.localIp}</Badge>
</div>
)}
</div>
{ipInfo.location && (
<div className="flex items-center gap-2">
<MapPin className="w-4 h-4" />
<span className="text-sm">:</span>
<span className="text-sm">
{ipInfo.location.city}, {ipInfo.location.country} ({ipInfo.location.isp})
</span>
<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"}>
{ipInfo.isAllowed ? '允許' : '拒絕'}
</Badge>
</div>
)}
<div>
<h4 className="font-medium mb-2"></h4>
<Badge variant={ipInfo.enableIpWhitelist ? "default" : "secondary"}>
{ipInfo.enableIpWhitelist ? '已啟用' : '已禁用'}
</Badge>
</div>
<div>
<h4 className="font-medium mb-2"></h4>
<Badge variant="outline">
{ipInfo.debug.environment}
</Badge>
</div>
</div>
</CardContent>
</Card>
{/* 所有找到的IP */}
<Card>
<CardHeader>
<CardTitle>IP</CardTitle>
<CardDescription>IP地址</CardDescription>
<CardTitle>IP地址</CardTitle>
<CardDescription>
IP地址
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
<div className="space-y-2">
{ipInfo.debug.allFoundIps.length > 0 ? (
ipInfo.debug.allFoundIps.map((ip, index) => (
<Badge
key={index}
variant={ip === ipInfo.ip ? 'default' : 'outline'}
className={ip === '127.0.0.1' ? 'border-red-300 text-red-700' : ''}
>
{ip} {ip === ipInfo.ip && '(已選擇)'}
</Badge>
<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>
))
) : (
<span className="text-muted-foreground">IP</span>
<p className="text-muted-foreground">IP地址</p>
)}
</div>
</CardContent>
</Card>
{/* 允許的IP列表 */}
{ipInfo.enableIpWhitelist && (
<Card>
<CardHeader>
<CardTitle>IP地址</CardTitle>
<CardDescription>IP白名單</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{ipInfo.allowedIps.length > 0 ? (
ipInfo.allowedIps.map((ip, index) => (
<Badge key={index} variant="outline">
{ip}
</Badge>
))
) : (
<span className="text-muted-foreground">IP白名單</span>
)}
</div>
</CardContent>
</Card>
)}
{/* 調試信息 */}
{/* IP來源詳細信息 */}
<Card>
<CardHeader>
<CardTitle>調</CardTitle>
<CardDescription>IP來源和請求頭信息</CardDescription>
<CardTitle>IP來源詳細信</CardTitle>
<CardDescription>
HTTP頭和連接信息中的IP地址
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<h4 className="font-medium mb-2">IP來源:</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{Object.entries(ipInfo.debug.allIpSources).map(([key, value]) => (
<div key={key} className="flex justify-between items-center p-2 bg-muted rounded">
<span className="text-sm font-mono">{key}:</span>
<span className="text-sm text-muted-foreground">
{value || 'null'}
</span>
</div>
))}
<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>
<Separator />
<div>
<h4 className="font-medium mb-2">:</h4>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm">Host:</span>
<span className="text-sm text-muted-foreground">
{ipInfo.debug.host || 'null'}
</span>
</div>
<div className="flex justify-between">
<span className="text-sm">Referer:</span>
<span className="text-sm text-muted-foreground">
{ipInfo.debug.referer || 'null'}
</span>
</div>
<div className="flex justify-between">
<span className="text-sm">User Agent:</span>
<span className="text-sm text-muted-foreground max-w-xs truncate">
{ipInfo.debug.userAgent || 'null'}
</span>
</div>
<div className="flex justify-between">
<span className="text-sm">:</span>
<span className="text-sm text-muted-foreground">
{new Date(ipInfo.timestamp).toLocaleString('zh-TW')}
</span>
</div>
</div>
</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 className="flex items-center gap-2">
<MapPin className="w-5 h-5" />
</CardTitle>
<CardTitle></CardTitle>
<CardDescription>
IP地址的地理位置信
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h4 className="font-medium mb-2"></h4>
<div className="space-y-1 text-sm">
<div>: {ipInfo.location.country} ({ipInfo.location.countryCode})</div>
<div>: {ipInfo.location.regionName}</div>
<div>: {ipInfo.location.city}</div>
<div>: {ipInfo.location.zip}</div>
<div>: {ipInfo.location.timezone}</div>
</div>
</div>
<div>
<h4 className="font-medium mb-2"></h4>
<div className="space-y-1 text-sm">
<div>ISP: {ipInfo.location.isp}</div>
<div>: {ipInfo.location.org}</div>
<div>AS: {ipInfo.location.as}</div>
<div>: {ipInfo.location.mobile ? '是' : '否'}</div>
<div>: {ipInfo.location.proxy ? '是' : '否'}</div>
<div>: {ipInfo.location.hosting ? '是' : '否'}</div>
</div>
</div>
</div>
<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>
<h4 className="font-medium mb-2"></h4>
<p className="text-sm font-mono">{ipInfo.debug.host || '未設置'}</p>
</div>
<div>
<h4 className="font-medium mb-2"></h4>
<p className="text-sm font-mono">{ipInfo.debug.referer || '未設置'}</p>
</div>
</div>
<div>
<h4 className="font-medium mb-2"></h4>
<p className="text-sm font-mono break-all">{ipInfo.debug.userAgent || '未設置'}</p>
</div>
<div>
<h4 className="font-medium mb-2"></h4>
<p className="text-sm">{ipInfo.timestamp}</p>
</div>
</div>
</CardContent>
</Card>
{/* 操作按鈕 */}
<div className="flex justify-center">
<div className="flex justify-center gap-4">
<Button onClick={fetchIpInfo} className="flex items-center gap-2">
<RefreshCw className="w-4 h-4" />
IP
</Button>
<Button variant="outline" onClick={() => window.open('/api/ip', '_blank')}>
<Globe className="w-4 h-4 mr-2" />
API響應
</Button>
</div>
</div>

View File

@@ -0,0 +1,246 @@
'use client'
import { useState, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Globe, RefreshCw, CheckCircle, AlertCircle, Info } from 'lucide-react'
interface IpTestResult {
originalIp: string
cleanedIp: string
isIPv4: boolean
isIPv6: boolean
isLocalhost: boolean
description: string
}
export default function IpFormatTestPage() {
const [testResults, setTestResults] = useState<IpTestResult[]>([])
const [loading, setLoading] = useState(false)
// 測試IP地址清理函數
function cleanIpForDisplay(ip: string): string {
if (!ip) return '127.0.0.1';
// 移除空白字符
ip = ip.trim();
// 處理IPv6格式的IPv4地址 (例如: ::ffff:192.168.1.1)
if (ip.startsWith('::ffff:')) {
return ip.substring(7);
}
// 處理純IPv6本地回環地址
if (ip === '::1') {
return '127.0.0.1';
}
// 驗證是否為有效的IPv4地址
const ipv4Regex = /^(?:(?: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]?)$/;
if (ipv4Regex.test(ip)) {
return ip;
}
// 如果不是有效的IPv4返回默認值
return '127.0.0.1';
}
// 檢查IP類型
function analyzeIp(ip: string): { isIPv4: boolean; isIPv6: boolean; isLocalhost: boolean; description: string } {
const ipv4Regex = /^(?:(?: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 ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
const isIPv4 = ipv4Regex.test(ip);
const isIPv6 = ipv6Regex.test(ip) || ip.startsWith('::ffff:') || ip === '::1';
const isLocalhost = ip === '127.0.0.1' || ip === '::1' || ip === 'localhost';
let description = '';
if (ip.startsWith('::ffff:')) {
description = 'IPv6格式的IPv4地址';
} else if (ip === '::1') {
description = 'IPv6本地回環地址';
} else if (isIPv4) {
description = '標準IPv4地址';
} else if (isIPv6) {
description = 'IPv6地址';
} else {
description = '無效的IP地址格式';
}
return { isIPv4, isIPv6, isLocalhost, description };
}
const runTests = () => {
setLoading(true);
const testIps = [
'::ffff:127.0.0.1',
'::1',
'127.0.0.1',
'192.168.1.1',
'::ffff:192.168.1.100',
'2001:db8::1',
'invalid-ip',
'localhost',
'::ffff:203.0.113.1',
'10.0.0.1'
];
const results: IpTestResult[] = testIps.map(originalIp => {
const cleanedIp = cleanIpForDisplay(originalIp);
const analysis = analyzeIp(originalIp);
return {
originalIp,
cleanedIp,
...analysis
};
});
setTestResults(results);
setLoading(false);
};
useEffect(() => {
runTests();
}, []);
return (
<div className="container mx-auto p-6 max-w-6xl space-y-6">
<div className="text-center">
<h1 className="text-3xl font-bold mb-2">IPv4 </h1>
<p className="text-muted-foreground">IP地址清理和IPv4格式轉換功能</p>
</div>
{/* 說明 */}
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
<div className="font-medium mb-2"></div>
<div className="text-sm space-y-1">
<div> IPv6格式的IPv4地址能正確轉換為IPv4</div>
<div> IP地址都顯示為標準IPv4格式</div>
<div> IP地址格式的處理邏輯</div>
<div> </div>
</div>
</AlertDescription>
</Alert>
{/* 測試結果 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Globe className="w-5 h-5" />
IP格式轉換測試結果
</CardTitle>
<CardDescription>
IP地址格式的清理和轉換結果
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{testResults.map((result, index) => (
<div key={index} className="border rounded-lg p-4 space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-medium">IP:</span>
<Badge variant="outline" className="font-mono">
{result.originalIp}
</Badge>
</div>
<div className="flex items-center gap-2">
<span className="font-medium">:</span>
<Badge variant="default" className="font-mono">
{result.cleanedIp}
</Badge>
</div>
</div>
<div className="flex items-center gap-4 text-sm">
<div className="flex items-center gap-1">
{result.isIPv4 ? (
<CheckCircle className="w-4 h-4 text-green-500" />
) : (
<AlertCircle className="w-4 h-4 text-red-500" />
)}
<span>IPv4: {result.isIPv4 ? '是' : '否'}</span>
</div>
<div className="flex items-center gap-1">
{result.isIPv6 ? (
<CheckCircle className="w-4 h-4 text-blue-500" />
) : (
<span className="text-gray-400">-</span>
)}
<span>IPv6: {result.isIPv6 ? '是' : '否'}</span>
</div>
<div className="flex items-center gap-1">
{result.isLocalhost ? (
<CheckCircle className="w-4 h-4 text-orange-500" />
) : (
<span className="text-gray-400">-</span>
)}
<span>: {result.isLocalhost ? '是' : '否'}</span>
</div>
</div>
<div className="text-sm text-muted-foreground">
{result.description}
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* 操作按鈕 */}
<div className="flex justify-center">
<Button onClick={runTests} disabled={loading} className="flex items-center gap-2">
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
{loading ? '測試中...' : '重新測試'}
</Button>
</div>
{/* 總結 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
IPv4格式轉換的關鍵點
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div>
<h4 className="font-medium mb-2"> </h4>
<ul className="text-sm space-y-1 text-muted-foreground">
<li> <code>::ffff:127.0.0.1</code> <code>127.0.0.1</code></li>
<li> <code>::1</code> <code>127.0.0.1</code></li>
<li> <code>127.0.0.1</code> <code>127.0.0.1</code> ()</li>
<li> <code>192.168.1.1</code> <code>192.168.1.1</code> ()</li>
</ul>
</div>
<div>
<h4 className="font-medium mb-2"> </h4>
<ul className="text-sm space-y-1 text-muted-foreground">
<li> IP地址 <code>127.0.0.1</code> ()</li>
<li> null <code>127.0.0.1</code> ()</li>
</ul>
</div>
<div>
<h4 className="font-medium mb-2">🎯 </h4>
<p className="text-sm text-muted-foreground">
IP地址都是標準的IPv4格式
</p>
</div>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -186,6 +186,10 @@ ALLOWED_IPS=你的真實IP地址,其他允許的IP`}
<Globe className="w-4 h-4 mr-2" />
IP調試
</Button>
<Button variant="outline" onClick={() => window.open('/test/ip-format-test', '_blank')}>
<CheckCircle className="w-4 h-4 mr-2" />
IPv4格式測試
</Button>
<Button variant="outline" onClick={() => window.open('/api/ip', '_blank')}>
<ExternalLink className="w-4 h-4 mr-2" />
IP API