子比主题开发文档
使用指南Codestar Framework主题扩展在线部署AI 功能推荐插件赞助打赏

IP 归属地与位置展示

梳理子比主题 IP 获取、腾讯/高德/太平洋归属地查询、评论与用户位置展示、注册防刷和后台测试链路。

子比主题的 IP 归属地功能不是一个只负责前端展示的小组件。它同时服务评论地址、用户主页地址、注册防刷、后台评论列表和后台接口测试。二次开发时要先判断自己需要的是“记录 IP”、“查询归属地”还是“展示归属地徽章”,不要在页面渲染时反复请求第三方接口。

源码入口

文件作用
inc/functions/zib-tool.php获取访客 IP、请求腾讯位置服务、高德位置服务和太平洋公共接口
inc/functions/functions.php按后台配置调度 IP 归属地 SDK,并生成前端徽章 HTML
inc/functions/zib-comments-list.php评论保存时写入 comment_addr,评论列表展示地址
inc/functions/admin/admin-main.php后台评论列表展示评论 IP 归属地
inc/functions/user/user.php用户登录时更新 user_addr,注册时记录 register_ip,用户页展示地址
inc/functions/zib-theme.php注册防刷按 register_ip 统计频率
inc/options/admin-options.phpIP 归属地接口配置和测试表单
inc/options/action.phptest_ip_addr_sdk 后台 Ajax 测试接口
inc/dependent.php声明 user_addrcomment_addr 等主题聚合 meta key

IP 获取

主题统一用 zib_get_remote_ip_addr() 获取访客 IP:

function zib_get_remote_ip_addr()
{
    if (getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
        $ip = getenv('HTTP_CLIENT_IP');
    } elseif (getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
        $ip = getenv('HTTP_X_FORWARDED_FOR');
    } elseif (getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
        $ip = getenv('REMOTE_ADDR');
    } elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    return preg_match('/[\d\.]{7,15}/', $ip, $matches) ? $matches[0] : '';
}

这个函数会从 HTTP_CLIENT_IPHTTP_X_FORWARDED_FORREMOTE_ADDR 中取值,并用正则截取第一个 IPv4 片段。站点接入 CDN、反向代理或负载均衡后,如果上游没有正确传递真实 IP,主题拿到的可能是代理节点 IP。

评论提交时,主题还把 WordPress 的评论 IP 获取改为这个函数:

function zib_pre_comment_user_ip()
{
    return zib_get_remote_ip_addr();
}
add_filter('pre_comment_user_ip', 'zib_pre_comment_user_ip');

因此评论归属地、注册防刷和用户登录地址都会受同一个 IP 来源影响。排查地址不准时,先查服务器和 CDN 的真实 IP 头配置,再查 SDK。

查询结果结构

三种接口最后都会整理成同一类数组:

$data = array(
    'ip'       => $ip,
    'nation'   => '',
    'province' => '',
    'city'     => '',
    'district' => '',
    'sdk'      => 'qq',
);

sdk 用来标记来源,常见值是 qqamappconline。扩展业务如果需要保存地址,建议原样保存这个结构,不要只保存一段文本。后续展示粒度、排查接口来源、迁移数据都会更方便。

腾讯位置服务

腾讯接口入口是 zib_get_geographical_position_by_qq($ip, $key, $Secret_key, $debug = false)。请求地址:

$api_url = 'http://apis.map.qq.com/ws/location/v1/ip?ip=' . $ip . '&key=' . $key;

如果后台填写了 Secret Key,主题会追加签名:

$api_url .= '&sig=' . md5('/ws/location/v1/ip?ip=' . $ip . '&key=' . $key . $Secret_key);

成功时读取 result.ad_info,失败时普通模式返回 false。后台测试会以 debug 模式调用,遇到腾讯返回“签名验证失败”时,主题会改成更适合后台提示的“签名校验Secret key填写错误”。

后台配置字段是 _pz('ip_addr_sdk_qq'),内部包含:

字段说明
appkey腾讯位置服务 WebServiceAPI Key
secretkey启用签名校验后填写的 Secret Key

高德位置服务

高德接口入口是 zib_get_geographical_position_by_amap($ip, $key, $Secret_key, $debug = false)。请求地址:

$api_url = 'http://restapi.amap.com/v3/ip?ip=' . $ip . '&key=' . $key;

如果后台填写了数据签名秘钥,主题会追加:

$api_url .= '&sig=' . md5('ip=' . $ip . '&key=' . $key . $Secret_key);

高德返回 status != 1 时视为失败。后台 debug 模式会把 INVALID_USER_SIGNATURE 转为“数据签名秘钥填写错误”。

