149 lines
4.8 KiB
TypeScript
149 lines
4.8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { isInternalIP, detectIsIntranet, extractClientIpFromForwarded } from '@/lib/network-detection'
|
||
|
||
// 判断是否为内网IP地址(保留向后兼容,用于判断hostname)
|
||
function isInternalIPLegacy(hostname: string): boolean {
|
||
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
|
||
return true
|
||
}
|
||
|
||
// 判断是否为内网IP段
|
||
// 10.0.0.0 - 10.255.255.255
|
||
// 172.16.0.0 - 172.31.255.255
|
||
// 192.168.0.0 - 192.168.255.255
|
||
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
|
||
const match = hostname.match(ipv4Regex)
|
||
|
||
if (match) {
|
||
const [, a, b] = match.map(Number)
|
||
|
||
// 10.x.x.x
|
||
if (a === 10) return true
|
||
|
||
// 172.16.x.x - 172.31.x.x
|
||
if (a === 172 && b >= 16 && b <= 31) return true
|
||
|
||
// 192.168.x.x
|
||
if (a === 192 && b === 168) return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// 网络状态检测API
|
||
// GET: 检测用户是否在内网,以及是否访问公网域名
|
||
export async function GET(request: NextRequest) {
|
||
try {
|
||
// 获取当前访问的域名
|
||
const host = request.headers.get('host') || ''
|
||
const hostname = host.split(':')[0] // 移除端口号
|
||
|
||
// 使用统一的网络检测函数(已包含严谨的检测逻辑)
|
||
const isIntranet = detectIsIntranet(request)
|
||
|
||
// 获取客户端IP(用于调试和日志,使用与 detectIsIntranet 相同的逻辑)
|
||
const forwarded = request.headers.get('x-forwarded-for')
|
||
const realIp = request.headers.get('x-real-ip')
|
||
const cfConnectingIp = request.headers.get('cf-connecting-ip')
|
||
const xClientIp = request.headers.get('x-client-ip')
|
||
|
||
let clientIp: string | null = null
|
||
let clientIpSource: string = 'unknown'
|
||
|
||
if (forwarded) {
|
||
clientIp = extractClientIpFromForwarded(forwarded)
|
||
clientIpSource = 'x-forwarded-for'
|
||
}
|
||
if (!clientIp && realIp) {
|
||
clientIp = realIp
|
||
clientIpSource = 'x-real-ip'
|
||
}
|
||
if (!clientIp && cfConnectingIp) {
|
||
clientIp = cfConnectingIp
|
||
clientIpSource = 'cf-connecting-ip'
|
||
}
|
||
if (!clientIp && xClientIp) {
|
||
clientIp = xClientIp
|
||
clientIpSource = 'x-client-ip'
|
||
}
|
||
|
||
const clientIpDisplay = clientIp || 'unknown'
|
||
|
||
// 检查 X-Forwarded-For 中是否有内网IP
|
||
let hasInternalIpInForwarded = false
|
||
let forwardedIpList: string[] = []
|
||
if (forwarded) {
|
||
forwardedIpList = forwarded.split(',').map(ip => ip.trim()).filter(ip => ip)
|
||
hasInternalIpInForwarded = forwardedIpList.some(ip => isInternalIP(ip))
|
||
}
|
||
|
||
// 判断是否为公网域名(非内网IP的域名,且不是localhost)
|
||
const hostnameIsIntranet = isInternalIPLegacy(hostname)
|
||
const isPublicDomain = !hostnameIsIntranet &&
|
||
hostname !== 'localhost' &&
|
||
hostname !== '127.0.0.1' &&
|
||
!hostname.match(/^192\.168\./) &&
|
||
!hostname.match(/^10\./) &&
|
||
!hostname.match(/^172\.(1[6-9]|2[0-9]|3[01])\./) &&
|
||
// 确保hostname不是IP格式(是域名)
|
||
!hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
|
||
|
||
const response = NextResponse.json({
|
||
success: true,
|
||
isIntranet,
|
||
isPublicDomain,
|
||
clientIp: clientIpDisplay,
|
||
clientIpSource,
|
||
hostname,
|
||
// 诊断信息(帮助排查问题)
|
||
debug: {
|
||
forwardedIpList,
|
||
hasInternalIpInForwarded,
|
||
realIp: realIp || null,
|
||
cfConnectingIp: cfConnectingIp || null,
|
||
xClientIp: xClientIp || null,
|
||
clientIpIsInternal: clientIp ? isInternalIP(clientIp) : null,
|
||
},
|
||
timestamp: new Date().toISOString()
|
||
})
|
||
|
||
// 添加CORS头,允许从任何来源访问(用于客户端直接检测内网访问)
|
||
const origin = request.headers.get('origin')
|
||
if (origin) {
|
||
response.headers.set('Access-Control-Allow-Origin', origin)
|
||
response.headers.set('Access-Control-Allow-Credentials', 'true')
|
||
response.headers.set('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||
response.headers.set('Access-Control-Allow-Headers', 'Content-Type')
|
||
}
|
||
|
||
return response
|
||
} catch (error) {
|
||
console.error('[Network Status] Error:', error)
|
||
return NextResponse.json(
|
||
{
|
||
success: false,
|
||
error: '网络检测失败'
|
||
},
|
||
{ status: 500 }
|
||
)
|
||
}
|
||
}
|
||
|
||
export async function POST(request: NextRequest) {
|
||
try {
|
||
// 接收测试数据并立即返回,用于测速
|
||
const data = await request.arrayBuffer()
|
||
|
||
return NextResponse.json({
|
||
status: 'ok',
|
||
receivedBytes: data.byteLength,
|
||
timestamp: new Date().toISOString()
|
||
})
|
||
} catch (error) {
|
||
return NextResponse.json(
|
||
{ error: '网络测试失败' },
|
||
{ status: 500 }
|
||
)
|
||
}
|
||
}
|