import IP2Region from 'ip2region' import { existsSync } from 'fs' import { join } from 'path' import { isPrivateIp, normalizeIpCandidate } from './clientIp' interface IpGeodata { country: string province: string city: string isp: string } const ipLocationLabelCache = new Map() let ip2Region: IP2Region | null | undefined export function getIpGeodataLabel(ip?: string | null) { const normalizedIp = normalizeIpCandidate(ip) if (!normalizedIp) { return null } const cachedLabel = ipLocationLabelCache.get(normalizedIp) if (cachedLabel) { return cachedLabel } const label = resolveIpGeodataLabel(normalizedIp) ipLocationLabelCache.set(normalizedIp, label) return label } function resolveIpGeodataLabel(ip: string) { if (isPrivateIp(ip)) { return '内网地址' } try { const geodata = getIp2Region()?.search(ip) as IpGeodata | null const parts = uniqueNonEmptyValues([ geodata?.country, geodata?.province, geodata?.city, geodata?.isp ]) return parts.length > 0 ? parts.join(' ') : '未知归属地' } catch { return '未知归属地' } } function getIp2Region() { if (ip2Region !== undefined) { return ip2Region } const dataDir = join(process.cwd(), 'node_modules', 'ip2region', 'data') const ipv4db = join(dataDir, 'ip2region.db') const ipv6db = join(dataDir, 'ipv6wry.db') if (!existsSync(ipv4db) || !existsSync(ipv6db)) { console.warn(`[geodata] ip2region database files not found in ${dataDir}`) ip2Region = null return ip2Region } ip2Region = new IP2Region({ ipv4db, ipv6db }) return ip2Region } function uniqueNonEmptyValues(values: Array) { return Array.from(new Set(values .map(value => value?.trim()) .filter((value): value is string => !!value && value !== '0'))) }