我将博客的IP归属地查询从纯真IP数据库换成了ip2region。因为纯真dat版已停更,czdb版的IPv6没什么用,而ip2region v3.0+免费、格式规范且支持IPv6。对于博客评论,城市级精度完全足够。本地数据库还有稳定、快速、保护隐私三大优势。现已成功集成,效果不错。文章目录为什么我选择本地IP数据库?从纯真IP切换到ip2region为什么选择ip2region?动手在WordPress里集成ip2region1、下载必要文件2、创建转换文件3、在主题中加载4、在评论中调用5、仅后台评论列表展示6、只支持IPv4的版本

前几天和@上善若水聊起IP归属地这事儿,发现市面上免费的IP查询服务,精度其实都挺一般的。

为什么我选择本地IP数据库?

对我来说,博客上显示IP归属地,真没必要搞什么付费精确定位。能知道访客大概在哪个省份、哪个城市,已经完全够用了。本地数据库有几个实实在在的好处:

够稳定:不不依赖外部接口,不用担心API挂掉响应快:本地查询,速度比在线查询快不少隐私性好:不需要将用户IP发送给第三方从纯真IP切换到ip2region

之前Weisay Grace一直用的是纯真IP数据库dat格式,但它2024年9月就正式停更了。虽然出了新的CZDB格式,但需要申请密钥,用起来还是有点麻烦。所以后续主题更新都会改用ip2region。

纯真IP数据库确实年头久了,但也积累了一些问题:

数据格式比较乱,错别字时不时能看到IPv6支持很基础,国内IPv6一律只显示“中国”我之前还专门用IPlook配合Excel手动修正过dat格式数据库为什么选择ip2region?

ip2region在今年9月发布了v3.0版本,开始真正支持IPv6:

IPv6能定位到地级市(虽然准确度还有提升空间)数据数据格式规范,看着舒服项目还在持续更新维护

虽然ip2region只能到地级市,不如纯真IP有些能到区县镇那么细,但对博客评论显示来说,知道到城市这个级别,真的已经足够了。

动手在WordPress里集成ip2region

简单四步就能搞定:

1、下载必要文件

从GitHub或Gitee下载ip2region,主要需要这三个文件:

data/ip2region_v4.xdb(IPv4数据库)

data/ip2region_v6.xdb(IPv6数据库)

binding/php/xdb/Searcher.class.php(查询核心文件)

2、创建转换文件

在主题文件夹中创建ip2region.php,添加下面的IP转换代码,注意代码中引用的文件路径。

