TaobaoIPQuery2 完整實現
<?php
class TaobaoIPQuery2 {
private const API_URL = 'https://ip.taobao.com/service/getIpInfo.php';
private const TIMEOUT = 3.0;
private string $ip;
private ?object $data = null;
public function __construct(string $ip) {
if (empty($ip)) throw new InvalidArgumentException('IP address required');
$this->ip = $this->normalizeIp($ip);
$this->fetch();
}
private function normalizeIp(string $ip): string {
// 取第一個非 unknown IP (代理鏈場景)
$ips = array_map('trim', explode(',', $ip));
foreach ($ips as $candidate) {
if ($candidate !== 'unknown' && filter_var($candidate, FILTER_VALIDATE_IP)) {
return $candidate;
}
}
return $ips[0] ?? '0.0.0.0';
}
private function fetch(): void {
$url = self::API_URL . '?ip=' . urlencode($this->ip);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => self::TIMEOUT,
CURLOPT_SSL_VERIFYPEER => false,
]);
$resp = curl_exec($ch);
$errno = curl_errno($ch);
curl_close($ch);
if ($errno) throw new RuntimeException("cURL error: " . curl_error($ch));
if (!$resp) throw new RuntimeException('Empty response');
$json = json_decode($resp);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON error: ' . json_last_error_msg());
}
if (!isset($json->code) || $json->code !== 0) {
throw new RuntimeException('API error: ' . ($json->msg ?? 'Unknown'));
}
$this->data = $json->data;
}
public function getCountry(): string { return $this->data->country ?? ''; }
public function getRegion(): string { return $this->data->region ?? ''; }
public function getCity(): string { return $this->data->city ?? ''; }
public function getIsp(): string { return $this->data->isp ?? ''; }
public function getArea(): string { return $this->data->area ?? ''; }
public function getIp(): string { return $this->data->ip ?? $this->ip; }
public function toArray(): array { return (array)$this->data; }
}
// 使用
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
$query = new TaobaoIPQuery2($ip);
echo "{$query->getCountry()} {$query->getRegion()} {$query->getCity()} {$query->getIsp()}";
新浪 IP 接口替代
class SinaIPQuery {
private const API_URL = 'https://ipapi.sinaapp.com/';
public static function query(string $ip): ?array {
$url = self::API_URL . '?ip=' . urlencode($ip) . '&type=json';
$json = @file_get_contents($url);
if (!$json) return null;
$data = json_decode($json, true);
return $data['ret'] === 1 ? $data['data'] : null;
}
}
// 返回格式: ['country'=>'中国', 'province'=>'北京', 'city'=>'北京', 'isp'=>'联通', ...]
生產環境架構建議
| 層級 | 方案 | 優點 |
| L1 緩存 | Redis 緩存查詢結果 (TTL 30 天) | 毫秒級響應、減輕外部 API 壓力 |
| L2 異步 | 消息隊列異步查詢,不阻塞主流程 | 高併發下穩定 |
| L3 降級 | 多源輪詢 (淘寶→新浪→ip2location 本地庫) | 單點故障不影響業務 |
| L4 離線 | MaxMind GeoIP2 / IP2Location 本地數據庫 | 零網絡延遲、無外部依賴、適合超高頻 |
Composer 現代替代
composer require geoip2/geoip2:~2.0
// 使用 MaxMind 官方庫
$reader = new GeoIp2\Database\Reader('/path/to/GeoLite2-City.mmdb');
$record = $reader->city($ip);
echo $record->country->name; // China
echo $record->city->name; // Beijing