Files
web_template/internal/geoip/geoip.go
2026-09-09 02:39:57 +08:00

108 lines
2.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, " ")
}