/** * package utils * 作用:IP 归属地查询(基于本地 ip2region.xdb 离线库) */ package utils import ( "log" "strings" "sync" "github.com/lionsoul2014/ip2region/binding/golang/xdb" ) var ( ipSearcher *xdb.Searcher ipSearcherOnce sync.Once ipSearcherErr error ipCache sync.Map ) /** * InitIP2Region * 功能:加载 ip2region.xdb,服务启动时调用一次 */ func InitIP2Region(dbPath string) error { ipSearcherOnce.Do(func() { ipSearcher, ipSearcherErr = xdb.NewWithFileOnly(xdb.IPv4, dbPath) if ipSearcherErr != nil { log.Printf("❌ [IP2Region] 加载失败: %v", ipSearcherErr) return } log.Printf("✅ [IP2Region] 已加载: %s", dbPath) }) return ipSearcherErr } /** * GetIPLocation * 功能:查询 IP 归属地,内网/本地返回「本地」 */ func GetIPLocation(ip string) string { if isLocalIP(ip) { return "本地" } if ip == "" { return "未知" } if cached, ok := ipCache.Load(ip); ok { return cached.(string) } if ipSearcher == nil { return "未知" } region, err := ipSearcher.Search(ip) if err != nil || region == "" { return "未知" } // 格式: 国家|区域|省份|城市|ISP parts := strings.Split(region, "|") labels := []string{} for _, p := range parts { p = strings.TrimSpace(p) if p != "" && p != "0" { labels = append(labels, p) } } result := "未知" if len(labels) > 0 { result = strings.Join(labels, " ") } ipCache.Store(ip, result) return result } /** * isLocalIP * 功能:判断是否为本地/内网 IP */ func isLocalIP(ip string) bool { if ip == "127.0.0.1" || ip == "localhost" || ip == "::1" { return true } if strings.HasPrefix(ip, "192.168.") || strings.HasPrefix(ip, "10.") { return true } if strings.HasPrefix(ip, "172.16.") || strings.HasPrefix(ip, "172.17.") || strings.HasPrefix(ip, "172.18.") || strings.HasPrefix(ip, "172.19.") || strings.HasPrefix(ip, "172.20.") || strings.HasPrefix(ip, "172.21.") || strings.HasPrefix(ip, "172.22.") || strings.HasPrefix(ip, "172.23.") || strings.HasPrefix(ip, "172.24.") || strings.HasPrefix(ip, "172.25.") || strings.HasPrefix(ip, "172.26.") || strings.HasPrefix(ip, "172.27.") || strings.HasPrefix(ip, "172.28.") || strings.HasPrefix(ip, "172.29.") || strings.HasPrefix(ip, "172.30.") || strings.HasPrefix(ip, "172.31.") { return true } return false }