基於本地數據庫的 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] = iconv('GBK', '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/

 純真ip庫下載:https://files.cnblogs.com/files/yehuisir/qqwry.zip

 https://files.cnblogs.com/files/yehuisir/qqwry_lastest.zip

 

純真ip庫下載 https://github.com/out0fmemory/qqwry.dat


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM