Files
wish-pool/app/api/ip/route.ts
aken1023 2808852e9f 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.
2025-08-01 15:35:15 +08:00

176 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server'
import { getClientIp, isIpAllowed, getIpLocation, getDetailedIpInfo } from '@/lib/ip-utils'
// 強制動態渲染
export const dynamic = 'force-dynamic'
export async function GET(request: NextRequest) {
try {
// 使用詳細的IP檢測功能
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
const forwardedFor = request.headers.get('x-forwarded-for');
if (forwardedFor) {
const ips = forwardedFor.split(',').map(ip => ip.trim());
for (const ip of ips) {
// 處理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;
}
}
}
// 檢查其他可能的IP來源
const realIp = request.headers.get('x-real-ip');
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) {
let cleanClientIp = clientIpHeader;
if (clientIpHeader.startsWith('::ffff:')) {
cleanClientIp = clientIpHeader.substring(7);
}
if (cleanClientIp !== '127.0.0.1') {
clientIp = cleanClientIp;
}
}
}
const allowedIps = process.env.ALLOWED_IPS || ''
const enableIpWhitelist = process.env.ENABLE_IP_WHITELIST === 'true'
const isAllowed = enableIpWhitelist ? isIpAllowed(clientIp, allowedIps) : true
// 嘗試獲取地理位置信息
let locationInfo = null
if (clientIp && clientIp !== '127.0.0.1' && isPublicIp(clientIp)) {
try {
locationInfo = await getIpLocation(clientIp)
} catch (error) {
console.error('Error fetching location info:', error)
}
}
// 確保最終返回的IP是IPv4格式
const finalIp = ensureIPv4Format(clientIp);
const originalIp = detailedInfo.detectedIp;
const isIPv6Mapped = isIPv6MappedIPv4(originalIp);
const ipv6Format = getIPv6MappedFormat(finalIp);
return NextResponse.json({
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,
isLocalDevelopment: detailedInfo.isLocalDevelopment,
localIp: detailedInfo.localIp,
environment: process.env.NODE_ENV,
host: request.headers.get('host'),
referer: request.headers.get('referer'),
userAgent: request.headers.get('user-agent'),
originalDetectedIp: detailedInfo.detectedIp,
finalDetectedIp: finalIp,
rawDetectedIp: clientIp, // 保留原始檢測到的IP用於調試
ipDetectionMethod: isIPv6Mapped ? 'IPv6-Mapped-IPv4' : 'Standard-IPv4'
},
location: locationInfo,
// 本地開發環境的特殊信息
development: detailedInfo.isLocalDevelopment ? {
message: '本地開發環境 - IP檢測可能受限',
suggestions: [
'在生產環境中部署後IP檢測會更準確',
'可以使用 ngrok 或類似工具進行外部測試',
'檢查防火牆和網路配置',
'確認代理伺服器設置',
'如果使用VPN請檢查VPN設置'
]
} : null
})
} catch (error) {
console.error('Error getting IP info:', error)
return NextResponse.json(
{ error: '無法獲取IP信息' },
{ status: 500 }
)
}
}
// 檢查是否為公網IP的輔助函數
function isPublicIp(ip: string): boolean {
const privateRanges = [
/^10\./, // 10.0.0.0/8
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12
/^192\.168\./, // 192.168.0.0/16
/^127\./, // 127.0.0.0/8
/^169\.254\./, // 169.254.0.0/16 (Link-local)
/^0\./, // 0.0.0.0/8
/^224\./, // 224.0.0.0/4 (Multicast)
/^240\./, // 240.0.0.0/4 (Reserved)
];
return !privateRanges.some(range => range.test(ip));
}