引言
在日常开发中,我们经常需要根据用户的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库!如果有任何问题,欢迎留言讨论。