后台配置字段是 _pz('ip_addr_sdk_amap'),内部包含:

字段说明
appkey高德 Web 服务 Key
secretkey开启数字签名后的秘钥

太平洋公共接口

太平洋公共接口入口是 zib_get_geographical_position_by_pconline($ip, $debug = false),不需要后台配置:

$api_url = 'http://whois.pconline.com.cn/ipJson.jsp?json=true&ip=' . $ip;

主题用 body('GB2312') 读取响应,再用正则取 procityregionaddr。它适合做默认兜底,但后台说明里也明确提示这个公共接口的可靠性较低。对准确性有要求的站点,建议配置腾讯或高德,并优先使用轮流查询。

SDK 调度

总调度函数是 zib_get_geographical_position_by_ip($ip)。它先排除空 IP、0.0.0.192.168.127.0.,再读取 _pz('ip_addr_sdk')

配置值行为
qq有腾讯 appkey 时只查腾讯
amap有高德 appkey 时只查高德
polling依次查腾讯、高德、太平洋,有 province 立即返回;都没有省份时再按 nation 兜底
其它值直接使用太平洋公共接口

轮流查询的核心逻辑是先追求 province,再退到 nation。因此展示层不能假设每次都有省市,海外 IP 或接口异常时可能只有国家,也可能返回 false

展示徽章

展示函数是 zib_get_ip_geographical_position_badge($data, $type = 'province', $class = '')。它接收上面的结构化数组,返回一个 <span>

return '<span class="' . $class . '">' . $text . '</span>';

显示规则:

$type结果
province优先显示省份,省份为空时显示城市或国家
city省市相同或省份为空时只显示城市,否则显示省 + 市

输出前会去掉 特别行政区。如果最终没有可显示文本,函数直接返回空。扩展列表或卡片时可以复用它,但传入的数据应该来自已保存的 meta。

评论地址

评论地址由 wp_insert_comment 写入:

function zib_insert_comment_action($id, $comment)
{
    add_comment_meta($id, 'comment_like', 0);

    if (_pz('comment_city_s')) {
        zib_update_comment_meta($id, 'comment_addr', zib_get_geographical_position_by_ip($comment->comment_author_IP));
    }
}
add_action('wp_insert_comment', 'zib_insert_comment_action', 10, 2);

前台评论底部展示时读取:

$addr_data = zib_get_comment_meta($comment->comment_ID, 'comment_addr', true);
$addr_html = zib_get_ip_geographical_position_badge($addr_data, _pz('comment_city_type'), 'badg badg-sm');

后台评论列表也会读取 comment_addr,并用 city 粒度展示。这里的设计要点是:查询发生在评论创建时,列表渲染只读保存结果。不要在评论循环里实时请求 IP 接口,否则评论页会被网络耗时拖慢,第三方接口也容易触发限频。

用户地址

用户登录时,主题会按开关更新当前位置:

function zib_updata_user_addr_meta($user_login, $user)
{
    if (!_pz('user_city_s', true)) {
        return;
    }
    $user_addr = zib_get_geographical_position_by_ip(zib_get_remote_ip_addr());
    if (!empty($user_addr['province']) || !empty($user_addr['nation'])) {
        zib_update_user_meta($user->ID, 'user_addr', $user_addr);
    }
}
add_action('wp_login', 'zib_updata_user_addr_meta', 10, 2);

用户注册时只记录原始 IP:

function zib_add_user_ip_addr_meta($user_id)
{
    $register_ip = zib_get_remote_ip_addr();
    if ($register_ip) {
        update_user_meta($user_id, 'register_ip', $register_ip);
    }
}
add_action('user_register', 'zib_add_user_ip_addr_meta');

用户主页展示时读取 user_addr,并把徽章追加到作者头部描述:

$addr_data = zib_get_user_meta($user_id, 'user_addr', true);
return $desc . zib_get_ip_geographical_position_badge($addr_data, _pz('user_city_type'), 'badg');

user_addrcomment_addr 都在主题依赖文件的 meta key 列表里声明。读取时使用 zib_get_user_meta()zib_get_comment_meta(),写入时使用 zib_update_user_meta()zib_update_comment_meta(),不要绕过主题的聚合 meta 封装。

注册防刷

注册防刷使用 register_ip,不是 user_addr

$ip_addr = zib_get_remote_ip_addr();
if (!$ip_addr || preg_match('/^(?:127|172\.16|192\.168)\./', $ip_addr)) {
    return $errors;
}

