You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 00:24:03