package utils import ( "context" "encoding/json" "fmt" "io" "math/rand" "net/http" "time" ) // 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) } body, err := io.ReadAll(resp.Body) 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) }) }