人氣 53°c

調用純真IP庫獲取國家地區歸屬地

純真 IP 庫是騰訊 QQ 旗下的產品,開放給大眾使用的 IP 數據庫,純真 IP 庫是一個輕量級的 IP 數據庫,數據格式簡單,查詢速度快,功能全面,也是免費共享任用的,很適合 Web 應用;IP 查詢工具經常需要根據使用者的 IP 地址,獲取其地理位置資訊,例如國家、省份、城市等信息;純真 IP 庫(QQWry)是一個常用的 IP 地址資料庫,提供了豐富的 IP 位址與地理位置的映射關係, 本文將介紹如何使用 PHP 接入純真 IP 庫,並通過一個完整的案例演示如何實現 IP 位址的地理位置,從而調用純真 IP 庫獲取國家地區歸屬地等查詢。

純真 IP 庫 (QQWry.dat) 你可以前往純真網站或 GitHub 下載。

純真官方網站:cz88.net

純真 IP 社區版:cz88.net/geo-public

IP 查詢應用可利用純真 IP 庫的 DAT 數據格式,編寫一個 PHP 類來實現 IP 地址的查詢功能。 

完整代碼如下:

01. 將以下代碼儲存成「cz88.php」檔案文件

<?php
error_reporting(0); // 關閉錯誤報告

class QQWry {
   
    private $file; // IP數據庫文件路徑
    private $fd;   // 文件句柄

    private $total; // 總記錄數

    // 索引區
    private $indexStartOffset; // 索引區起始偏移量
    private $indexEndOffset;   // 索引區結束偏移量

    /**
     * 構造函數,初始化IP數據庫
     * @param string $file IP數據庫文件路徑
     * @throws Exception 文件不存在或不可讀時拋出異常
     */
    public function __construct($file) {
   
        if (!file_exists($file) || !is_readable($file)) {
   
            throw new Exception("{$file} does not exist, or is not readable");
        }
        $this->file = $file;
        $this->fd = fopen($file, 'rb');

        // 讀取索引區起始偏移量
        $this->indexStartOffset = $this->unpackLong($this->readOffset(4, 0));

        // 讀取索引區結束偏移量
        $this->indexEndOffset = $this->unpackLong($this->readOffset(4));

        // 計算總記錄數
        $this->total = ($this->indexEndOffset - $this->indexStartOffset) / 7 + 1;
    }

    /**
     * 查詢IP地址對應的地理位置
     * @param string $ip IP地址
     * @return array 包含國家和地區的數組
     * @throws Exception IP地址無效時拋出異常
     */
    public function query($ip) {
   
        // 驗證IP地址格式
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
   
            throw new Exception("{$ip} is not a valid IP address");
        }

        // 將IP地址轉換為整數
        $ipNum = ip2long($ip);

        // 查找IP所在的索引
        $ipFind = $this->find($ipNum, 0, $this->total);

        // 計算IP記錄的偏移量
        $ipOffset = $this->indexStartOffset + $ipFind * 7 + 4;
        $ipRecordOffset = $this->unpackLong($this->readOffset(3, $ipOffset) . chr(0));