主题随后查询 usermeta 中相同 register_ip 的用户数量,并按 _pz('brush_limit_register')minutes_10day_1 判断是否拦截注册。它只依赖原始 IP,不依赖第三方归属地接口,所以即使腾讯、高德或太平洋接口不可用,注册防刷仍然可以工作。

如果站点在 CDN 后面,真实 IP 配置错误会让大量用户共享同一个代理 IP,导致误拦截。遇到“很多用户提示操作过于频繁”时,先检查 register_ip 是否都变成了同一个地址。

后台设置和测试

后台的“IP归属地”设置页提供:

配置说明
ip_addr_sdk轮流查询、腾讯位置服务、高德位置服务、太平洋公共接口
ip_addr_sdk_qq腾讯 App Key 和 Secret Key
ip_addr_sdk_amap高德 Key 和数据签名秘钥
test_ip_addr_sdk保存配置后测试某个 IP 或当前 IP

测试 Ajax 是 wp_ajax_test_ip_addr_sdk,只允许超级管理员调用:

if (!is_super_admin()) {
    echo(json_encode(array('error' => 1, 'ys' => 'danger', 'msg' => __('操作权限不足', 'zib_language'))));
    exit();
}

它会拒绝空 IP、0.0.0.192.168.127.0.,并按 sdk 参数测试腾讯、高德或太平洋。扩展后台诊断功能时,可以参考这个返回格式:error 表示成败,msg 放可读结果,必要时用 JSON_UNESCAPED_UNICODE 输出接口结果。

扩展示例:给自定义行为记录位置

如果自定义业务需要保存一次操作位置,可以在行为发生时查询并保存,不要在展示时查询:

function zib_docs_save_action_addr($user_id, $action_id)
{
    if (!$user_id || !$action_id) {
        return;
    }

    $ip        = zib_get_remote_ip_addr();
    $addr_data = zib_get_geographical_position_by_ip($ip);

    if (empty($addr_data['province']) && empty($addr_data['nation'])) {
        return;
    }

    zib_update_user_meta($user_id, 'docs_last_action_addr', array(
        'action_id' => $action_id,
        'ip'        => $ip,
        'addr'      => $addr_data,
        'time'      => current_time('mysql'),
    ));
}
add_action('zib_docs_user_action_done', 'zib_docs_save_action_addr', 10, 2);

展示时再读取已保存的数据:

function zib_docs_get_action_addr_badge($user_id)
{
    $data = zib_get_user_meta($user_id, 'docs_last_action_addr', true);

    if (empty($data['addr'])) {
        return '';
    }

    return zib_get_ip_geographical_position_badge($data['addr'], 'city', 'badg badg-sm');
}

如果保存的是评论相关数据,换成 zib_update_comment_meta()zib_get_comment_meta();如果保存的是文章相关数据,优先用 zib_update_post_meta()zib_get_post_meta()。核心原则是让查询发生在写入节点,让展示只读缓存结果。

排查清单

现象优先检查
归属地显示为空comment_city_suser_city_s 是否开启,meta 是否已写入
评论地址不更新评论是否是在开启地址显示前创建,comment_addr 是否存在
用户地址一直是旧地址用户是否重新登录,user_city_s 是否开启
所有用户地址相同CDN 或反向代理是否传错真实 IP
腾讯测试失败appkeysecretkey、WebServiceAPI、签名校验设置
高德测试失败appkey、数字签名秘钥、服务平台是否为 Web 服务
太平洋接口不稳定公共接口网络或响应格式变化,建议配置腾讯或高德
注册频繁误拦截register_ip 是否集中为同一个代理 IP
页面加载变慢是否在循环渲染时实时调用 zib_get_geographical_position_by_ip()

开发边界

  • IP 归属地只能用于展示和低风险风控提示,不要当作权限判断依据。
  • 第三方接口可能失败,调用处要允许 false,不要让失败中断评论、登录或注册流程。
  • 评论、用户和自定义业务位置都应该在写入节点保存,展示节点只读 meta。
  • 代理、CDN、局域网部署会影响 IP 来源,归属地不准时先排查 zib_get_remote_ip_addr() 的结果。
  • register_ip 是注册防刷数据,user_addr 是用户展示数据,两者不要混用。
  • 后台测试接口只给管理员使用,不要把第三方 key、签名、原始错误暴露给普通用户。

本页根据 inc/functions/zib-tool.phpinc/functions/functions.phpinc/functions/zib-comments-list.phpinc/functions/admin/admin-main.phpinc/functions/user/user.phpinc/functions/zib-theme.phpinc/options/admin-options.phpinc/options/action.phpinc/dependent.php 蒸馏整理。

On this page