107 lines
4.0 KiB
Go
107 lines
4.0 KiB
Go
package utils
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"math/rand"
|
||
"net/http"
|
||
"time"
|
||
|
||
"golang.org/x/text/encoding/simplifiedchinese"
|
||
"golang.org/x/text/transform"
|
||
)
|
||
|
||
// LocationResponse represents the response from IP location API
|
||
type LocationResponse struct {
|
||
IP string `json:"ip"`
|
||
Pro string `json:"pro"`
|
||
ProCode string `json:"proCode"`
|
||
City string `json:"city"`
|
||
CityCode string `json:"cityCode"`
|
||
Region string `json:"region"`
|
||
RegionCode string `json:"regionCode"`
|
||
Addr string `json:"addr"`
|
||
RegionNames string `json:"regionNames"`
|
||
Err string `json:"err"`
|
||
}
|
||
|
||
// userAgents is a list of common browser user agents for random selection
|
||
var userAgents = []string{
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
|
||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
|
||
"Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
|
||
"Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
|
||
}
|
||
|
||
// randomUserAgent returns a random user agent string
|
||
func randomUserAgent() string {
|
||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||
return userAgents[r.Intn(len(userAgents))]
|
||
}
|
||
|
||
// GetLocation 获取IP的地理位置信息
|
||
// 调用太平洋网络IP地址查询API,返回省份和城市
|
||
func GetLocation(ctx context.Context, ip string) (province string, city string, err error) {
|
||
url := fmt.Sprintf("https://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true", ip)
|
||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||
if err != nil {
|
||
return "", "", fmt.Errorf("create request failed: %w", err)
|
||
}
|
||
|
||
// Set random user agent to simulate browser
|
||
req.Header.Set("User-Agent", randomUserAgent())
|
||
req.Header.Set("Accept", "application/json, text/plain, */*")
|
||
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
||
req.Header.Set("Referer", "https://whois.pconline.com.cn/")
|
||
|
||
client := &http.Client{
|
||
Timeout: 5 * time.Second,
|
||
}
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return "", "", fmt.Errorf("request failed: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
return "", "", fmt.Errorf("request failed with status: %d", resp.StatusCode)
|
||
}
|
||
|
||
// API返回的是GBK编码,需要转换为UTF-8
|
||
utf8Reader := transform.NewReader(resp.Body, simplifiedchinese.GBK.NewDecoder())
|
||
body, err := io.ReadAll(utf8Reader)
|
||
if err != nil {
|
||
return "", "", fmt.Errorf("read response failed: %w", err)
|
||
}
|
||
|
||
var location LocationResponse
|
||
if err := json.Unmarshal(body, &location); err != nil {
|
||
return "", "", fmt.Errorf("parse response failed: %w", err)
|
||
}
|
||
|
||
// Check for error in response
|
||
if location.Err != "" {
|
||
return "", "", fmt.Errorf("API error: %s", location.Err)
|
||
}
|
||
|
||
return location.Pro, location.City, nil
|
||
}
|
||
|
||
// GetLocationWithCache 获取IP的地理位置信息(带缓存)
|
||
// 如果缓存中有则直接返回,否则调用API并缓存结果
|
||
func GetLocationWithCache(ctx context.Context, ip string, cache func(key string, fetch func() (string, string, error)) (string, string, error)) (province string, city string, err error) {
|
||
return cache(ip, func() (string, string, error) {
|
||
return GetLocation(ctx, ip)
|
||
})
|
||
}
|