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

时间:2018-08-20 17:09:02

标签: php sql geoip

以前,我曾经使用php中的一些简单功能访问Geomin数据库的Maxmind本地副本。

基本上,他们的数据库具有类似以下的模式:

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
);

以类似于 120.120.120.120/8 的方式(如CIDR地址)压缩网络的地方。而没有StartIpNumEndIpNum

您可以在图像中看到它:

Network is expressed now in CIDR way

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

1 个答案:

答案 0 :(得分: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。这只是一种粗略且并非完全有效的方法,但是如果您现在需要解决方案,它就会起作用。