TypeScript中通过单个IPv4/IPv6地址查询匹配对应的CIDR键
实现思路
- 预加工所有CIDR存储数据:将每个CIDR键解析为IP版本(v4/v6)、网段起始值、网段结束值三个核心字段,同时按前缀长度从大到小排序,确保最长前缀优先匹配(网段范围越小优先级越高)。
- 查询时先将待查IP转换为对应版本的数值格式,遍历预加工好的CIDR列表,判断IP数值是否落在当前CIDR的网段区间内,第一个匹配到的结果即为最优解,直接返回关联数据即可。
- IPv6地址长度为128位,需使用
BigInt类型存储数值,避免普通number类型的精度丢失问题。
完整TypeScript实现(无需第三方依赖)
// 解析IP为数值,v4返回number,v6返回BigInt function ipToNum(ip: string): number | bigint { // 判断是否为IPv4 if (ip.includes('.')) { return ip.split('.').reduce((sum, octet, idx) => { return sum + (parseInt(octet) << (24 - idx * 8)) }, 0) } // 处理IPv6 let parts = ip.split(':') // 处理::压缩位 const emptyIdx = parts.indexOf('') if (emptyIdx !== -1) { const fillCount = 8 - parts.length + 1 parts = [ ...parts.slice(0, emptyIdx), ...Array(fillCount).fill('0'), ...parts.slice(emptyIdx + 1) ] } let num = 0n for (let i = 0; i < 8; i++) { const block = parseInt(parts[i] || '0', 16) num = (num << 16n) + BigInt(block) } return num } // 解析CIDR为网段信息 interface ParsedCidr { version: 4 | 6 start: number | bigint end: number | bigint prefix: number data: any } function parseCidr(cidr: string, data: any): ParsedCidr { const [ipPart, prefixStr] = cidr.split('/') const prefix = parseInt(prefixStr) const ipNum = ipToNum(ipPart) let start: number | bigint, end: number | bigint if (typeof ipNum === 'number') { // IPv4处理 const mask = 0xffffffff << (32 - prefix) start = ipNum & mask end = start | (~mask >>> 0) return { version: 4, start, end, prefix, data } } else { // IPv6处理 const mask = (0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffffn << BigInt(128 - prefix)) start = ipNum & mask end = start | (~mask & 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffffn) return { version: 6, start, end, prefix, data } } } // CIDR匹配器类 class CidrMatcher { private parsedV4Cidrs: ParsedCidr[] = [] private parsedV6Cidrs: ParsedCidr[] = [] // 批量加载CIDR数据 loadCidrs(cidrMap: Record<string, any>) { const v4List: ParsedCidr[] = [] const v6List: ParsedCidr[] = [] for (const [cidr, data] of Object.entries(cidrMap)) { const parsed = parseCidr(cidr, data) parsed.version === 4 ? v4List.push(parsed) : v6List.push(parsed) } // 按前缀长度降序排序,优先匹配最长前缀 this.parsedV4Cidrs = v4List.sort((a, b) => b.prefix - a.prefix) this.parsedV6Cidrs = v6List.sort((a, b) => b.prefix - a.prefix) } // 查询IP对应的数据 match(ip: string): any | null { const ipNum = ipToNum(ip) const list = typeof ipNum === 'number' ? this.parsedV4Cidrs : this.parsedV6Cidrs for (const cidr of list) { if (ipNum >= cidr.start && ipNum <= cidr.end) { return cidr.data } } return null } } // 示例用法 const matcher = new CidrMatcher() // 加载你的存储数据 matcher.loadCidrs({ "103.21.244.0/24": "data, lorem ipsum, etc", // 其他CIDR数据... }) // 查询IP const result = matcher.match("103.21.244.1") console.log(result) // 输出:data, lorem ipsum, etc
优化建议
- 预加载解析步骤仅需在项目启动、或CIDR存储数据更新时执行一次,无需每次查询重复解析,万级以内CIDR数据量下线性遍历查询性能完全满足业务需求。
- 若CIDR数据量超过10万条,可以将预解析的CIDR数据改为前缀树(Trie)结构存储,查询时间复杂度可降低到O(32)(IPv4)或O(128)(IPv6),进一步提升查询效率。
- 如果不需要最长前缀匹配规则,可自行调整
loadCidrs方法中的排序逻辑,改成你需要的优先级即可。
内容的提问来源于stack exchange,提问作者Mecanik
相关产品推荐
相关产品推荐

