PHP通过ip2long计算子网IPv4长整型范围 快速判断用户IP是否属欧盟网段
PHP判断用户IP是否属于欧盟IPv4网段最优方案
你的思路完全可行
你想到的将CIDR转成起止长整型再通过数据库范围查询的方案,是当前场景下效率最高的实现,完全避免了逐行遍历10.7万条记录的性能损耗,实际使用中单请求查询耗时可以控制在1ms以内,不会拖慢任何用户的访问速度。
1. 计算CIDR对应的起止长整型值
首先要注意:存储IP长整型的字段必须用INT UNSIGNED类型,避免超过有符号INT最大值导致溢出。
PHP计算单个CIDR起止值的代码:
function cidr_to_ip_range(string $cidr): array|false { list($ip, $mask) = explode('/', $cidr); if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || $mask < 0 || $mask > 32) { return false; } $ipLong = ip2long($ip); $hostCount = 1 << (32 - $mask); $start = $ipLong & (~($hostCount - 1)); $end = $start + $hostCount - 1; return [$start, $end]; }
你可以用这个函数批量处理现有10.7万条记录,写入新增的ip_start和ip_end字段。
也可以直接在MySQL中批量更新:
首先给表新增字段:
ALTER TABLE `你的表名` ADD COLUMN `ip_start` INT UNSIGNED NOT NULL DEFAULT 0, ADD COLUMN `ip_end` INT UNSIGNED NOT NULL DEFAULT 0;
然后执行批量更新(注意替换你的表名和存储CIDR的字段名):
UPDATE `你的表名` SET ip_start = INET_ATON(SUBSTRING_INDEX(cidr, '/', 1)) & (~(pow(2, 32 - SUBSTRING_INDEX(cidr, '/', -1)) - 1)), ip_end = INET_ATON(SUBSTRING_INDEX(cidr, '/', 1)) & (~(pow(2, 32 - SUBSTRING_INDEX(cidr, '/', -1)) - 1)) + pow(2, 32 - SUBSTRING_INDEX(cidr, '/', -1)) -1;
2. 加索引提升查询性能
给两个字段加联合索引,范围查询效率会提升百倍以上:
ALTER TABLE `你的表名` ADD INDEX `idx_ip_range` (`ip_start`, `ip_end`);
3. PHP查询逻辑示例
// 获取用户IP $userIp = $_SERVER['REMOTE_ADDR']; // 仅处理IPv4,IPv6可根据你的业务需求补充逻辑 if (!filter_var($userIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { $isEu = false; } else { $userIpLong = ip2long($userIp); // 这里用PDO示例,避免SQL注入 $pdo = new PDO('mysql:host=你的主机;dbname=你的库;charset=utf8mb4', '用户名', '密码'); $stmt = $pdo->prepare("SELECT 1 FROM `你的表名` WHERE ip_start <= ? AND ip_end >= ? LIMIT 1"); $stmt->execute([$userIpLong, $userIpLong]); $isEu = (bool)$stmt->fetchColumn(); } // 后续逻辑:如果$isEu为true就展示Cookie同意弹窗
可选优化
如果访问量较高,可以额外加一层缓存:把已经查询过的IP的判断结果存在Redis或者本地文件缓存中,缓存有效期设置为7天,重复IP访问无需再查询数据库,性能可以进一步提升。
内容的提问来源于stack exchange,提问作者Bill Demaria
相关产品推荐
相关产品推荐