?php * Ip2region 是一个离线 IP 数据管理框架和定位库,支持 IPv4 和 IPv6。此代码版本支持 IPv4 和 IPv6。 * 官方社区:https://ip2region.net/require_once __DIR__ . '/xdb/Searcher.class.php';use \ip2region\xdb\Util;use \ip2region\xdb\Searcher;//初始化,使用向量索引function init_ip2region_vector($dbFile) { if (!file_exists($dbFile)) { error_log("IP数据库文件不存在: " . $dbFile); return null; try { // 读取文件头,获取版本信息 $header = Util::loadHeaderFromFile($dbFile); $version = Util::versionFromHeader($header); // 加载向量索引 $vIndex = Util::loadVectorIndexFromFile($dbFile); // 创建 Searcher return Searcher::newWithVectorIndex($version, $dbFile, $vIndex); } catch (Exception $e) { error_log("IP数据库初始化失败: " . $e- getMessage()); return null;// 全局 Searcher,显式初始化为 nullglobal $ip2region_searcher_v4, $ip2region_searcher_v6;$ip2region_searcher_v4 = $ip2region_searcher_v4 ?? null;$ip2region_searcher_v6 = $ip2region_searcher_v6 ?? null;//获取 IPv4 或 IPv6 Searcherfunction get_ip_searcher($ip) { global $ip2region_searcher_v4, $ip2region_searcher_v6; // 判断 ip 类型 $isIpv6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); $isIpv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); // 尝试加载 IPv6 searcher if ($isIpv6) { if ($ip2region_searcher_v6 === null) { $dbFile_v6 = __DIR__ . '/data/ip2region_v6.xdb'; $ip2region_searcher_v6 = init_ip2region_vector($dbFile_v6); if ($ip2region_searcher_v6 !== null) { return $ip2region_searcher_v6; // 如果 IPv6 DB 不可用,继续尝试加载 IPv4(fallback降级) // IPv4 路径(或者作为fallback降级) if ($ip2region_searcher_v4 === null) { $dbFile_v4 = __DIR__ . '/data/ip2region_v4.xdb'; $ip2region_searcher_v4 = init_ip2region_vector($dbFile_v4); return $ip2region_searcher_v4;//判断 IP 类型function get_ip_type($ip) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return 'ipv4'; } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { return 'ipv6'; return false;//判断内网IP并返回显示文本function is_private_ip($ip) { $ip_type = get_ip_type($ip); if ($ip_type === 'ipv4') { if (filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) === false) { return '内网IP'; } elseif ($ip_type === 'ipv6') { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false) { return '内网IP'; if ($ip === '::1') { return '内网IP'; if (preg_match('/^fe80:/i', $ip)) { return '内网IP'; return false;//IP转换函数function convertip($ip, $withIsp = false, $simpleMode = false) { if (!$ip) return '火星'; // 检查内网IP $private_result = is_private_ip($ip); if ($private_result !== false) { return $private_result; // 获取 searcher(延迟加载、并可降级) $searcher = get_ip_searcher($ip); if ($searcher === null) { return '火星'; // 数据库不存在或加载失败 try { $region = $searcher- search($ip); } catch (Exception $e) { return '火星'; $parts = explode('|', $region); $country = ($parts[0] !== '0') ? $parts[0] : ''; $province = ($parts[1] !== '0') ? $parts[1] : ''; $city = ($parts[2] !== '0') ? $parts[2] : ''; $isp = ($parts[3] !== '0') ? $parts[3] : ''; $resultParts = []; // 处理 "中国|0|0|ISP" 特殊情况 if ($country === '中国' && $province === '' && $city === '') { $resultParts[] = '中国'; if ($withIsp && $isp) { $resultParts[] = ' ' . $isp; return implode('', $resultParts); // 处理中国的专属逻辑 if ($country === '中国') { // 直辖市列表 $municipalities = ['北京', '上海', '天津', '重庆']; // 直辖市逻辑 if (in_array($province, $municipalities) || in_array($city, $municipalities)) { $resultParts[] = $city ?: $province; } else { // 普通省份逻辑 if ($province === $city) { $resultParts[] = $city; } else { if ($province) $resultParts[] = $province; if (!$simpleMode && $city) $resultParts[] = $city; } else { // 国外逻辑 if ($country) $resultParts[] = $country; if ($province) $resultParts[] = $province; if (!$simpleMode && $city) $resultParts[] = $city; // 可选显示网络ISP if ($withIsp && $isp) { $resultParts[] = ' ' . $isp; // 兜底,防止结果为空 if (empty($resultParts)) { return $country ?: '火星'; return implode('', $resultParts);//简版 - 国内只显示省,国外显示国家 + 省function convertipsimple($ip, $withIsp = false) { return convertip($ip, $withIsp, true);3、在主题中加载

在function.php中添加下面代码,注意代码中引用的文件路径:

require get_template_directory() . '/ip2region.php';4、在评论中调用

想在评论里显示地理位置?用这几个函数就行:

// 显示省市(无运营商)echo convertip(get_comment_author_IP());// 显示省市 + 运营商echo convertip(get_comment_author_IP(), true);// 只显示省份echo convertipsimple(get_comment_author_IP());// 只显示省份 + 运营商,echo convertipsimple(get_comment_author_IP(), true);//如果是国外,以上都会加上国家,只有中国时会过滤一下

这样设置完,就能在WordPress评论里就能看到评论者的地理位置了,简单又直观。其实稍微改改获取IP的方式,这套代码也能用在其他PHP博客系统上。

5、仅后台评论列表展示

其实不少博主不太喜欢在前台展示IP归属地,不过我倒是觉得,作为管理员了解一下访客的大致地区还是挺有必要的。不如再换个思路,只在后台评论列表里加一列IP归属地,这样查看起来方便,不影响前台体验,也挺好。

前面的第1、2、3步完成之后,将下面的代码放到主题的 function.php 里面就行。

