基于-纯-真-本地数据库的 IP 地址查询 PHP 源码

举报
lxw1844912514 发表于 2022/08/26 23:31:17 2022/08/26
【摘要】 很多第三方的 IP 地址查询 API 接口,直接调用第三方的接口很方便,但也容易失效导致无法使用。因此今天来分享一个基于本地数据库的 IP 地址查询源码(亲测可行)! 模块代码 <?php/** * 纯真 IP 数据库查询 * 使用示例: *...

很多第三方的 IP 地址查询 API 接口,直接调用第三方的接口很方便,但也容易失效导致无法使用。因此今天来分享一个基于本地数据库的 IP 地址查询源码(亲测可行)!

模块代码


   
  1. <?php
  2. /**
  3.  * 纯真 IP 数据库查询
  4.  * 使用示例:
  5.  *   $ip = new IPQuery();
  6.  *   $addr = $ip->query('IP地址');
  7.  *   print_r($addr);
  8.  */
  9. class IPQuery
  10. {
  11.     private $fh;        // IP数据库文件句柄
  12.     private $first;     // 第一条索引
  13.     private $last;      // 最后一条索引
  14.     private $total;     // 索引总数
  15.     private $dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'qqwry.dat';      // 纯真 IP 数据库文件存放路径
  16.     private $dbExpires = 86400 * 10;        // 数据库文件有效期(10天)如无需自动更新 IP 数据库,请将此值改为 0
  17.     // 构造函数
  18.     function __construct()
  19.     {
  20.         // IP 数据库文件不存在或已过期,则自动获取
  21.         if (!file_exists($this->dbFile) || ($this->dbExpires && ((time() - filemtime($this->dbFile)) > $this->dbExpires))) {
  22.             $this->update();
  23.         }
  24.     }
  25.     // 忽略超时
  26.     private function ignore_timeout()
  27.     {
  28.         @ignore_user_abort(true);
  29.         @ini_set('max_execution_time'48 * 60 * 60);
  30.         @set_time_limit(48 * 60 * 60);    // set_time_limit(0)  2day
  31.         @ini_set('memory_limit''4000M');// 4G;
  32.     }
  33.     // 读取little-endian编码的4个字节转化为长整型数
  34.     private function getLong4()
  35.     {
  36.         $result = unpack('Vlong', fread($this->fh, 4));
  37.         return $result['long'];
  38.     }
  39.     // 读取little-endian编码的3个字节转化为长整型数
  40.     private function getLong3()
  41.     {
  42.         $result = unpack('Vlong', fread($this->fh, 3) . chr(0));
  43.         return $result['long'];
  44.     }
  45.     // 查询位置信息
  46.     private function getPos($data = '')
  47.     {
  48.         $char = fread($this->fh, 1);
  49.         while (ord($char) != 0) {   // 地区信息以 0 结束
  50.             $data .= $char;
  51.             $char = fread($this->fh, 1);
  52.         }
  53.         return $data;
  54.     }
  55.     // 查询运营商
  56.     private function getISP()
  57.     {
  58.         $byte = fread($this->fh, 1);    // 标志字节
  59.         switch (ord($byte)) {
  60.             case 0:
  61.                 $area = '';
  62.                 break;  // 没有相关信息
  63.             case 1// 被重定向
  64.                 fseek($this->fh, $this->getLong3());
  65.                 $area = $this->getPos();
  66.                 break;
  67.             case 2// 被重定向
  68.                 fseek($this->fh, $this->getLong3());
  69.                 $area = $this->getPos();
  70.                 break;
  71.             default:
  72.                 $area = $this->getPos($byte);
  73.                 break;     // 没有被重定向
  74.         }
  75.         return $area;
  76.     }
  77.     // 检查 IP 格式是否正确
  78.     public function checkIp($ip)
  79.     {
  80.         $arr = explode('.', $ip);
  81.         if (count($arr) != 4return false;
  82.         for ($i = 0; $i < 4; $i++) {
  83.             if ($arr[$i] < '0' || $arr[$i] > '255') {
  84.                 return false;
  85.             }
  86.         }
  87.         return true;
  88.     }
  89.     // 查询 IP 地址
  90.     public function query($ip)
  91.     {
  92.         if (!$this->checkIp($ip)) {
  93.             return false;
  94.         }
  95.         $this->fh = fopen($this->dbFile, 'rb');
  96.         $this->first = $this->getLong4();
  97.         $this->last = $this->getLong4();
  98.         $this->total = ($this->last - $this->first) / 7;    // 每条索引7字节
  99.         $ip = pack('N', intval(ip2long($ip)));
  100.         // 二分查找 IP 位置
  101.         $l = 0;
  102.         $r = $this->total;
  103.         while ($l <= $r) {
  104.             $m = floor(($l + $r) / 2);     // 计算中间索引
  105.             fseek($this->fh, $this->first + $m * 7);
  106.             $beginip = strrev(fread($this->fh, 4)); // 中间索引的开始IP地址
  107.             fseek($this->fh, $this->getLong3());
  108.             $endip = strrev(fread($this->fh, 4));   // 中间索引的结束IP地址
  109.             if ($ip < $beginip) {   // 用户的IP小于中间索引的开始IP地址时
  110.                 $r = $m - 1;
  111.             } else {
  112.                 if ($ip > $endip) { // 用户的IP大于中间索引的结束IP地址时
  113.                     $l = $m + 1;
  114.                 } else {            // 用户IP在中间索引的IP范围内时
  115.                     $findip = $this->first + $m * 7;
  116.                     break;
  117.                 }
  118.             }
  119.         }
  120.         // 查找 IP 地址段
  121.         fseek($this->fh, $findip);
  122.         $location['beginip'] = long2ip($this->getLong4());   // 用户IP所在范围的开始地址
  123.         $offset = $this->getlong3();
  124.         fseek($this->fh, $offset);
  125.         $location['endip'] = long2ip($this->getLong4());     // 用户IP所在范围的结束地址
  126.         // 查找 IP 信息
  127.         $byte = fread($this->fh, 1); // 标志字节
  128.         switch (ord($byte)) {
  129.             case 1:  // 都被重定向
  130.                 $countryOffset = $this->getLong3(); // 重定向地址
  131.                 fseek($this->fh, $countryOffset);
  132.                 $byte = fread($this->fh, 1); // 标志字节
  133.                 switch (ord($byte)) {
  134.                     case 2// 信息被二次重定向
  135.                         fseek($this->fh, $this->getLong3());
  136.                         $location['pos'] = $this->getPos();
  137.                         fseek($this->fh, $countryOffset + 4);
  138.                         $location['isp'] = $this->getISP();
  139.                         break;
  140.                     default// 信息没有被二次重定向
  141.                         $location['pos'] = $this->getPos($byte);
  142.                         $location['isp'] = $this->getISP();
  143.                         break;
  144.                 }
  145.                 break;
  146.             case 2// 信息被重定向
  147.                 fseek($this->fh, $this->getLong3());
  148.                 $location['pos'] = $this->getPos();
  149.                 fseek($this->fh, $offset + 8);
  150.                 $location['isp'] = $this->getISP();
  151.                 break;
  152.             default// 信息没有被重定向
  153.                 $location['pos'] = $this->getPos($byte);
  154.                 $location['isp'] = $this->getISP();
  155.                 break;
  156.         }
  157.         // 信息转码处理
  158.         foreach ($location as $k => $v) {
  159.             $location[$k] = iconv('gb2312''utf-8', $v);
  160.             $location[$k] = preg_replace(array('/^.*CZ88\.NET.*$/isU''/^.*纯真.*$/isU''/^.*日IP数据/'), '', $location[$k]);
  161.             $location[$k] = htmlspecialchars($location[$k]);
  162.         }
  163.         return $location;
  164.     }
  165.     // 更新数据库 https://www.22vd.com/40035.html
  166.     public function update()
  167.     {
  168.         $this->ignore_timeout();
  169.         $copywrite = file_get_contents('http://update.cz88.net/ip/copywrite.rar');
  170.         $qqwry = file_get_contents('http://update.cz88.net/ip/qqwry.rar');
  171.         $key = unpack('V6', $copywrite)[6];
  172.         for ($i = 0; $i < 0x200; $i++) {
  173.             $key *= 0x805;
  174.             $key++;
  175.             $key = $key & 0xFF;
  176.             $qqwry[$i] = chr(ord($qqwry[$i]) ^ $key);
  177.         }
  178.         $qqwry = gzuncompress($qqwry);
  179.         file_put_contents($this->dbFile, $qqwry);
  180.     }
  181.     // 析构函数
  182.     function __destruct()
  183.     {
  184.         if ($this->fh) {
  185.             fclose($this->fh);
  186.         }
  187.         $this->fp = null;
  188.     }
  189. }

使用方法

将上面的模块代码保存为 IPQuery.class.php,然后按照如下方法调用即可:


   
  1. require_once('IPQuery.class.php');
  2. $ip = new IPQuery();
  3. $addr = $ip->query('123.223.233.233');
  4. echo "<pre>
  5. IP起始段:{$addr['beginip']}
  6. IP结束段:{$addr['endip']}
  7. 实际地址:{$addr['pos']}
  8. 运 营 商:{$addr['isp']}
  9. </pre>";

输出效果如下所示:

68bbda702fdb33b6cfea3e8d12c665b1.png

注意事项

本模块会在第一次被调用时自动从纯真网下载最新的 IP 数据库到本地,因此第一次进行查询时会有点慢。如果你的服务器因为某些原因,无法连接到纯真网获取数据库,可以直接下载离线版,并将 IPQuery.class.php 第 25 行的 $dbExpires 值改为“0”(即永不自动更新数据库)。

在[ 码农编程进阶笔记 ]公众号回复【激活码】关键字,获取最新[全家桶][webstorm][pycharm]激活码百度盘链接

文章来源: blog.csdn.net,作者:lxw1844912514,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/lxw1844912514/article/details/126515923

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。