        // 讀取並返回IP記錄
        return $this->readRecord($ipRecordOffset);
    }

    /**
     * 讀取IP記錄
     * @param int $offset 記錄偏移量
     * @return array 包含國家和地區的數組
     */
    private function readRecord($offset) {
   
        $record = ['', ''];

        $offset += 4;

        $flag = ord($this->readOffset(1, $offset));

        if ($flag == 1) {
   
            $locationOffset = $this->unpackLong($this->readOffset(3, $offset + 1) . chr(0));
            $subFlag = ord($this->readOffset(1, $locationOffset));

            if ($subFlag == 2) {
   
                // 國家
                $countryOffset = $this->unpackLong($this->readOffset(3, $locationOffset + 1) . chr(0));
                $record[0] = $this->readLocation($countryOffset);
                // 地區
                $record[1] = $this->readLocation($locationOffset + 4);
            } else {
   
                $record[0] = $this->readLocation($locationOffset);
                $record[1] = $this->readLocation($locationOffset + strlen($record[0]) + 1);
            }
        } elseif ($flag == 2) {
   
            // 地區
            $record[1] = $this->readLocation($offset + 4);

            // 國家
            $countryOffset = $this->unpackLong($this->readOffset(3, $offset + 1) . chr(0));
            $record[0] = $this->readLocation($countryOffset);
        } else {
   
            $record[0] = $this->readLocation($offset);
            $record[1] = $this->readLocation($offset + strlen($record[0]) + 1);
        }
        return $record;
    }

    /**
     * 讀取地區信息
     * @param int $offset 地區偏移量
     * @return string 地區信息
     */
    private function readLocation($offset) {
   
        if ($offset == 0) {
   
            return '';
        }

        $flag = ord($this->readOffset(1, $offset));

        // 出錯
        if ($flag == 0) {
   
            return '';
        }

        // 仍然為重定向
        if ($flag == 2) {
   
            $offset = $this->unpackLong($this->readOffset(3, $offset + 1) . chr(0));
            return $this->readLocation($offset);
        }

        $location = '';
        $chr = $this->readOffset(1, $offset);
        while (ord($chr) != 0) {
   
            $location .= $chr;
            $offset++;
            $chr = $this->readOffset(1, $offset);
        }
        return iconv('GBK', 'UTF-8//IGNORE', $location);
    }

    /**
     * 查找IP所在的索引
     * @param int $ipNum IP地址的整數表示
     * @param int $l 左邊界
     * @param int $r 右邊界
     * @return int 找到的索引
     */
    private function find($ipNum, $l, $r) {
   
        if ($l + 1 >= $r) {
   
            return $l;
        }
        $m = intval(($l + $r) / 2);

        $find = $this->readOffset(4, $this->indexStartOffset + $m * 7);
        $mIp = $this->unpackLong($find);

        if ($ipNum < $mIp) {
   
            return $this->find($ipNum, $l, $m);
        } else {
   
            return $this->find($ipNum, $m, $r);
        }
    }

    /**
     * 讀取指定偏移量的數據
     * @param int $numberOfBytes 要讀取的字節數
     * @param int|null $offset 偏移量
     * @return string 讀取的數據
     */
    private function readOffset($numberOfBytes, $offset = null) {
   
        if (!is_null($offset)) {
   
            fseek($this->fd, $offset);
        }
        return fread($this->fd, $numberOfBytes);
    }

    /**
     * 將4字節的字符串轉換為長整型
     * @param string $str 4字節的字符串
     * @return int 長整型
     */
    private function unpackLong($str) {
   
        return unpack('L', $str)[1];
    }

    /**
     * 析構函數,關閉文件句柄
     */
    public function __destruct() {
   
        if ($this->fd) {
   
            fclose($this->fd);
        }
    }
}
?>

02. 將以下代碼儲存成「index.php」檔案文件

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-TW">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>查詢IP位址</title>
</head>
<body>
<?php
function getClientIp() {
    $ipKeys = [
        'HTTP_CLIENT_IP', 
        'HTTP_X_FORWARDED_FOR', 
        'HTTP_X_REAL_IP', 
        'HTTP_CF_CONNECTING_IP', // 適配 Cloudflare
        'REMOTE_ADDR'
    ];

    foreach ($ipKeys as $key) {
        if (!empty($_SERVER[$key])) {
            // 有時標頭會包含多個逗號分隔的 IP,取第一個即可
            $ipList = explode(',', $_SERVER[$key]);
            foreach ($ipList as $ip) {
                $ip = trim($ip);
                // 驗證是否為有效的 IPv4 或 IPv6 
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }
    }
    return '0.0.0.0';
}
?>
<?php
require 'cz88.php';

try {
   
    // 初始化IP庫
    $qqwry = new QQWry('./src/qqwry.dat');

    // 查詢IP地址
    $ip = getClientIp(); // 要查詢的IP地址
    $result = $qqwry->query($ip);

    // 輸出結果
    echo "IP: {$ip} \n";
    echo "國家: {$result[0]} \n";
    echo "地區: {$result[1]} \n";
} catch (Exception $e) {
   
    echo "Error: " . $e->getMessage();
}
?>
</body>
</html>

提醒:其中「src/qqwry.dat」為放置純真 IP 的路徑,如有變更請自行修改相對路徑。

圖片預覧:

技術文檔:

01. PHP接入純真IP庫

02. 基於純真IP庫實現評論者IP歸屬地

純真 IP 查詢工具:

- IP歸屬地查詢源碼-支持IPV4/IPV6

- 查詢工具下載地址!

相關文章:
01. 純真QQIP數據庫下載
02 .網頁底部加入顯示IP﹑作業系統﹑網絡服務公司
標籤: