首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 依據 IP 判斷地理位置資訊的實作範例

     編輯:PHP 相關知識

根據訪客 IP 位址解析地理位置(國家、城市、ISP 等)是個性化內容、安全防護、統計分析的常見需求。本文示範從取得真實 IP 到呼叫第三方 API 解析的完整流程,並提供錯誤處理與效能優化建議。

第一步:精確取得客戶端真實 IP

考慮代理、負載平衡、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

常用免費介面(需遵守呼叫頻率限制):

服務商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.comhttps://ip-api.com/json/JSON45 req/min (免費)國際通用,含時區、ISP
ipinfo.iohttps://ipinfo.io/{ip}/jsonJSON50k req/mo (免費)HTTPS、詳細組織資訊

以淘寶 API 為例的完整解析類

<?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 "地理位置解析失敗"; } ?>

進階:本地 IP 資料庫離線解析(高效能、無頻率限制)

針對高併發場景,建議使用 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。

錯誤處理與快取策略

  • 快取解析結果:同一 IP 短時間內重複查詢無意義,用 Redis 快取 24 小時 SET ip:geo:{ip} {json} EX 86400
  • 降級策略:API 失敗時回傳預設值或啟用本地資料庫備援
  • 非同步處理:記錄 IP 至佇列,背景任務批次解析,不阻塞主流程
  • 隱私合規:依 GDPR/個資法規範,IP 屬個人資料,需告知用途、最小化保留、提供刪除機制

總結

IP 地理位置解析看似簡單,實則涉及網路拓撲識別、第三方服務可靠性、效能與合規多重考量。建議採用「線上 API 開發測試 → 本地資料庫生產部署 → 多層快取與降級」的演進路徑,構建穩健可擴展的定位服務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved