使用 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 的方式显示......并且没有 StartIpNum
和 EndIpNum
你可以在图片中看到它:
现在我无法通过StartIPNum
和EndIpNum
进行搜索,我该如何进行查询?
【问题讨论】:
你的 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 IP Detect 使用 php 和 mysql 数据库