package geoip import ( "encoding/json" "fmt" "net/http" "strings" "sync" "time" ) // ipLocation 缓存的 IP 归属地信息 type ipLocation struct { Location string FetchedAt time.Time } // Service IP 归属地查询服务(基于 ip-api.com 免费接口 + 内存缓存) type Service struct { cache sync.Map } // NewService 创建 IP 归属地查询服务 func NewService() *Service { return &Service{} } // ipApiResponse ip-api.com 返回结构 type ipApiResponse struct { Status string `json:"status"` Country string `json:"country"` RegionName string `json:"regionName"` City string `json:"city"` ISP string `json:"isp"` Query string `json:"query"` } // Lookup 查询 IP 归属地,带内存缓存(24小时有效) func (s *Service) Lookup(ip string) string { // 本地/内网 IP 直接返回 if ip == "127.0.0.1" || ip == "::1" || ip == "" || strings.HasPrefix(ip, "192.168.") || strings.HasPrefix(ip, "10.") || strings.HasPrefix(ip, "172.") { return "本地网络" } // 检查缓存(24小时有效) if v, ok := s.cache.Load(ip); ok { loc := v.(*ipLocation) if time.Since(loc.FetchedAt) < 24*time.Hour { return loc.Location } s.cache.Delete(ip) } // 查询 ip-api.com(免费接口,限制 45 次/分钟) location := s.queryIPAPI(ip) if location == "" { return "未知" } // 写入缓存 s.cache.Store(ip, &ipLocation{ Location: location, FetchedAt: time.Now(), }) return location } // queryIPAPI 调用 ip-api.com 查询 func (s *Service) queryIPAPI(ip string) string { client := &http.Client{Timeout: 3 * time.Second} url := fmt.Sprintf("http://ip-api.com/json/%s?lang=zh-CN", ip) resp, err := client.Get(url) if err != nil { return "" } defer resp.Body.Close() var result ipApiResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return "" } if result.Status != "success" { return "" } parts := []string{} if result.Country != "" { parts = append(parts, result.Country) } if result.RegionName != "" { parts = append(parts, result.RegionName) } if result.City != "" { parts = append(parts, result.City) } if result.ISP != "" { parts = append(parts, result.ISP) } return strings.Join(parts, " ") }