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

如何用PHP&PDO结合GeoIP2数据库实现IP地址国家定位查询

要实现这个小型国家位置检测功能,核心是把用户的IPv4地址转换成整数,然后在geoip2数据库中找到包含该IP的最精确CIDR网段(也就是最长前缀匹配)。我来给你一步步拆解PHP+PDO的实现方案:

1. 先处理用户IP的合法性与转换

首先要确保获取到的IP是有效的IPv4地址,然后把它转换成和MySQLINET_ATON()输出一致的无符号整数,避免符号差异导致的匹配错误:

// 获取你通过环境变量拿到的IP
$userIp = getenv('YOUR_IP_ENV_VAR'); 

// 验证IPv4合法性,过滤无效IP
if (!filter_var($userIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
    die('Invalid IPv4 address provided');
}

// 转成无符号整数,和MySQL的INET_ATON结果对齐
$ipInt = sprintf('%u', ip2long($userIp));

2. 数据库查询的核心逻辑

geoip2的network字段是CIDR格式(比如41.74.160.0/20),我们需要判断用户IP是否落在该网段的范围内,并且优先匹配掩码最长的网段——因为更长的掩码对应更精确的子网,是我们要找的最匹配记录。

方案一:直接查询(适合小数据量)

如果你的数据库数据量不大,可以直接在SQL中实时计算网段的起始/结束IP:

// 初始化PDO连接(替换成你的数据库信息)
$dsn = 'mysql:host=localhost;dbname=your_database;charset=utf8mb4';
try {
    $pdo = new PDO($dsn, 'your_username', 'your_password');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die('Database connection failed: ' . $e->getMessage());
}

// 准备查询语句
$sql = <<<SQL
SELECT 
    network,
    geoname_id,
    continent_code,
    continent_name,
    country_iso_code,
    country_name,
    is_anonymous_proxy,
    is_satellite_provider
FROM geoip2
WHERE INET_ATON(:ip) BETWEEN 
    INET_ATON(SUBSTRING_INDEX(network, '/', 1)) 
    AND 
    (INET_ATON(SUBSTRING_INDEX(network, '/', 1)) + (POWER(2, 32 - SUBSTRING_INDEX(network, '/', -1)) - 1))
ORDER BY SUBSTRING_INDEX(network, '/', -1) DESC
LIMIT 1;
SQL;

// 执行查询并绑定参数
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':ip', $userIp, PDO::PARAM_STR);
$stmt->execute();

// 获取匹配结果
$match = $stmt->fetch(PDO::FETCH_ASSOC);

方案二:预计算字段优化(适合大数据量)

如果数据库里的记录很多,每次查询都计算网段范围会拖慢速度。建议预先计算三个字段并建立索引,大幅提升查询效率:

-- 新增用于快速匹配的字段
ALTER TABLE geoip2 
ADD COLUMN start_ip INT UNSIGNED NOT NULL,
ADD COLUMN end_ip INT UNSIGNED NOT NULL,
ADD COLUMN prefix_length TINYINT UNSIGNED NOT NULL;

-- 批量计算各网段的起始IP、结束IP和掩码长度
UPDATE geoip2 
SET 
    start_ip = INET_ATON(SUBSTRING_INDEX(network, '/', 1)),
    prefix_length = SUBSTRING_INDEX(network, '/', -1),
    end_ip = INET_ATON(SUBSTRING_INDEX(network, '/', 1)) + (POWER(2, 32 - SUBSTRING_INDEX(network, '/', -1)) - 1);

-- 建立复合索引,让IP范围查询更快
CREATE INDEX idx_ip_range ON geoip2 (start_ip, end_ip, prefix_length);

优化后的查询语句会更简洁高效:

$sql = <<<SQL
SELECT * FROM geoip2
WHERE INET_ATON(:ip) BETWEEN start_ip AND end_ip
ORDER BY prefix_length DESC
LIMIT 1;
SQL;

3. 处理查询结果

最后可以根据业务需求使用匹配到的位置数据:

if ($match) {
    echo "IP位置检测结果:\n";
    echo "所属大陆:{$match['continent_name']}({$match['continent_code']})\n";
    echo "所属国家:{$match['country_name']}({$match['country_iso_code']})\n";
    // 其他字段如代理标记、卫星提供商标记等按需使用
} else {
    echo "未找到该IP对应的位置记录";
}

注意事项

  • 确保你的PHP环境支持ip2long()和filter_var()——这些都是PHP核心函数,默认环境都支持。
  • 如果后续需要处理IPv6,这个方案需要调整,但你的示例数据是IPv4,暂时不用考虑。
  • 记得定期更新geoip2数据库,保证位置数据的准确性。

内容的提问来源于stack exchange,提问作者delato468

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:41:37