QQ登录

只需要一步,快速开始

APP扫码登录

只需要一步,快速开始

手机号码,快捷登录

手机号码,快捷登录

查看: 2026|回复: 0

[PHP] 基于纯真数据库的IP地址查询

[复制链接]

等级头衔

积分成就    金币 : 2857
   泡泡 : 1516
   精华 : 6
   在线时间 : 1314 小时
   最后登录 : 2025-1-19

丰功伟绩

优秀达人突出贡献荣誉管理论坛元老活跃会员

联系方式
发表于 2019-8-30 10:57:54 | 显示全部楼层 |阅读模式
       本源码模块会在第一次被调用时自动从纯真网下载最新的 IP 数据库到本地,因此第一次进行查询时会有点慢。如果你的服务器因为某些原因,无法连接到纯真网获取数据库,可以直接下载离线版,并将 IPQuery.class.php 第 25 行的 $dbExpires 值改为“0”(即永不自动更新数据库)。
( K& p* \  P. N/ U" H9 {模块代码:
. [5 h" t# g& B# q' z+ h9 T9 i
<?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 = 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;
    }
}
使用方法:) _; P1 m; @4 q
       将上面的模块代码保存为 IPQuery.class.php,然后按照如下方法调用即可:# w+ l$ C* E$ H/ D7 _* s- O
<?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>";
输出效果如下:
2 z* o/ J( D9 `1 H4 [ 20190707_005256_66.jpg * ^6 I3 E( I4 W* u
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|paopaomj.COM ( 渝ICP备18007172号|渝公网安备50010502503914号 )

GMT+8, 2025-1-19 23:14

Powered by paopaomj X3.5 © 2016-2025 sitemap

快速回复 返回顶部 返回列表