根據訪客 IP 位址解析地理位置(國家、城市、ISP 等)是個性化內容、安全防護、統計分析的常見需求。本文示範從取得真實 IP 到呼叫第三方 API 解析的完整流程,並提供錯誤處理與效能優化建議。
考慮代理、負載平衡、CDN 等網路拓撲,需依序檢查多個 HTTP 標頭:
<?php
/**
* 取得客戶端真實 IP 位址
* @return string IPv4 或 IPv6 位址
*/
function getRealClientIP(): string {
// 1. 標準代理標頭(最常見)
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// 可能包含多個 IP,取第一個(原始客戶端)
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ips[0]);
}
// 2. 另一種代理標頭
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
}
// 3. 直接連線或無代理
if (!empty($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
}
// 4. 兼容舊版 PHP / CGI 模式
if (getenv('HTTP_X_FORWARDED_FOR')) {
$ips = explode(',', getenv('HTTP_X_FORWARDED_FOR'));
return trim($ips[0]);
}
if (getenv('HTTP_CLIENT_IP')) {
return getenv('HTTP_CLIENT_IP');
}
if (getenv('REMOTE_ADDR')) {
return getenv('REMOTE_ADDR');
}
return '0.0.0.0'; // 無法判斷
}
// 使用範例
$clientIP = getRealClientIP();
echo "訪客 IP:$clientIP";
?>
常用免費介面(需遵守呼叫頻率限制):
| 服務商 | API 端點 | 回傳格式 | 頻率限制 | 備註 |
|---|---|---|---|---|
| 淘寶 IP 庫 | https://ip.taobao.com/service/getIpInfo.php?ip= | JSON | ~10 QPS | 國內準確度高,支援 IPv6 |
| 新浪 IP 查詢 | https://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip= | JSON | 較寬鬆 | 部分地區精度一般 |
| ip-api.com | https://ip-api.com/json/ | JSON | 45 req/min (免費) | 國際通用,含時區、ISP |
| ipinfo.io | https://ipinfo.io/{ip}/json | JSON | 50k req/mo (免費) | HTTPS、詳細組織資訊 |
<?php
class IPGeoLocator {
private const API_URL = 'https://ip.taobao.com/service/getIpInfo.php?ip=';
private const TIMEOUT = 3; // 秒
/**
* 解析 IP 地理位置
* @param string $ip 目標 IP,空值則自動偵測
* @return array|false 成功回傳結構化資料,失敗回傳 false
*/
public static function locate(string $ip = ''): array|false {
if (empty($ip)) {
$ip = self::getRealClientIP();
}
// 驗證 IP 格式
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
trigger_error("無效 IP 格式: $ip", E_USER_WARNING);
return false;
}
$url = self::API_URL . urlencode($ip);
$context = stream_context_create([
'http' => [
'timeout' => self::TIMEOUT,
'ignore_errors' => true,
]
]);
$response = @file_get_contents($url, false, $context);
if ($response === false) {
trigger_error("API 請求失敗或超時: $url", E_USER_WARNING);
return false;
}
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
trigger_error("API 回傳 JSON 解析失敗", E_USER_WARNING);
return false;
}
// 淘寶 API 回傳結構:code=0 成功,data 含 country, region, city, isp 等
if (($data['code'] ?? -1) !== 0) {
trigger_error("API 回傳錯誤: " . ($data['msg'] ?? '未知'), E_USER_WARNING);
return false;
}
return [
'ip' => $ip,
'country' => $data['data']['country'] ?? '',
'region' => $data['data']['region'] ?? '',
'city' => $data['data']['city'] ?? '',
'isp' => $data['data']['isp'] ?? '',
'area' => $data['data']['area'] ?? '', // 區域(如華東、華南)
'area_id' => $data['data']['area_id'] ?? '',
'region_id'=> $data['data']['region_id'] ?? '',
'city_id' => $data['data']['city_id'] ?? '',
'isp_id' => $data['data']['isp_id'] ?? '',
'raw' => $data['data'], // 保留原始資料供擴充
];
}
private static function getRealClientIP(): string {
// 同上 getRealClientIP() 實作
// ...
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
}
// 使用範例
$geo = IPGeoLocator::locate('114.114.114.114'); // 或不傳參數自動偵測
if ($geo) {
echo "國家:{$geo['country']}
";
echo "省份:{$geo['region']}
";
echo "城市:{$geo['city']}
";
echo "ISP:{$geo['isp']}
";
// 直接存取物件屬性風格(若 json_decode 不加 true)
// $obj = json_decode($response); echo $obj->data->city;
} else {
echo "地理位置解析失敗";
}
?>
針對高併發場景,建議使用 MaxMind GeoIP2 或 ip2region 等本地資料庫:
<?php
// Composer 安裝:composer require geoip2/geoip2:~2.0
use GeoIp2\Database\Reader;
$reader = new Reader('/path/to/GeoLite2-City.mmdb');
$record = $reader->city('114.114.114.114');
echo $record->country->isoCode; // CN
echo $record->country->name; // China
echo $record->mostSpecificSubdivision->name; // 省份
echo $record->city->name; // 城市
echo $record->location->latitude; // 緯度
echo $record->location->longitude; // 經度
echo $record->traits->isp; // ISP
?>
優勢:微秒級解析、無網路依賴、無頻率限制、資料可定期更新。
適用:即時風控、廣告定向、日誌分析、高併發 API。
SET ip:geo:{ip} {json} EX 86400IP 地理位置解析看似簡單,實則涉及網路拓撲識別、第三方服務可靠性、效能與合規多重考量。建議採用「線上 API 開發測試 → 本地資料庫生產部署 → 多層快取與降級」的演進路徑,構建穩健可擴展的定位服務。