直接用$group对IP字段分组失效,因IP为字符串类型,需先提取网段(如IPv4前三段)再分组;IPv4可用$regexFind捕获,IPv6需分流处理;分组后须$project重命名、$sort排序才能完成统计。

为什么直接用 $group 对 IP 字段分组会失效
IP 地址(如 "192.168.3.105")在 MongoDB 中是字符串类型,直接按完整值 $group 会导致每个 IP 都算作独立分组,无法实现“按网段统计”的目标。更麻烦的是,不同长度的 IP(如 IPv4 vs IPv6)、带端口("192.168.3.105:8080")、含空格或前导零的写法,都会让正则或子串提取出错。
用 $substrCP + $indexOfCP 提取 IPv4 前三段(/24 掩码)
对标准 IPv4(点分十进制),最稳定的方式是定位第 3 个 . 的位置,再截取其前全部字符。注意必须用 $substrCP(按 Unicode 码点截取),避免中文或 emoji 导致字节偏移错乱。
-
$indexOfCP找第 3 个点:先用$indexOfCP找第 1 个点,再从该位置后找第 2 个,再往后找第 3 个;嵌套写法较冗长但可靠 - 更实用的替代:用
$regexFind(MongoDB 4.2+)一次性匹配^(d+.d+.d+).,捕获组$1就是前三段 - 示例管道片段:
{ $project: { ip_segment: { $regexFind: { input: "$ip", regex: "^(d+.d+.d+)." } } } }, { $project: { ip_segment: "$ip_segment.captures.0" } }
处理 IPv6 或混合 IP 格式时改用 $cond + $regexMatch 分流
IPv6(如 "2001:db8::1")无法用点号切分。若集合中同时存在 IPv4 和 IPv6,硬统一处理会失败。必须先判断格式,再走不同路径:
- 用
$regexMatch判断是否为 IPv4:{ $regexMatch: { input: "$ip", regex: "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$" } } - IPv4 走
$regexFind提取 /24 段;IPv6 可按/64掩码取前 4 组(用类似正则或$split+$arrayElemAt) - 关键陷阱:正则中点号
.必须双反斜杠转义,否则匹配任意字符;$split对 IPv6 的::处理不可靠,不建议用于压缩格式
$group 后加 $sort 和 $count 才算完成统计
只做分组不等于得到统计结果。常见遗漏是忘了把分组键映射成易读字段,或没控制输出顺序:
- 务必用
$project把_id重命名为segment,否则结果里全是{"_id":"192.168.3","count":42}这种难读结构 - 加
$sort: { count: -1 }才能看出哪些网段访问最密集;漏掉这步,结果顺序随机,不利于排查异常流量 - 如果要统计「单个 IP 出现次数」而非网段,直接
$group: { _id: "$ip" }即可,但需确认字段名确实是ip而非client_ip或其他别名
真正容易被忽略的是 IPv4 正则里的数字范围校验——d{1,3} 会匹配 999,但合法 IP 每段只能是 0–255。生产环境若需严格校验,得用四个 $and 套 $gte/$lte,或者改用应用层预处理。

















