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

PHP 獲取瀏覽器信息與客戶端地理位置完整解決方案

     編輯:PHP 相關知識

方案一:輕量級 User-Agent 解析器(無依賴)

原文代碼存在 $_server 大小寫錯誤、正則簡陋、無緩存等問題。現代化重構如下:

<?php
/**
 * ClientInfo - 瀏覽器/OS/IP 解析
 * 無第三方依賴,支持緩存、擴展
 */
class ClientInfo
{
    private array $ua;
    private array $server;
    private static array $cache = [];
    
    public function __construct(?array $server = null, ?string $userAgent = null)
    {
        $this->server = $server ?? $_SERVER;
        $this->ua = [
            'string' => $userAgent ?? ($this->server['HTTP_USER_AGENT'] ?? ''),
            'lower' => strtolower($userAgent ?? ($this->server['HTTP_USER_AGENT'] ?? '')),
        ];
    }
    
    /** 獲取瀏覽器名稱與版本 */
    public function getBrowser(): array
    {
        return $this->matchPatterns('browser', [
            ['Edge', 'Edg/([\d.]+)'],
            ['Chrome', 'Chrome/([\d.]+)'],
            ['Firefox', 'Firefox/([\d.]+)'],
            ['Safari', 'Version/([\d.]+).*Safari'],
            ['Opera', 'OPR/([\d.]+)'],
            ['IE', 'MSIE ([\d.]+)|Trident/.*rv:([\d.]+)'],
            ['WeChat', 'MicroMessenger/([\d.]+)'],
            ['QQ', 'QQ/([\d.]+)'],
        ]);
    }
    
    /** 獲取操作系統 */
    public function getOS(): array
    {
        return $this->matchPatterns('os', [
            ['Windows 11', 'Windows NT 10.0.*Build/22'],
            ['Windows 10', 'Windows NT 10.0'],
            ['Windows 8.1', 'Windows NT 6.3'],
            ['Windows 8', 'Windows NT 6.2'],
            ['Windows 7', 'Windows NT 6.1'],
            ['macOS', 'Mac OS X ([\d_]+)'],
            ['iOS', 'CPU iPhone OS ([\d_]+)'],
            ['iPadOS', 'CPU OS ([\d_]+).*iPad'],
            ['Android', 'Android ([\d.]+)'],
            ['Linux', 'Linux'],
            ['Ubuntu', 'Ubuntu'],
            ['CentOS', 'CentOS'],
        ]);
    }
    
    /** 獲取客戶端真實 IP */
    public function getRealIP(): string
    {
        $headers = [
            'HTTP_CF_CONNECTING_IP',      // Cloudflare
            'HTTP_X_FORWARDED_FOR',       // 標準代理
            'HTTP_X_REAL_IP',             // Nginx 代理
            'HTTP_CLIENT_IP',             // 部分代理
            'HTTP_X_CLUSTER_CLIENT_IP',   // 集群
            'REMOTE_ADDR',                // 直連
        ];
        
        foreach ($headers as $header) {
            if (!empty($this->server[$header])) {
                $ips = array_map('trim', explode(',', $this->server[$header]));
                foreach ($ips as $ip) {
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                        return $ip;
                    }
                }
                // 允許私有 IP(內網環境)
                return $ips[0];
            }
        }
        return 'unknown';
    }
    
    /** 獲取語言偏好 */
    public function getLanguage(): string
    {
        $acceptLang = $this->server['HTTP_ACCEPT_LANGUAGE'] ?? '';
        $langs = [];
        foreach (explode(',', $acceptLang) as $part) {
            [$lang, $q] = array_pad(explode(';q=', $part), 2, 1);
            $langs[trim($lang)] = (float)$q;
        }
        arsort($langs);
        $primary = array_key_first($langs);
        return match (substr($primary, 0, 2)) {
            'zh' => str_contains($primary, 'TW') || str_contains($primary, 'HK') ? '繁體中文' : '簡體中文',
            'en' => 'English',
            'ja' => '日本語',
            'ko' => '한국어',
            default => $primary
        };
    }
    
    /** 通用模式匹配 */
    private function matchPatterns(string $type, array $patterns): array
    {
        $cacheKey = $type . ':' . substr($this->ua['lower'], 0, 50);
        if (isset(self::$cache[$cacheKey])) return self::$cache[$cacheKey];
        
        foreach ($patterns as [$name, $pattern]) {
            if (preg_match('/' . $pattern . '/i', $this->ua['string'], $matches)) {
                $version = $matches[1] ?? '';
                // 格式化版本號
                if ($type === 'os' && $name === 'macOS') {
                    $version = str_replace('_', '.', $version);
                } elseif ($type === 'os' && in_array($name, ['iOS', 'iPadOS'])) {
                    $version = str_replace('_', '.', $version);
                }
                return self::$cache[$cacheKey] = ['name' => $name, 'version' => $version];
            }
        }
        return self::$cache[$cacheKey] = ['name' => 'Unknown', 'version' => ''];
    }
    
    /** 一次性獲取所有信息 */
    public function getAll(): array
    {
        return [
            'browser' => $this->getBrowser(),
            'os' => $this->getOS(),
            'ip' => $this->getRealIP(),
            'language' => $this->getLanguage(),
            'user_agent' => $this->ua['string'],
        ];
    }
}

// 使用示例
$client = new ClientInfo();
info($client->getAll());
/*
輸出示例:
[
  'browser' => ['name' => 'Chrome', 'version' => '119.0.6045.105'],
  'os' => ['name' => 'Windows 10', 'version' => ''],
  'ip' => '203.0.113.45',
  'language' => '簡體中文',
  'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...'
]
*/

方案二:IP 地理位置查詢(多服務商容灾)

interface IpGeoProvider
{
    public function locate(string $ip): ?array; // ['country','region','city','isp','lat','lng']
}

class IpGeoLocator
{
    private array $providers = [];
    
    public function __construct(array $providers = [])
    {
        $this->providers = $providers ?: [
            new SinaIpProvider(),
            new TencentIpProvider(),
            new IpApiComProvider(), // 國際備用
        ];
    }
    
    public function locate(string $ip): ?array
    {
        // 內網 IP 直接返回
        if ($this->isPrivateIp($ip)) {
            return ['country' => '內網', 'region' => '', 'city' => '局域網', 'isp' => ''];
        }
        
        foreach ($this->providers as $provider) {
            try {
                $result = $provider->locate($ip);
                if ($result && !empty($result['city'])) {
                    return $result;
                }
            } catch (Throwable $e) {
                error_log("IP定位失敗 [{$provider::class}]: {$e->getMessage()}");
                continue; // 嘗試下一個
            }
        }
        return null;
    }
    
    private function isPrivateIp(string $ip): bool
    {
        $long = ip2long($ip);
        return $long !== false && (
            ($long >= ip2long('10.0.0.0') && $long <= ip2long('10.255.255.255')) ||
            ($long >= ip2long('172.16.0.0') && $long <= ip2long('172.31.255.255')) ||
            ($long >= ip2long('192.168.0.0') && $long <= ip2long('192.168.255.255')) ||
            ($long >= ip2long('127.0.0.0') && $long <= ip2long('127.255.255.255'))
        );
    }
}

// 新浪接口實現
class SinaIpProvider implements IpGeoProvider
{
    public function locate(string $ip): ?array
    {
        $url = "https://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip={$ip}";
        $json = @file_get_contents($url);
        if (!$json) return null;
        
        $data = json_decode($json, true);
        if (!$data || $data['ret'] != 1) return null;
        
        return [
            'country' => $data['country'] ?? '',
            'region' => $data['province'] ?? '',
            'city' => $data['city'] ?? '',
            'isp' => $data['isp'] ?? '',
            'lat' => $data['lat'] ?? null,
            'lng' => $data['lng'] ?? null,
        ];
    }
}

// 騰訊接口實現
class TencentIpProvider implements IpGeoProvider
{
    public function locate(string $ip): ?array
    {
        $url = "https://apis.map.qq.com/ws/location/v1/ip?ip={$ip}&key=YOUR_KEY"; // 需申請 Key
        $json = @file_get_contents($url);
        if (!$json) return null;
        $data = json_decode($json, true);
        if (($data['status'] ?? 1) !== 0) return null;
        
        $result = $data['result'] ?? [];
        $ad = $result['ad_info'] ?? [];
        return [
            'country' => $ad['nation'] ?? '',
            'region' => $ad['province'] ?? '',
            'city' => $ad['city'] ?? '',
            'isp' => $result['isp'] ?? '',
            'lat' => $result['location']['lat'] ?? null,
            'lng' => $result['location']['lng'] ?? null,
        ];
    }
}

// 國際備用 ip-api.com
class IpApiComProvider implements IpGeoProvider
{
    public function locate(string $ip): ?array
    {
        $url = "https://ip-api.com/json/{$ip}?lang=zh-CN";
        $json = @file_get_contents($url);
        if (!$json) return null;
        $data = json_decode($json, true);
        if (($data['status'] ?? '') !== 'success') return null;
        
        return [
            'country' => $data['country'] ?? '',
            'region' => $data['regionName'] ?? '',
            'city' => $data['city'] ?? '',
            'isp' => $data['isp'] ?? '',
            'lat' => $data['lat'] ?? null,
            'lng' => $data['lon'] ?? null,
        ];
    }
}

// 使用
$locator = new IpGeoLocator();
$client = new ClientInfo();
$ip = $client->getRealIP();
$geo = $locator->locate($ip);
echo "IP: {$ip}\n";
echo "位置: " . ($geo ? "{$geo['country']} {$geo['region']} {$geo['city']} {$geo['isp']}" : '未知') . "\n";

生產環境優化建議

  • 本地化 IP 庫:高並發場景下載 ip2region、MaxMind GeoIP2 離線庫,微秒級查詢
  • Redis 緩存:IP 歸屬地 24h 不變,緩存命中率極高
  • 異步記錄:日誌記錄放入隊列,不阻塞主請求
  • 隱私合規:GDPR/個信法要求,IP 屬個人信息,需脫敏存儲、最小化采集

組合使用「輕量 UA 解析 + 多源 IP 定位 + 離線庫兜底」,可構建高可用、低延遲的客戶端畫像服務。

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