看分享,阅见世界,分享此刻。是一个整理、分享,搜索的网站。智阅无界,洞见未来。
  • 文章
  • 专题
  • 文章
  • 登录
  • 注册
计科知识库 计科知识库 8天前

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

php

为什么选择ip2region?

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

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

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

  1. <?php
  2. /**
  3. * Ip2region 是一个离线 IP 数据管理框架和定位库,支持 IPv4 和 IPv6。此代码版本支持 IPv4 和 IPv6。
  4. *
  5. * 官方社区:https://ip2region.net/
  6. */
  7. require_once __DIR__ . '/xdb/Searcher.class.php';
  8. use \ip2region\xdb\Util;
  9. use \ip2region\xdb\Searcher;
  10. //初始化,使用向量索引
  11. function init_ip2region_vector($dbFile) {
  12. if (!file_exists($dbFile)) {
  13. error_log("IP数据库文件不存在: " . $dbFile);
  14. return null;
  15. }
  16. try {
  17. // 读取文件头,获取版本信息
  18. $header = Util::loadHeaderFromFile($dbFile);
  19. $version = Util::versionFromHeader($header);
  20. // 加载向量索引
  21. $vIndex = Util::loadVectorIndexFromFile($dbFile);
  22. // 创建 Searcher
  23. return Searcher::newWithVectorIndex($version, $dbFile, $vIndex);
  24. } catch (Exception $e) {
  25. error_log("IP数据库初始化失败: " . $e->getMessage());
  26. return null;
  27. }
  28. }
  29. // 全局 Searcher,显式初始化为 null
  30. global $ip2region_searcher_v4, $ip2region_searcher_v6;
  31. $ip2region_searcher_v4 = $ip2region_searcher_v4 ?? null;
  32. $ip2region_searcher_v6 = $ip2region_searcher_v6 ?? null;
  33. //获取 IPv4 或 IPv6 Searcher
  34. function get_ip_searcher($ip) {
  35. global $ip2region_searcher_v4, $ip2region_searcher_v6;
  36. // 判断 ip 类型
  37. $isIpv6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
  38. $isIpv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
  39. // 尝试加载 IPv6 searcher
  40. if ($isIpv6) {
  41. if ($ip2region_searcher_v6 === null) {
  42. $dbFile_v6 = __DIR__ . '/data/ip2region_v6.xdb';
  43. $ip2region_searcher_v6 = init_ip2region_vector($dbFile_v6);
  44. }
  45. if ($ip2region_searcher_v6 !== null) {
  46. return $ip2region_searcher_v6;
  47. }
  48. // 如果 IPv6 DB 不可用,继续尝试加载 IPv4(fallback降级)
  49. }
  50. // IPv4 路径(或者作为fallback降级)
  51. if ($ip2region_searcher_v4 === null) {
  52. $dbFile_v4 = __DIR__ . '/data/ip2region_v4.xdb';
  53. $ip2region_searcher_v4 = init_ip2region_vector($dbFile_v4);
  54. }
  55. return $ip2region_searcher_v4;
  56. }
  57. //判断 IP 类型
  58. function get_ip_type($ip) {
  59. if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
  60. return 'ipv4';
  61. } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
  62. return 'ipv6';
  63. }
  64. return false;
  65. }
  66. //判断内网IP并返回显示文本
  67. function is_private_ip($ip) {
  68. $ip_type = get_ip_type($ip);
  69. if ($ip_type === 'ipv4') {
  70. if (filter_var(
  71. $ip,
  72. FILTER_VALIDATE_IP,
  73. FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
  74. ) === false) {
  75. return '内网IP';
  76. }
  77. } elseif ($ip_type === 'ipv6') {
  78. if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false) {
  79. return '内网IP';
  80. }
  81. if ($ip === '::1') {
  82. return '内网IP';
  83. }
  84. if (preg_match('/^fe80:/i', $ip)) {
  85. return '内网IP';
  86. }
  87. }
  88. return false;
  89. }
  90. //IP转换函数
  91. function convertip($ip, $withIsp = false, $simpleMode = false) {
  92. if (!$ip) return '火星';
  93. // 检查内网IP
  94. $private_result = is_private_ip($ip);
  95. if ($private_result !== false) {
  96. return $private_result;
  97. }
  98. // 获取 searcher(延迟加载、并可降级)
  99. $searcher = get_ip_searcher($ip);
  100. if ($searcher === null) {
  101. return '火星'; // 数据库不存在或加载失败
  102. }
  103. try {
  104. $region = $searcher->search($ip);
  105. } catch (Exception $e) {
  106. return '火星';
  107. }
  108. $parts = explode('|', $region);
  109. $country = ($parts[0] !== '0') ? $parts[0] : '';
  110. $province = ($parts[1] !== '0') ? $parts[1] : '';
  111. $city = ($parts[2] !== '0') ? $parts[2] : '';
  112. $isp = ($parts[3] !== '0') ? $parts[3] : '';
  113. $resultParts = [];
  114. // 处理 "中国|0|0|ISP" 特殊情况
  115. if ($country === '中国' && $province === '' && $city === '') {
  116. $resultParts[] = '中国';
  117. if ($withIsp && $isp) {
  118. $resultParts[] = ' ' . $isp;
  119. }
  120. return implode('', $resultParts);
  121. }
  122. // 处理中国的专属逻辑
  123. if ($country === '中国') {
  124. // 直辖市列表
  125. $municipalities = ['北京', '上海', '天津', '重庆'];
  126. // 直辖市逻辑
  127. if (in_array($province, $municipalities) || in_array($city, $municipalities)) {
  128. $resultParts[] = $city ?: $province;
  129. } else {
  130. // 普通省份逻辑
  131. if ($province === $city) {
  132. $resultParts[] = $city;
  133. } else {
  134. if ($province) $resultParts[] = $province;
  135. if (!$simpleMode && $city) $resultParts[] = $city;
  136. }
  137. }
  138. } else {
  139. // 国外逻辑
  140. if ($country) $resultParts[] = $country;
  141. if ($province) $resultParts[] = $province;
  142. if (!$simpleMode && $city) $resultParts[] = $city;
  143. }
  144. // 可选显示网络ISP
  145. if ($withIsp && $isp) {
  146. $resultParts[] = ' ' . $isp;
  147. }
  148. // 兜底,防止结果为空
  149. if (empty($resultParts)) {
  150. return $country ?: '火星';
  151. }
  152. return implode('', $resultParts);
  153. }
  154. //简版 - 国内只显示省,国外显示国家 + 省
  155. function convertipsimple($ip, $withIsp = false) {
  156. return convertip($ip, $withIsp, true);
  157. }
  158. ?>

