原文代碼存在 $_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)...'
]
*/
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";
ip2region、MaxMind GeoIP2 離線庫,微秒級查詢組合使用「輕量 UA 解析 + 多源 IP 定位 + 離線庫兜底」,可構建高可用、低延遲的客戶端畫像服務。