整合 IP 取得邏輯與新浪 IP 歸屬地查詢 API,封裝為可重用類別,支援 JSON/純文字格式、Cookie 快取、代理穿透偵測,適用於訪客統計、內容地域化、安全風控等場景。
<?php
class IPLocator {
private $config = [];
private $sinaApi = 'https://int.dpool.sina.com.cn/iplookup/iplookup.php';
public function __construct(array $options = []) {
$defaults = [
'format' => 'json', // json | text
'timeout' => 5, // 秒
'cache_ttl' => 3600, // 秒,0 關閉快取
'proxy_headers' => ['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'],
];
$this->config = array_merge($defaults, $options);
}
/**
* 取得客戶端真實 IP (支援代理穿透)
*/
public function getClientIP() {
foreach ($this->config['proxy_headers'] as $header) {
if (!empty($_SERVER[$header]) && $_SERVER[$header] !== 'unknown') {
// 取第一個非私有 IP (代理鏈中最原始的)
$ips = array_map('trim', explode(',', $_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';
}
/**
* 查詢 IP 歸屬地
* @param string|null $ip 指定 IP,null 為自動偵測
* @return array|string|false
*/
public function locate($ip = null) {
$ip = $ip ?? $this->getClientIP();
// 驗證 IP 格式
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
return false;
}
// 快取檢查
$cacheKey = 'ip_loc_' . md5($ip);
if ($this->config['cache_ttl'] > 0) {
// 這裡可整合 Redis/Memcached/APCu
// $cached = Cache::get($cacheKey);
// if ($cached) return $cached;
}
// 建立請求 URL
$url = $this->sinaApi . '?ip=' . urlencode($ip) . '&format=' . $this->config['format'];
// cURL 請求
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->config['timeout'],
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_USERAGENT => 'PHP IPLocator/1.0',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $httpCode !== 200) {
return false;
}
// 格式化回應
$result = $this->config['format'] === 'json' ? json_decode($response, true) : $response;
// 新浪 API 文字格式為 GBK,需轉碼
if ($this->config['format'] === 'text') {
$result = iconv('GBK', 'UTF-8//IGNORE', $result);
}
// 寫入快取
// if ($this->config['cache_ttl'] > 0) Cache::set($cacheKey, $result, $this->config['cache_ttl']);
return $result;
}
/**
* 靜態快速呼叫
*/
public static function getLocation($ip = null, $format = 'json') {
$locator = new self(compact('format'));
return $locator->locate($ip);
}
}
// 使用範例
$locator = new IPLocator(['format' => 'json', 'cache_ttl' => 3600]);
// 1. 取得當前訪客 IP
$ip = $locator->getClientIP();
echo "訪客 IP: $ip\n";
// 2. 查詢歸屬地 (JSON 格式)
$location = $locator->locate();
if ($location) {
echo "國家: " . ($location['country'] ?? '未知') . "\n";
echo "省份: " . ($location['province'] ?? '未知') . "\n";
echo "城市: " . ($location['city'] ?? '未知') . "\n";
echo "ISP: " . ($location['isp'] ?? '未知') . "\n";
}
// 3. 指定 IP 查詢
$location = IPLocator::getLocation('8.8.8.8');
var_dump($location);
?>
{
"ret": 1,
"start": "8.8.8.0",
"end": "8.8.8.255",
"country": "美国",
"province": "加利福尼亚",
"city": "山景城",
"district": "",
"isp": "Google",
"type": "DNS服务器",
"desc": "Google Public DNS"
}
8.8.8.0 8.8.8.255 美国 加利福尼亚 山景城 Google DNS服务器 Google Public DNS
<script>
// 後端直接輸出 JS 變數
var visitorIP = "<?php echo (new IPLocator(['format'=>'json']))->getClientIP(); ?>";
var locationData = <?php echo json_encode((new IPLocator(['format'=>'json']))->locate()); ?>;
console.log('IP:', visitorIP);
console.log('位置:', locationData);
// 顯示在地化內容
if (locationData && locationData.city) {
document.getElementById('local-content').textContent =
'歡迎來自 ' + locationData.city + ' 的訪客!';
}
</script>
| 方案 | 準確度 | 速度 | 成本 | 維護 |
|---|---|---|---|---|
| 新浪 API | 中 (國內較準) | 慢 (網路請求) | 免費 | 零維護 |
| ip-api.com | 中 | 中 | 免費/付費 | 零維護 |
| MaxMind GeoIP2 (本地) | 高 | 極快 (本地) | 免費/授權費 | 定期更新資料庫 |
| ipinfo.io | 高 | 快 | 免費額度/付費 | 零維護 |