使用 PHP 从保存在本地的 GeoIP 数据库中获取数据

Posted

技术标签:

【中文标题】使用 PHP 从保存在本地的 GeoIP 数据库中获取数据【英文标题】:Fetch data from GeoIP database saved in local using PHP 【发布时间】:2018-08-20 17:09:01 【问题描述】:

前段时间我用一些简单的php函数访问Maxmind本地的GeoIP数据库副本。

基本上他们的数据库是这样的架构:

CREATE TABLE `geoip_city_blocks` (
    `startIpNum` INT(10) UNSIGNED NOT NULL,
    `endIpNum` INT(10) UNSIGNED NOT NULL,
    `locId` INT(10) UNSIGNED NOT NULL,
    PRIMARY KEY (`startIpNum`, `endIpNum`),
    INDEX `startIpNum` (`startIpNum`),
    INDEX `endIpNum` (`endIpNum`),
    INDEX `locId` (`locId`)
)

要获取某个 IP 的国家/城市信息,您只需将其转换为对应的数字,并带有:

$numeric_ip = ip2num($ip);

ip2num() 是:

function ip2num($ip) 

        $ip = explode(".",$ip);
        return (( (int) $ip[0] ) * 16777216) + (( (int) $ip[1] ) * 65536) + (( (int) $ip[2] ) * 256) + (( (int) $ip[3] ) * 1);

    

然后投射一个简单的查询:

SELECT * FROM geoip_city_blocks AS blocks LEFT JOIN geoip_city_locations AS locations ON (blocks.locId = locations.locId) WHERE ".$numeric_ip." >= startIpNum AND ".$numeric_ip." <= endIpNum LIMIT 1

这很好,因为对于任何数据库 mysql、SQLite、Postgre.. 等等,您都可以对该查询进行 CAS 查询并只比较 2 个整数..


使用新版本的 GeoIP,您将拥有这种新的架构:

CREATE TABLE blocks(
  "network" TEXT,
  "geoname_id" TEXT,
  "registered_country_geoname_id" TEXT,
  "represented_country_geoname_id" TEXT,
  "is_anonymous_proxy" TEXT,
  "is_satellite_provider" TEXT
);

网络以类似于 CIDR 地址的 120.120.120.120/8 的方式显示......并且没有 StartIpNumEndIpNum

你可以在图片中看到它:

现在我无法通过StartIPNumEndIpNum 进行搜索,我该如何进行查询?

【问题讨论】:

你的 DBMS 是什么? 看这个话题:***.com/questions/45656070/… @stickybit 我同时使用 MySQL 和另一组依赖 SQLite 的脚本(最后,由于各种原因,我使用越来越多的 SQLite)。它们都具有相同的表架构。 【参考方案1】:

这是我自己使用的解决方法:

没有办法知道网络是什么,所以最简单的解决方案是查看 IP 地址,就好像它的格式如下:a.b.c.d 可能是 24.185.38.192 其中 a = 24,c = 38,等等

得到一个只包含a、b、c的子串:

$ip_substring = 'a.b.c.';

然后执行一个 do-while 循环,该循环将在返回有效值或检查所有网络时结束。该查询将在数据库中搜索任何值,例如带有 % 通配符的子字符串,以包含任何主机和掩码:

$ip_substring .= '%';
$search_ip = (string)$ip_substring;

$sql = "SELECT * FROM blocks WHERE network LIKE $search_ip";

执行此查询后,将结果捕获为一个数组,通过它进行遍历,然后您可以检查每个单独的网络以查看您的 ip 是否存在于其中:

foreach ( $result as $r ) 
     $network = $r['network'];

     // converts the network (CIDR) to an ip range
     $range = array();
     $cidr = explode( '/', $network );
     $range[0] = long2ip( ( ip2long( $cidr[0] )) & (( -1 << ( 32 - (int)$cidr[1] ) )) );
     $range[1] = long2ip( ( ip2long( $range[0] )) + pow( 2, (32 - (int)$cidr[1])) - 1 );

     // check that ip is within range
     if ( ( ip2long( $ip ) >= ip2long( $range[0] ) ) &&
          ( ip2long( $ip ) <= ip2long( $range[1] ) ) ) 
          // you can use the associated postal_code etc this is the row you were looking for
          echo $r['postal_code'];
     

有一些逻辑来处理您的 foreach 没有返回有效网络的实例,在这种情况下,您需要通过删除 ip_substring 中的最后一个字符来扩大搜索范围:

含义:在最后一次循环中,您搜索了24.185.38 并没有返回任何内容,那么这次将您的搜索范围扩大到24.185.3。这只是一种粗略且不完全有效的方法,但如果您现在需要解决方案,它就可以工作。

【讨论】:

以上是关于使用 PHP 从保存在本地的 GeoIP 数据库中获取数据的主要内容,如果未能解决你的问题,请参考以下文章

本地主机中的 GeoIP

我应该保存 GeoIP 数据吗

PHP 从 IP 地址获取位置 MaxMind GEOIP

Geoip IP Detect 使用 php 和 mysql 数据库

Rails GeoLiteCity GeoIP 在本地开发环境中不起作用

从 php url 获取数据以在 react native 中保存到本地 sqlite