//后台评论管理新增地理位置信息function my_comments_columns( $columns ){ $columns[ 'location' ] = __( '位置' ); return $columns;add_filter( 'manage_edit-comments_columns', 'my_comments_columns' );function output_my_comments_columns(){ echo convertip(get_comment_author_ip()); //可以使用第4步其他方式add_action( 'manage_comments_custom_column', 'output_my_comments_columns', 10, 2 );6、只支持IPv4的版本

有些博客的服务器没有配置支持IPv6,那么其实是不会有IPv6的评论的,这样我们就只需支持IPv4就可以。其他不变,当然IPv6的数据库也就不需要了,将第2步的代码换成下面的代码就行。

?php * Ip2region 是一个离线 IP 数据管理框架和定位库,支持 IPv4 和 IPv6。此代码版本只支持 IPv4 。 * 官方社区:https://ip2region.net/require_once __DIR__ . '/xdb/Searcher.class.php';use \ip2region\xdb\Util;use \ip2region\xdb\Searcher;// 全局 Searcherglobal $ip2region_searcher_v4;$ip2region_searcher_v4 = null;// 初始化 IPv4 Searcher(向量索引模式)function init_ip2region_vector($dbFile) { if (!file_exists($dbFile)) { error_log("IP数据库文件不存在: " . $dbFile); return null; try { // 读取文件头,获取版本信息 $header = Util::loadHeaderFromFile($dbFile); $version = Util::versionFromHeader($header); // 加载向量索引 $vIndex = Util::loadVectorIndexFromFile($dbFile); // 创建 Searcher return Searcher::newWithVectorIndex($version, $dbFile, $vIndex); } catch (Exception $e) { error_log("IP数据库初始化失败: " . $e- getMessage()); return null;// 获取 IPv4 searcherfunction get_ip_searcher() { global $ip2region_searcher_v4; if ($ip2region_searcher_v4 === null) { $dbFile_v4 = __DIR__ . '/data/ip2region_v4.xdb'; $ip2region_searcher_v4 = init_ip2region_vector($dbFile_v4); return $ip2region_searcher_v4;// 检查内网 IPv4function is_private_ip($ip) { return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) === false;// 主转换函数function convertip($ip, $withIsp = false, $simpleMode = false) { if (!$ip) return '火星'; if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return '火星'; // 内网IP if (is_private_ip($ip)) { return '内网IP'; // 获取 Searcher $searcher = get_ip_searcher(); if ($searcher === null) { return '火星'; // 查询 IP try { $region = $searcher- search($ip); } catch (Exception $e) { return '火星'; $parts = explode('|', $region); $country = ($parts[0] !== '0') ? $parts[0] : ''; $province = ($parts[1] !== '0') ? $parts[1] : ''; $city = ($parts[2] !== '0') ? $parts[2] : ''; $isp = ($parts[3] !== '0') ? $parts[3] : ''; $resultParts = []; // 处理 "中国|0|0|ISP" 特殊情况 if ($country === '中国' && $province === '' && $city === '') { $resultParts[] = '中国'; if ($withIsp && $isp) { $resultParts[] = ' ' . $isp; return implode('', $resultParts); // 处理中国的专属逻辑 if ($country === '中国') { // 直辖市列表 $municipalities = ['北京', '上海', '天津', '重庆']; // 直辖市逻辑 if (in_array($province, $municipalities) || in_array($city, $municipalities)) { $resultParts[] = $city ?: $province; } else { // 普通省份逻辑 if ($province === $city) { $resultParts[] = $city; } else { if ($province) $resultParts[] = $province; if (!$simpleMode && $city) $resultParts[] = $city; } else { // 国外逻辑 if ($country) $resultParts[] = $country; if ($province) $resultParts[] = $province; if (!$simpleMode && $city) $resultParts[] = $city; // 可选显示网络ISP if ($withIsp && $isp) { $resultParts[] = ' ' . $isp; // 兜底,防止结果为空 if (empty($resultParts)) { return $country ?: '火星'; return implode('', $resultParts);//简版 - 国内只显示省,国外显示国家 + 省function convertipsimple($ip, $withIsp = false) { return convertip($ip, $withIsp, true);