107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
|
||
/**
|
||
* 判断IP是否为内网地址
|
||
*/
|
||
function isIntranetIP(ip: string): boolean {
|
||
if (!ip) return false
|
||
|
||
// 处理可能存在的端口号
|
||
const cleanIP = ip.split(':')[0]
|
||
|
||
// localhost
|
||
if (cleanIP === '127.0.0.1' || cleanIP === 'localhost' || cleanIP === '::1') {
|
||
return true
|
||
}
|
||
|
||
// IPv4 内网IP段判断
|
||
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
|
||
const match = cleanIP.match(ipv4Regex)
|
||
|
||
if (match) {
|
||
const [, a, b, c, d] = match.map(Number)
|
||
|
||
// 验证IP有效性
|
||
if (a > 255 || b > 255 || c > 255 || d > 255) {
|
||
return false
|
||
}
|
||
|
||
// 10.0.0.0 - 10.255.255.255 (Class A)
|
||
if (a === 10) return true
|
||
|
||
// 172.16.0.0 - 172.31.255.255 (Class B)
|
||
if (a === 172 && b >= 16 && b <= 31) return true
|
||
|
||
// 192.168.0.0 - 192.168.255.255 (Class C)
|
||
if (a === 192 && b === 168) return true
|
||
|
||
// 169.254.0.0 - 169.254.255.255 (Link-local)
|
||
if (a === 169 && b === 254) return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* 获取客户端真实IP地址
|
||
*/
|
||
function getClientIP(request: NextRequest): string {
|
||
// 优先级:x-real-ip > x-forwarded-for > connection
|
||
const xRealIP = request.headers.get('x-real-ip')
|
||
if (xRealIP) return xRealIP
|
||
|
||
const xForwardedFor = request.headers.get('x-forwarded-for')
|
||
if (xForwardedFor) {
|
||
// x-forwarded-for 可能包含多个IP,取第一个(客户端真实IP)
|
||
return xForwardedFor.split(',')[0].trim()
|
||
}
|
||
|
||
const cfConnectingIP = request.headers.get('cf-connecting-ip') // Cloudflare
|
||
if (cfConnectingIP) return cfConnectingIP
|
||
|
||
const trueClientIP = request.headers.get('true-client-ip') // Akamai
|
||
if (trueClientIP) return trueClientIP
|
||
|
||
// 如果都没有,返回默认值
|
||
return 'unknown'
|
||
}
|
||
|
||
/**
|
||
* GET /api/network-status
|
||
* 返回用户的网络环境状态
|
||
*/
|
||
export async function GET(request: NextRequest) {
|
||
try {
|
||
const clientIP = getClientIP(request)
|
||
const isIntranet = isIntranetIP(clientIP)
|
||
|
||
// 获取当前访问的域名
|
||
const hostname = request.headers.get('host') || ''
|
||
const publicDomains = (process.env.PUBLIC_DOMAINS || '').split(',').map(s => s.trim()).filter(Boolean)
|
||
const isPublicDomain = publicDomains.length > 0 ? publicDomains.includes(hostname) : false
|
||
|
||
console.log(`[Network Status] IP: ${clientIP}, isIntranet: ${isIntranet}, hostname: ${hostname}`)
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
isIntranet,
|
||
isPublicDomain,
|
||
// 不返回完整IP地址(隐私保护)
|
||
ipMasked: clientIP.split('.').slice(0, 2).join('.') + '.xxx.xxx',
|
||
message: isIntranet
|
||
? '检测到内网访问'
|
||
: '检测到公网访问',
|
||
intranetUrl: isIntranet && isPublicDomain
|
||
? (process.env.NEXT_PUBLIC_INTRANET_URL || '')
|
||
: null
|
||
})
|
||
} catch (error) {
|
||
console.error('[Network Status] Error:', error)
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: '网络状态检测失败'
|
||
}, { status: 500 })
|
||
}
|
||
}
|
||
|