在主题中加载

  1. require get_template_directory() . '/ip2region.php';

在评论中调用

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

  1. // 显示省市(无运营商)
  2. echo convertip(get_comment_author_IP());
  3. // 显示省市 + 运营商
  4. echo convertip(get_comment_author_IP(), true);
  5. // 只显示省份
  6. echo convertipsimple(get_comment_author_IP());
  7. // 只显示省份 + 运营商,
  8. echo convertipsimple(get_comment_author_IP(), true);
  9. //如果是国外,以上都会加上国家,只有中国时会过滤一下

只支持IPv4的版本

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

  1. <?php
  2. /**
  3. * Ip2region 是一个离线 IP 数据管理框架和定位库,支持 IPv4 和 IPv6。此代码版本只支持 IPv4 。
  4. *
  5. * 官方社区:https://ip2region.net/
  6. */
  7. require_once __DIR__ . '/xdb/Searcher.class.php';
  8. use \ip2region\xdb\Util;
  9. use \ip2region\xdb\Searcher;
  10. // 全局 Searcher
  11. global $ip2region_searcher_v4;
  12. $ip2region_searcher_v4 = null;
  13. // 初始化 IPv4 Searcher(向量索引模式)
  14. function init_ip2region_vector($dbFile) {
  15. if (!file_exists($dbFile)) {
  16. error_log("IP数据库文件不存在: " . $dbFile);
  17. return null;
  18. }
  19. try {
  20. // 读取文件头,获取版本信息
  21. $header = Util::loadHeaderFromFile($dbFile);
  22. $version = Util::versionFromHeader($header);
  23. // 加载向量索引
  24. $vIndex = Util::loadVectorIndexFromFile($dbFile);
  25. // 创建 Searcher
  26. return Searcher::newWithVectorIndex($version, $dbFile, $vIndex);
  27. } catch (Exception $e) {
  28. error_log("IP数据库初始化失败: " . $e->getMessage());
  29. return null;
  30. }
  31. }
  32. // 获取 IPv4 searcher
  33. function get_ip_searcher() {
  34. global $ip2region_searcher_v4;
  35. if ($ip2region_searcher_v4 === null) {
  36. $dbFile_v4 = __DIR__ . '/data/ip2region_v4.xdb';
  37. $ip2region_searcher_v4 = init_ip2region_vector($dbFile_v4);
  38. }
  39. return $ip2region_searcher_v4;
  40. }
  41. // 检查内网 IPv4
  42. function is_private_ip($ip) {
  43. return filter_var(
  44. $ip,
  45. FILTER_VALIDATE_IP,
  46. FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
  47. ) === false;
  48. }
  49. // 主转换函数
  50. function convertip($ip, $withIsp = false, $simpleMode = false) {
  51. if (!$ip) return '火星';
  52. if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
  53. return '火星';
  54. }
  55. // 内网IP
  56. if (is_private_ip($ip)) {
  57. return '内网IP';
  58. }
  59. // 获取 Searcher
  60. $searcher = get_ip_searcher();
  61. if ($searcher === null) {
  62. return '火星';
  63. }
  64. // 查询 IP
  65. try {
  66. $region = $searcher->search($ip);
  67. } catch (Exception $e) {
  68. return '火星';
  69. }
  70. $parts = explode('|', $region);
  71. $country = ($parts[0] !== '0') ? $parts[0] : '';
  72. $province = ($parts[1] !== '0') ? $parts[1] : '';
  73. $city = ($parts[2] !== '0') ? $parts[2] : '';
  74. $isp = ($parts[3] !== '0') ? $parts[3] : '';
  75. $resultParts = [];
  76. // 处理 "中国|0|0|ISP" 特殊情况
  77. if ($country === '中国' && $province === '' && $city === '') {
  78. $resultParts[] = '中国';
  79. if ($withIsp && $isp) {
  80. $resultParts[] = ' ' . $isp;
  81. }
  82. return implode('', $resultParts);
  83. }
  84. // 处理中国的专属逻辑
  85. if ($country === '中国') {
  86. // 直辖市列表
  87. $municipalities = ['北京', '上海', '天津', '重庆'];
  88. // 直辖市逻辑
  89. if (in_array($province, $municipalities) || in_array($city, $municipalities)) {
  90. $resultParts[] = $city ?: $province;
  91. } else {
  92. // 普通省份逻辑
  93. if ($province === $city) {
  94. $resultParts[] = $city;
  95. } else {
  96. if ($province) $resultParts[] = $province;
  97. if (!$simpleMode && $city) $resultParts[] = $city;
  98. }
  99. }
  100. } else {
  101. // 国外逻辑
  102. if ($country) $resultParts[] = $country;
  103. if ($province) $resultParts[] = $province;
  104. if (!$simpleMode && $city) $resultParts[] = $city;
  105. }
  106. // 可选显示网络ISP
  107. if ($withIsp && $isp) {
  108. $resultParts[] = ' ' . $isp;
  109. }
  110. // 兜底,防止结果为空
  111. if (empty($resultParts)) {
  112. return $country ?: '火星';
  113. }
  114. return implode('', $resultParts);
  115. }
  116. //简版 - 国内只显示省,国外显示国家 + 省
  117. function convertipsimple($ip, $withIsp = false) {
  118. return convertip($ip, $withIsp, true);
  119. }
  120. ?>

参考出处
https://www.weisay.com/blog/get-ip-locations-with-ip2region.html

  • © 2025 看分享 阅见世界,分享此刻。
  • 建议
  • | 鄂ICP备14016484号-7

    鄂公网安备 42068402000189

    访问微博看分享