1. 程式人生 > 實用技巧 >基於本地資料庫的 IP 地址查詢 PHP 原始碼

基於本地資料庫的 IP 地址查詢 PHP 原始碼

之前介紹過很多第三方的 IP 地址查詢 API 介面,詳見:分享幾個IP獲取地理位置的API介面,直接呼叫第三方的介面很方便,但也容易失效導致無法使用。因此今天來分享一個基於本地資料庫的 IP 地址查詢原始碼!

模組程式碼

<?php
/**
 * 純真 IP 資料庫查詢 
 * 
 * 參考資料:
 * - 純真 IP 資料庫 http://www.cz88.net/ip/
 * - PHP 讀取純真IP地址資料庫 http://ju.outofmemory.cn/entry/42500
 * - 純真 IP 資料庫自動更新檔案教程 https://www.22vd.com/40035.html
 * - IpLocation https://github.com/nauxliu/IpLocation/
 * - 基於本地資料庫的 IP 地址查詢 PHP 原始碼 https://mkblog.cn/?p=1951
 * 
 * 使用示例:
 *   $ip = new IPQuery();
 *   $addr = $ip->query('IP地址');
 *   print_r($addr);
 
*/ class IPQuery { private $fh; // IP資料庫檔案控制代碼 private $first; // 第一條索引 private $last; // 最後一條索引 private $total; // 索引總數 private $dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'qqwry.dat'; // 純真 IP 資料庫檔案存放路徑 private $dbExpires = 86400 * 10; // 資料庫檔案有效期(10天)如無需自動更新 IP 資料庫,請將此值改為 0 // 建構函式
function __construct() { // IP 資料庫檔案不存在或已過期,則自動獲取 if(!file_exists($this->dbFile) || ($this->dbExpires && ((time() - filemtime($this->dbFile)) > $this->dbExpires))) { $this->update(); } } // 忽略超時 private function ignore_timeout() { @
ignore_user_abort(true); @ini_set('max_execution_time', 48 * 60 * 60); @set_time_limit(48 * 60 * 60); // set_time_limit(0) 2day @ini_set('memory_limit', '4000M');// 4G; } // 讀取little-endian編碼的4個位元組轉化為長整型數 private function getLong4() { $result = unpack('Vlong', fread($this->fh, 4)); return $result['long']; } // 讀取little-endian編碼的3個位元組轉化為長整型數 private function getLong3() { $result = unpack('Vlong', fread($this->fh, 3).chr(0)); return $result['long']; } // 查詢位置資訊 private function getPos($data = '') { $char = fread($this->fh, 1); while (ord($char) != 0) { // 地區資訊以 0 結束 $data .= $char; $char = fread($this->fh, 1); } return $data; } // 查詢運營商 private function getISP() { $byte = fread($this->fh, 1); // 標誌位元組 switch (ord($byte)) { case 0: $area = ''; break; // 沒有相關資訊 case 1: // 被重定向 fseek($this->fh, $this->getLong3()); $area = $this->getPos(); break; case 2: // 被重定向 fseek($this->fh, $this->getLong3()); $area = $this->getPos(); break; default: $area = $this->getPos($byte); break; // 沒有被重定向 } return $area; } // 檢查 IP 格式是否正確 public function checkIp($ip) { $arr = explode('.', $ip); if(count($arr) != 4) return false; for ($i = 0; $i < 4; $i++) { if ($arr[$i] < '0' || $arr[$i] > '255') { return false; } } return true; } // 查詢 IP 地址 public function query($ip) { if(!$this->checkIp($ip)) { return false; } $this->fh = fopen($this->dbFile, 'rb'); $this->first = $this->getLong4(); $this->last = $this->getLong4(); $this->total = ($this->last - $this->first) / 7; // 每條索引7位元組 $ip = pack('N', intval(ip2long($ip))); // 二分查詢 IP 位置 $l = 0; $r = $this->total; while($l <= $r) { $m = floor(($l + $r) / 2); // 計算中間索引 fseek($this->fh, $this->first + $m * 7); $beginip = strrev(fread($this->fh, 4)); // 中間索引的開始IP地址 fseek($this->fh, $this->getLong3()); $endip = strrev(fread($this->fh, 4)); // 中間索引的結束IP地址 if ($ip < $beginip) { // 使用者的IP小於中間索引的開始IP地址時 $r = $m - 1; } else { if ($ip > $endip) { // 使用者的IP大於中間索引的結束IP地址時 $l = $m + 1; } else { // 使用者IP在中間索引的IP範圍內時 $findip = $this->first + $m * 7; break; } } } // 查詢 IP 地址段 fseek($this->fh, $findip); $location['beginip'] = long2ip($this->getLong4()); // 使用者IP所在範圍的開始地址 $offset = $this->getlong3(); fseek($this->fh, $offset); $location['endip'] = long2ip($this->getLong4()); // 使用者IP所在範圍的結束地址 // 查詢 IP 資訊 $byte = fread($this->fh, 1); // 標誌位元組 switch (ord($byte)) { case 1: // 都被重定向 $countryOffset = $this->getLong3(); // 重定向地址 fseek($this->fh, $countryOffset); $byte = fread($this->fh, 1); // 標誌位元組 switch (ord($byte)) { case 2: // 資訊被二次重定向 fseek($this->fh, $this->getLong3()); $location['pos'] = $this->getPos(); fseek($this->fh, $countryOffset + 4); $location['isp'] = $this->getISP(); break; default: // 資訊沒有被二次重定向 $location['pos'] = $this->getPos($byte); $location['isp'] = $this->getISP(); break; } break; case 2: // 資訊被重定向 fseek($this->fh, $this->getLong3()); $location['pos'] = $this->getPos(); fseek($this->fh, $offset + 8); $location['isp'] = $this->getISP(); break; default: // 資訊沒有被重定向 $location['pos'] = $this->getPos($byte); $location['isp'] = $this->getISP(); break; } // 資訊轉碼處理 foreach ($location as $k => $v) { $location[$k] = iconv('gb2312', 'utf-8', $v); $location[$k] = preg_replace(array('/^.*CZ88\.NET.*$/isU', '/^.*純真.*$/isU', '/^.*日IP資料/'), '', $location[$k]); $location[$k] = htmlspecialchars($location[$k]); } return $location; } // 更新資料庫 https://www.22vd.com/40035.html public function update() { $this->ignore_timeout(); $copywrite = file_get_contents('http://update.cz88.net/ip/copywrite.rar'); $qqwry = file_get_contents('http://update.cz88.net/ip/qqwry.rar'); $key = unpack('V6', $copywrite)[6]; for($i = 0; $i < 0x200; $i++) { $key *= 0x805; $key ++; $key = $key & 0xFF; $qqwry[$i] = chr(ord($qqwry[$i]) ^ $key); } $qqwry = gzuncompress($qqwry); file_put_contents($this->dbFile, $qqwry); } // 解構函式 function __destruct() { if($this->fh) { fclose($this->fh); } $this->fp = null; } }

使用方法

將上面的模組程式碼儲存為IPQuery.class.php,然後按照如下方法呼叫即可:

<?php
require_once('IPQuery.class.php');
 
$ip = new IPQuery();
$addr = $ip->query('123.233.233.233');
 
echo "<pre>
IP起始段:{$addr['beginip']}
IP結束段:{$addr['endip']}
實際地址:{$addr['pos']}
運 營 商:{$addr['isp']}
</pre>";

輸出效果如下所示:

注意事項

本模組會在第一次被呼叫時自動從純真網下載最新的 IP 資料庫到本地,因此第一次進行查詢時會有點慢。如果你的伺服器因為某些原因,無法連線到純真網獲取資料庫,可以直接下載離線版,並將IPQuery.class.php第 25 行的$dbExpires值改為“0”(即永不自動更新資料庫)。

轉載:本文作者為mengkun

https://mkblog.cn/1951/