108 lines
2.2 KiB
Go
108 lines
2.2 KiB
Go
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, " ")
|
||
}
|