威言威语
我愿像茶,苦涩留心,清香予人。
威言威语
当前位置: 首页 > 网络 > 正文

WordPress中使用ip2region实现评论者IP归属地

我将博客的IP归属地查询从纯真IP数据库换成了ip2region。因为纯真dat版已停更,czdb版的IPv6没什么用,而ip2region v3.0+免费、格式规范且支持IPv6。对于博客评论,城市级精度完全足够。本地数据库还有稳定、快速、保护隐私三大优势。现已成功集成,效果不错。
WordPress中使用ip2region实现评论者IP归属地

前几天和@上善若水聊起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、下载必要文件

GitHubGitee下载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,显式初始化为 null
global $ip2region_searcher_v4, $ip2region_searcher_v6;
$ip2region_searcher_v4 = $ip2region_searcher_v4 ?? null;
$ip2region_searcher_v6 = $ip2region_searcher_v6 ?? null;

//获取 IPv4 或 IPv6 Searcher
function 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;

// 全局 Searcher
global $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 searcher
function 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;
}

// 检查内网 IPv4
function 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);
}

?>

相关推荐

WordPress中使用ip2region实现评论者IP归属地:目前有 52 条评论

  1. Jeffer.Z
    22楼

    写了一个统计系统,本地ip统计不知道怎么办,看了这篇文章直接用了这个依赖,非常准的ip识别。

    2025-11-08 13:49 回复
    • William
      WilliamGoogle Chrome 142.0.0.0 Windows 11

      @Jeffer.Z免费的嘛,准确度肯定没有那么商业版的高,不过已经很不错了。

      2025-11-08 21:48 回复
  2. 西风
    21楼
    西风1Google Chrome 142.0.0.0 Mac OS X  10.15.7

    首页风格换了呀

    2025-11-07 23:31 回复
  3. 美樂地
    20楼

    这个不错,开源且数据常更新,我可以考虑加一个

    2025-11-02 20:13 回复
  4. 书签网
    19楼

    能做成插件最好了

    2025-11-02 11:23 回复
  5. vss
    18楼
    vss2Google Chrome 122.0.6261.95 Windows 10

    我的IP归属地插件用的是大发的

    2025-11-01 09:17 回复
    • William
      WilliamFirefox 144.0 Windows 10

      @vss恩, 用插件比较方便,不受主题影响。

      2025-11-01 17:56 回复
  6. 2broear
    17楼
    2broear3Microsoft Edge 141.0.0.0 Windows 10

    无折腾,不博客🤭

    2025-10-27 11:39 回复
  7. 满心
    16楼
    满心4Google Chrome 141.0.0.0 Windows 10

    我现在也是用的Ip2region这个库,还不错

    2025-10-27 10:28 回复
    • William
      WilliamGoogle Chrome 141.0.0.0 Windows 11

      @满心是的,格式比纯真规范很多,不用写很多特殊处理。

      2025-10-27 14:20 回复
  8. 青山
    15楼
    青山1Google Chrome 141.0.0.0 Mac OS X  10.15.7

    日常挂梯子,天天都是在美国😄。
    倒是浏览器和系统信息这块我觉得有必要更新一下,目前 WordPress 用的插件都是好几年前的,最新的浏览器和系统版本都没更新。

    2025-10-26 15:17 回复
    • William
      WilliamGoogle Chrome 141.0.0.0 Windows 11

      @青山浏览器和系统的插件我自己也有更新,补充了不少。
      不过现在比如mac和Win11,都已经不是简单的通过ua去判断他的实际信息了。

      2025-10-26 17:28 回复
  9. 我是军爸
    14楼

    太精确了也不太好。 Weisay真是一直在进步啊,我都想用你的主题了,奈何付费买的主题扔了也可惜

    2025-10-25 17:01 回复
    • William
      WilliamFirefox 144.0 Windows 10

      @我是军爸其实这个IP功能weisaygrace主题本身就是有的,用的纯真IP的,只是想着接下来换成ip2region,这样也能支持ipv6了。
      我自己觉得我主题超过不少付费主题的,哈哈

      2025-10-25 17:38 回复
  10. 彬红茶
    13楼

    确实,IP归属地意思意思就好

    2025-10-25 14:17 回复

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

gravatar

question razz sad smile redface biggrin eek shock confused cool lol mad rolleyes wink cry