海外独立站,表单自动获取访客
适用场景:搭建海外独立站,需要在表单提交时自动记录填表人的归属/地区,并写入数...
适用场景:搭建海外独立站,需要在表单提交时自动记录填表人的归属/地区,并写入数据库、显示在邮件通知中。
测试环境:腾讯云CDN、PHP 8.4+、MySQL 5.7+
一、实现效果
场景 | 效果 |
|---|---|
访客提交表单 | 自动从 IP 解析,写入 |
后台管理 | 表单列表中直接看到每条记录的 |
邮件通知 | 管理员收到的邮件中包含访客信息 |
IPv4 / IPv6 | 均支持 |
国内访客 | 到省份(中国·广东) |
海外访客 | 返回中文名(美国、日本等) |
CDN 环境 | 兼容 |
二、实现原理
访客提交表单 ↓ 触发 form_post_after 钩子 ↓ 从 $data['inputip'] 取真实IP ↓ 清洗IP(去掉CDN附加的端口号) ↓ 查缓存(7天有效期)→ 命中直接返回 ↓ (未命中) 国内IP → 系统IP库(到省) 海外IP → ip-api.com(免费、无需Key、支持IPv6) ↓ 写入数据库主表 country 列 ↓ 邮件模板通过 {php} 标签调用函数显示三、准备工作
1. 数据库:给表单主表添加 country 列
登录 phpMyAdmin,对每个需要字段的表单主表执行:
ALTER TABLE `dr_1_form_xunpan` ADD `country` VARCHAR(50) DEFAULT '' COMMENT '访客'; ALTER TABLE `dr_1_form_liuyan` ADD `country` VARCHAR(50) DEFAULT '' COMMENT '访客';
表名说明:
dr_1_form_xxx中1是站点ID,多站点环境下根据实际调整。
2. 创建缓存目录
mkdir -p cache/xr_geo chmod 755 cache/xr_geo
3. 确认服务器支持
依赖 | 说明 | 检查方式 |
|---|---|---|
cURL | ip-api.com 查询依赖 | `php -m |
外网访问 | 能访问 ip-api.com |
|
写权限 |
| 自动创建,失败会静默 |
四、部署代码
将以下完整代码保存为 config/hooks.php(注意:会覆盖原有内容,请先备份):
<?php
use Phpcmf\Hooks;
// ============================================================
// 原 dr_titleToUrl 钩子(保留)
// ============================================================
\Phpcmf\Hooks::app_on('dr_titleToUrl', 'module_content_before', function($data) {
if ($data[1]['status'] == 9) {
if(!$data[1]['filename']){
$data[1]['filename'] = dr_titleToUrl($data[1]['title']);
}
}
return dr_return_data(1, 'ok', $data);
});
\Phpcmf\Hooks::app_on('dr_titleToUrl', 'module_update_url', function($data) {
$url = my_zdyurl($data);
return dr_return_data(1, '', '/'.$url);
});
// ============================================================
// 表单自动
// ============================================================
/**
* 清洗IP:去掉端口号
* - "113.77.18.197-42588" => "113.77.18.197"
* - "113.77.18.197:80" => "113.77.18.197"
* - "2600:3c18::2000:eff:fec6:604f" => 原样返回
*/
function xr_clean_ip($ip) {
$ip = trim((string)$ip);
if (strpos($ip, '-') !== false) {
$ip = explode('-', $ip)[0];
}
if (strpos($ip, ':') !== false && substr_count($ip, ':') < 2) {
$ip = explode(':', $ip)[0];
}
return $ip;
}
/**
* 判断是否为 IPv6
*/
function xr_is_ipv6($ip) {
return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
}
/**
* IP 转(支持 IPv4 + IPv6)
*/
function xr_ip2country($ip) {
if (!$ip) return 'NoIP';
if ($ip == '127.0.0.1' || $ip == '::1') return 'Local';
if (strpos($ip, '10.') === 0 || strpos($ip, '192.168.') === 0 || strpos($ip, '172.') === 0) return 'Local';
$is_v6 = xr_is_ipv6($ip);
// 缓存:7 天
$cd = dirname(__FILE__) . '/../cache/xr_geo/';
if (!is_dir($cd)) @mkdir($cd, 0755, true);
$cf = $cd . md5($ip) . '.txt';
if (is_file($cf) && filemtime($cf) > time() - 604800) {
$c = trim(file_get_contents($cf));
if ($c && $c !== 'Unknown' && $c !== '中国·未知地区') {
return $c;
}
}
$country = '';
// 1) 系统 IP 库(仅 IPv4)
if (!$is_v6) {
try {
$prov = \Phpcmf\Service::L('ip')->province($ip);
if ($prov && $prov !== '未知地区') {
$country = '中国·' . $prov;
}
} catch (\Throwable $e) {}
}
// 2) ip-api.com(IPv4 + IPv6)
if (!$country && function_exists('curl_init')) {
try {
$api = 'http://ip-api.com/json/' . urlencode($ip) . '?fields=status,country,countryCode&lang=zh-CN';
$ch = curl_init($api);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
CURLOPT_HTTPHEADER => ['User-Agent: Mozilla/5.0'],
]);
$r = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($r && $code == 200) {
$j = json_decode($r, true);
if (!empty($j['country'])) {
$country = $j['country'];
} elseif (!empty($j['countryCode'])) {
$country = $j['countryCode'];
}
}
} catch (\Throwable $e) {}
}
$country = $country ?: 'Unknown';
@file_put_contents($cf, $country);
return $country;
}
/**
* 供邮件模板调用的全局函数
*/
function xr_country_label($ip_raw) {
$ip = xr_clean_ip($ip_raw);
if (!$ip) return '';
return xr_ip2country($ip);
}
// ============================================================
// 钩子:表单提交后写 country
// ============================================================
Hooks::on('form_post_after', function($data) {
$id = (int)($data['id'] ?? 0);
if (!$id) return $data;
$db_table = '';
try {
$c = \Phpcmf\Service::C();
$form_table = $c->form['table'] ?? '';
if (strpos($form_table, 'xunpan') !== false) {
$db_table = 'dr_1_form_xunpan';
} elseif (strpos($form_table, 'liuyan') !== false) {
$db_table = 'dr_1_form_liuyan';
}
} catch (\Throwable $e) {}
if (!$db_table) return $data;
$raw_ip = trim((string)($data['inputip'] ?? ''));
$ip = xr_clean_ip($raw_ip);
if (!$ip) return $data;
$country = xr_ip2country($ip);
try {
\Phpcmf\Service::M()->db->query(
"UPDATE `{$db_table}` SET `country` = " .
\Phpcmf\Service::M()->db->escape($country) .
" WHERE `id` = " . $id
);
} catch (\Throwable $e) {}
return $data;
});五、邮件模板配置
在迅睿后台 → 表单设置 → 邮件通知模板 中,添加显示:
<div> <span>Country</span> <div>{php echo xr_country_label($inputip);}</div> </div>为什么不用
{$country}?邮件通知在
form_post_after之前发送,$data['country']此时为空。通过{php}标签直接调用函数,利用缓存机制,几乎零延迟。
六、新增表单支持
如果需要给新的表单添加功能:
1. 数据库加列
ALTER TABLE `dr_1_form_新表单名` ADD `country` VARCHAR(50) DEFAULT '' COMMENT '访客';
2. 代码中添加映射
在 hooks.php 的 form_post_after 钩子里,找到这段:
if (strpos($form_table, 'xunpan') !== false) { $db_table = 'dr_1_form_xunpan'; } elseif (strpos($form_table, 'liuyan') !== false) { $db_table = 'dr_1_form_liuyan'; }照着加一行:
} elseif (strpos($form_table, '新表单名') !== false) { $db_table = 'dr_1_form_新表单名'; }七、常见问题
Q1:后台 country 列没有值?
排查项 | 操作 |
|---|---|
钩子是否加载 | 查看 |
表名是否正确 | phpMyAdmin 确认实际表名(多站点有 |
|
|
CDN 的 | 确认 |
Q2:IPv6 显示为 Unknown?
清缓存:
rm -f cache/xr_geo/*.txt测试 API:
curl "http://ip-api.com/json/你的IPv6?fields=country"确认服务器有外网:部分服务器屏蔽了到 ip-api.com 的连接
Q3:ip-api.com 有限制吗?
免费版:每分钟 45 次请求
7 天本地缓存大幅降低请求量,正常业务不会触发限制
如需更高限额,可注册付费版(更换 API 地址即可)
Q4:缓存文件太多?
7 天自动过期,也可定期清理:
# 删除 7 天前的缓存 find cache/xr_geo/ -name "*.txt" -mtime +7 -delete
Q5:会影响表单提交速度吗?
缓存命中:< 1ms
查询(API 调用):约 200-500ms
失败静默:不影响表单正常提交
八、文件结构总览
网站根目录/ ├── config/ │ └── hooks.php ← 本教程核心文件 ├── cache/ │ └── xr_geo/ ← IP 地理缓存(自动创建) │ ├── a1b2c3...txt │ └── d4e5f6...txt └── ...
九、版本记录
版本 | 日期 | 说明 |
|---|---|---|
v1.0 | 2026-09-23 | 初版,支持 xunpan 单表单 |
v1.1 | 2026-09-23 | 修复表名前缀问题,支持多站点 |
v1.2 | 2026-10-09 | 新增 liuyan 表单、IPv6 支持、邮件模板函数 |
v1.3 | 2026-10-09 | 修复缓存脏数据、优化 IPv6 查询稳定性 |
上一篇:没有了


预约免费服务咨询