Files
Ant-Browser/backend/app_proxy_location.go

194 lines
6.6 KiB
Go

package backend
import (
"encoding/json"
"fmt"
"strings"
"time"
)
type ProxyLocationResolveResult struct {
ProxyId string `json:"proxyId"`
Ok bool `json:"ok"`
Auto bool `json:"auto"`
Source string `json:"source"`
Error string `json:"error"`
IP string `json:"ip"`
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Timezone string `json:"timezone"`
Lang string `json:"lang"`
Health *ProxyIPHealthResult `json:"health,omitempty"`
Alternates []ProxyLocationOption `json:"alternates,omitempty"`
ResolvedAt string `json:"resolvedAt"`
}
type ProxyLocationOption struct {
Label string `json:"label"`
Timezone string `json:"timezone"`
Lang string `json:"lang"`
}
var countryLocaleDefaults = map[string]ProxyLocationOption{
"CN": {Label: "中国", Timezone: "Asia/Shanghai", Lang: "zh-CN"},
"HK": {Label: "中国香港", Timezone: "Asia/Hong_Kong", Lang: "zh-HK"},
"TW": {Label: "中国台湾", Timezone: "Asia/Taipei", Lang: "zh-TW"},
"US": {Label: "美国", Timezone: "America/New_York", Lang: "en-US"},
"GB": {Label: "英国", Timezone: "Europe/London", Lang: "en-GB"},
"JP": {Label: "日本", Timezone: "Asia/Tokyo", Lang: "ja-JP"},
"KR": {Label: "韩国", Timezone: "Asia/Seoul", Lang: "ko-KR"},
"SG": {Label: "新加坡", Timezone: "Asia/Singapore", Lang: "en-SG"},
"DE": {Label: "德国", Timezone: "Europe/Berlin", Lang: "de-DE"},
"FR": {Label: "法国", Timezone: "Europe/Paris", Lang: "fr-FR"},
"NL": {Label: "荷兰", Timezone: "Europe/Amsterdam", Lang: "nl-NL"},
"CA": {Label: "加拿大", Timezone: "America/Toronto", Lang: "en-CA"},
"AU": {Label: "澳大利亚", Timezone: "Australia/Sydney", Lang: "en-AU"},
"RU": {Label: "俄罗斯", Timezone: "Europe/Moscow", Lang: "ru-RU"},
"BR": {Label: "巴西", Timezone: "America/Sao_Paulo", Lang: "pt-BR"},
"IN": {Label: "印度", Timezone: "Asia/Kolkata", Lang: "en-IN"},
}
var cityTimezoneDefaults = map[string]string{
"US|new york": "America/New_York",
"US|los angeles": "America/Los_Angeles",
"US|san francisco": "America/Los_Angeles",
"US|chicago": "America/Chicago",
"US|denver": "America/Denver",
"US|phoenix": "America/Phoenix",
"CA|toronto": "America/Toronto",
"CA|vancouver": "America/Vancouver",
"AU|sydney": "Australia/Sydney",
"AU|melbourne": "Australia/Melbourne",
"AU|perth": "Australia/Perth",
}
func (a *App) BrowserProxyResolveLocation(proxyId string) ProxyLocationResolveResult {
proxyId = strings.TrimSpace(proxyId)
resolvedAt := time.Now().Format(time.RFC3339)
if proxyId == "" || strings.EqualFold(proxyId, "__direct__") {
return ProxyLocationResolveResult{ProxyId: proxyId, Ok: false, Auto: false, Source: "manual", Error: "直连或未选择代理,请手动选择定位", ResolvedAt: resolvedAt}
}
if cached, ok := a.cachedProxyIPHealthResult(proxyId); ok && cached.Ok {
return buildProxyLocationResolveResult(proxyId, cached, "cache", resolvedAt)
}
health := a.BrowserProxyCheckIPHealth(proxyId)
if !health.Ok {
return ProxyLocationResolveResult{ProxyId: proxyId, Ok: false, Auto: false, Source: health.Source, Error: health.Error, Health: &health, ResolvedAt: resolvedAt}
}
return buildProxyLocationResolveResult(proxyId, health, "ip_health", resolvedAt)
}
func (a *App) cachedProxyIPHealthResult(proxyId string) (ProxyIPHealthResult, bool) {
if a == nil || a.browserMgr == nil || a.browserMgr.ProxyDAO == nil {
return ProxyIPHealthResult{}, false
}
proxies, err := a.browserMgr.ProxyDAO.List()
if err != nil {
return ProxyIPHealthResult{}, false
}
for _, item := range proxies {
if !strings.EqualFold(strings.TrimSpace(item.ProxyId), proxyId) || strings.TrimSpace(item.LastIPHealthJSON) == "" {
continue
}
var result ProxyIPHealthResult
if err := json.Unmarshal([]byte(item.LastIPHealthJSON), &result); err == nil && result.ProxyId != "" {
return result, true
}
}
return ProxyIPHealthResult{}, false
}
func buildProxyLocationResolveResult(proxyId string, health ProxyIPHealthResult, source string, resolvedAt string) ProxyLocationResolveResult {
option := resolveProxyLocationOption(health.Country, health.City)
ok := health.Ok && option.Timezone != "" && option.Lang != ""
result := ProxyLocationResolveResult{
ProxyId: proxyId,
Ok: ok,
Auto: ok,
Source: source,
IP: health.IP,
Country: health.Country,
Region: health.Region,
City: health.City,
Timezone: option.Timezone,
Lang: option.Lang,
Health: &health,
ResolvedAt: resolvedAt,
}
if !ok {
result.Error = fmt.Sprintf("无法根据地区自动匹配定位:%s %s", strings.TrimSpace(health.Country), strings.TrimSpace(health.City))
result.Alternates = defaultProxyLocationOptions()
}
return result
}
func resolveProxyLocationOption(country string, city string) ProxyLocationOption {
countryCode := normalizeCountryCode(country)
option := countryLocaleDefaults[countryCode]
if option.Timezone == "" {
return ProxyLocationOption{}
}
cityKey := countryCode + "|" + strings.ToLower(strings.TrimSpace(city))
if timezone := cityTimezoneDefaults[cityKey]; timezone != "" {
option.Timezone = timezone
}
return option
}
func normalizeCountryCode(country string) string {
value := strings.TrimSpace(country)
upper := strings.ToUpper(value)
if len(upper) == 2 {
return upper
}
switch strings.ToLower(value) {
case "china", "中国", "mainland china":
return "CN"
case "hong kong", "香港":
return "HK"
case "taiwan", "台湾":
return "TW"
case "united states", "usa", "us", "美国":
return "US"
case "united kingdom", "uk", "great britain", "英国":
return "GB"
case "japan", "日本":
return "JP"
case "south korea", "korea", "韩国":
return "KR"
case "singapore", "新加坡":
return "SG"
case "germany", "德国":
return "DE"
case "france", "法国":
return "FR"
case "netherlands", "荷兰":
return "NL"
case "canada", "加拿大":
return "CA"
case "australia", "澳大利亚":
return "AU"
case "russia", "俄罗斯":
return "RU"
case "brazil", "巴西":
return "BR"
case "india", "印度":
return "IN"
default:
return upper
}
}
func defaultProxyLocationOptions() []ProxyLocationOption {
return []ProxyLocationOption{
countryLocaleDefaults["US"],
countryLocaleDefaults["GB"],
countryLocaleDefaults["JP"],
countryLocaleDefaults["SG"],
countryLocaleDefaults["CN"],
}
}