QQ登录

只需要一步,快速开始

APP扫码登录

只需要一步,快速开始

手机号码,快捷登录

手机号码,快捷登录

查看: 1987|回复: 0

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

[复制链接]

等级头衔

积分成就    金币 : 2841
   泡泡 : 1516
   精华 : 6
   在线时间 : 1295 小时
   最后登录 : 2024-11-23

丰功伟绩

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

联系方式
发表于 2019-8-30 10:57:54 | 显示全部楼层 |阅读模式
       本源码模块会在第一次被调用时自动从纯真网下载最新的 IP 数据库到本地,因此第一次进行查询时会有点慢。如果你的服务器因为某些原因,无法连接到纯真网获取数据库,可以直接下载离线版,并将 IPQuery.class.php 第 25 行的 $dbExpires 值改为“0”(即永不自动更新数据库)。
9 Q, i) A# E6 e/ K  a8 D模块代码:
! H; h$ x5 I/ B) {
  1. <?php
  2. /**
  3. * 纯真 IP 数据库查询
  4. *
  5. * 参考资料:
  6. * - 纯真 IP 数据库 http://www.cz88.net/ip/
  7. * - PHP 读取纯真IP地址数据库 http://ju.outofmemory.cn/entry/42500
  8. * - 纯真 IP 数据库自动更新文件教程 https://www.22vd.com/40035.html
  9. * - IpLocation https://github.com/nauxliu/IpLocation/
  10. *
  11. * 使用示例:
  12. *   $ip = new IPQuery();
  13. *   $addr = $ip->query('IP地址');
  14. *   print_r($addr);
  15. */
  16. class IPQuery {
  17.     private $fh;        // IP数据库文件句柄
  18.     private $first;     // 第一条索引
  19.     private $last;      // 最后一条索引
  20.     private $total;     // 索引总数
  21.     private $dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'qqwry.dat';      // 纯真 IP 数据库文件存放路径
  22.     private $dbExpires = 86400 * 10;        // 数据库文件有效期(10天)如无需自动更新 IP 数据库,请将此值改为 0
  23.     // 构造函数
  24.     function __construct() {
  25.         // IP 数据库文件不存在或已过期,则自动获取
  26.         if(!file_exists($this->dbFile) || ($this->dbExpires && ((time() - filemtime($this->dbFile)) > $this->dbExpires))) {
  27.             $this->update();
  28.         }
  29.     }
  30.    
  31.     // 忽略超时
  32.     private function ignore_timeout() {
  33.         @ignore_user_abort(true);
  34.         @ini_set('max_execution_time', 48 * 60 * 60);
  35.         @set_time_limit(48 * 60 * 60);    // set_time_limit(0)  2day
  36.         @ini_set('memory_limit', '4000M');// 4G;
  37.     }
  38.    
  39.     // 读取little-endian编码的4个字节转化为长整型数
  40.     private function getLong4() {
  41.         $result = unpack('Vlong', fread($this->fh, 4));
  42.         return $result['long'];
  43.     }
  44.    
  45.     // 读取little-endian编码的3个字节转化为长整型数
  46.     private function getLong3() {
  47.         $result = unpack('Vlong', fread($this->fh, 3).chr(0));
  48.         return $result['long'];
  49.     }
  50.         // 查询位置信息
  51.         private function getPos($data = '') {
  52.                 $char = fread($this->fh, 1);
  53.                 while (ord($char) != 0) {   // 地区信息以 0 结束
  54.                         $data .= $char;
  55.                         $char = fread($this->fh, 1);
  56.                 }
  57.                 return $data;
  58.         }
  59.     // 查询运营商
  60.     private function getISP() {
  61.         $byte = fread($this->fh, 1);    // 标志字节
  62.         switch (ord($byte)) {
  63.             case 0: $area = ''; break;  // 没有相关信息
  64.             case 1: // 被重定向
  65.                 fseek($this->fh, $this->getLong3());
  66.                 $area = $this->getPos(); break;
  67.             case 2: // 被重定向
  68.                 fseek($this->fh, $this->getLong3());
  69.                 $area = $this->getPos(); break;
  70.             default: $area = $this->getPos($byte); break;     // 没有被重定向
  71.         }
  72.         return $area;
  73.     }
  74.    
  75.     // 检查 IP 格式是否正确
  76.         public function checkIp($ip) {
  77.                 $arr = explode('.', $ip);
  78.                 if(count($arr) != 4) return false;
  79.                 for ($i = 0; $i < 4; $i++) {
  80.                         if ($arr[$i] < '0' || $arr[$i] > '255') {
  81.                                 return false;
  82.                         }
  83.                 }
  84.                 return true;
  85.         }
  86.    
  87.     // 查询 IP 地址
  88.     public function query($ip) {
  89.         if(!$this->checkIp($ip)) {
  90.             return false;
  91.         }
  92.         
  93.         $this->fh    = fopen($this->dbFile, 'rb');
  94.                 $this->first = $this->getLong4();
  95.                 $this->last  = $this->getLong4();
  96.                 $this->total = ($this->last - $this->first) / 7;    // 每条索引7字节
  97.         
  98.                 $ip = pack('N', intval(ip2long($ip)));
  99.         
  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.             
  110.             if ($ip < $beginip) {   // 用户的IP小于中间索引的开始IP地址时
  111.                 $r = $m - 1;
  112.             } else {
  113.                 if ($ip > $endip) { // 用户的IP大于中间索引的结束IP地址时
  114.                     $l = $m + 1;
  115.                 } else {            // 用户IP在中间索引的IP范围内时
  116.                     $findip = $this->first + $m * 7;
  117.                     break;
  118.                 }
  119.             }
  120.         }
  121.                 // 查找 IP 地址段
  122.                 fseek($this->fh, $findip);
  123.                 $location['beginip'] = long2ip($this->getLong4()); // 用户IP所在范围的开始地址
  124.                 $offset = $this->getlong3();
  125.                 fseek($this->fh, $offset);
  126.                 $location['endip'] = long2ip($this->getLong4()); // 用户IP所在范围的结束地址
  127.                
  128.                 // 查找 IP 信息
  129.         $byte = fread($this->fh, 1); // 标志字节
  130.         switch (ord($byte)) {
  131.             case 1:  // 都被重定向
  132.                 $countryOffset = $this->getLong3(); // 重定向地址
  133.                 fseek($this->fh, $countryOffset);
  134.                 $byte = fread($this->fh, 1); // 标志字节
  135.                 switch (ord($byte)) {
  136.                     case 2: // 信息被二次重定向
  137.                         fseek($this->fh, $this->getLong3());
  138.                         $location['pos'] = $this->getPos();
  139.                         fseek($this->fh, $countryOffset + 4);
  140.                         $location['isp'] = $this->getISP();
  141.                     break;
  142.                     default: // 信息没有被二次重定向
  143.                         $location['pos'] = $this->getPos($byte);
  144.                         $location['isp'] = $this->getISP();
  145.                     break;
  146.                 }
  147.             break;
  148.             
  149.             case 2: // 信息被重定向
  150.                 fseek($this->fh, $this->getLong3());
  151.                 $location['pos'] = $this->getPos();
  152.                 fseek($this->fh, $offset + 8);
  153.                 $location['isp'] = $this->getISP();
  154.             break;
  155.             
  156.             default: // 信息没有被重定向
  157.                 $location['pos'] = $this->getPos($byte);
  158.                 $location['isp'] = $this->getISP();
  159.             break;
  160.         }
  161.         
  162.         // 信息转码处理
  163.         foreach ($location as $k => $v) {
  164.             $location[$k] = iconv('gb2312', 'utf-8', $v);
  165.             $location[$k] = preg_replace(array('/^.*CZ88\.NET.*$/isU', '/^.*纯真.*$/isU', '/^.*日IP数据/'), '', $location[$k]);
  166.             $location[$k] = htmlspecialchars($location[$k]);
  167.         }
  168.                
  169.                 return $location;
  170.         }
  171.    
  172.     // 更新数据库 https://www.22vd.com/40035.html
  173.     public function update() {
  174.         $this->ignore_timeout();
  175.         $copywrite = file_get_contents('http://update.cz88.net/ip/copywrite.rar');
  176.         $qqwry     = file_get_contents('http://update.cz88.net/ip/qqwry.rar');
  177.         $key       = unpack('V6', $copywrite)[6];
  178.         for($i = 0; $i < 0x200; $i++) {
  179.             $key *= 0x805;
  180.             $key ++;
  181.             $key = $key & 0xFF;
  182.             $qqwry[$i] = chr(ord($qqwry[$i]) ^ $key);
  183.         }
  184.         $qqwry = gzuncompress($qqwry);
  185.         file_put_contents($this->dbFile, $qqwry);
  186.     }
  187.    
  188.     // 析构函数
  189.     function __destruct() {
  190.         if($this->fh) {
  191.             fclose($this->fh);
  192.         }
  193.         $this->fp = null;
  194.     }
  195. }
使用方法:
/ Y9 `: \/ x3 ~/ r% I5 ~       将上面的模块代码保存为 IPQuery.class.php,然后按照如下方法调用即可:+ ?; h( \& V! A+ P1 q, M9 F0 Q
  1. <?php
  2. require_once('IPQuery.class.php');
  3. $ip = new IPQuery();
  4. $addr = $ip->query('123.233.233.233');
  5. echo "<pre>
  6. IP起始段:{$addr['beginip']}
  7. IP结束段:{$addr['endip']}
  8. 实际地址:{$addr['pos']}
  9. 运 营 商:{$addr['isp']}
  10. </pre>";
输出效果如下:
) b& m# Z9 y, s; d& e5 q7 Z' v 20190707_005256_66.jpg : g) X$ l" ]3 J' x% _$ l
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-11-23 18:15

Powered by paopaomj X3.5 © 2016-2025 sitemap

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