package geo import ( "log" "os" "strings" "github.com/lionsoul2014/ip2region/binding/golang/xdb" ) // Geo 基于 ip2region v4 离线库的 IP 地区解析器(纯本地,无外部依赖) type Geo struct { searcher *xdb.Searcher } // New 按顺序尝试数据文件路径加载;全部失败时返回可用但禁用的实例(地区解析优雅降级) func New(paths ...string) *Geo { var dbPath string for _, p := range paths { if _, err := os.Stat(p); err == nil { dbPath = p break } } if dbPath == "" { log.Printf("[geo] 未找到 ip2region 数据文件(尝试: %v),地区解析已禁用", paths) return &Geo{} } handle, err := os.Open(dbPath) if err != nil { log.Printf("[geo] 打开 %s 失败: %v", dbPath, err) return &Geo{} } defer handle.Close() header, err := xdb.LoadHeader(handle) if err != nil { log.Printf("[geo] 读取 %s 头部失败: %v", dbPath, err) return &Geo{} } version, err := xdb.VersionFromHeader(header) if err != nil { log.Printf("[geo] 识别 %s 版本失败: %v", dbPath, err) return &Geo{} } // 整库载入内存,查询性能最好(约 11MB) buff, err := xdb.LoadContentFromFile(dbPath) if err != nil { log.Printf("[geo] 加载 %s 数据失败: %v", dbPath, err) return &Geo{} } searcher, err := xdb.NewWithBuffer(version, buff) if err != nil { log.Printf("[geo] 初始化搜索器失败: %v", err) return &Geo{} } log.Printf("[geo] ip2region 已加载(%.0f MB,%s)", float64(len(buff))/1024/1024, dbPath) return &Geo{searcher: searcher} } // Search 返回友好地区名(如 "广东省深圳市 电信"、"新加坡");解析失败返回 "" func (g *Geo) Search(ip string) string { if g == nil || g.searcher == nil || ip == "" { return "" } region, err := g.searcher.Search(ip) if err != nil { return "" } return Format(region) } // Format 把 ip2region v4 原始格式 "国家|省|市|ISP|国家码" 转为友好显示 // 例:中国|江苏省|南京市|0|CN → "江苏省南京市";中国|上海|上海市|电信|CN → "上海市 电信"; // United States|California|0|Google LLC|US → "United States California · Google LLC" func Format(region string) string { parts := strings.Split(region, "|") if len(parts) < 5 { return region } country, province, city, isp := parts[0], parts[1], parts[2], parts[3] if country == "" || country == "0" || country == "Reserved" { return "" // 保留地址/内网/IPv6 不在 v4 库中 } if isp == "0" { isp = "" } if country == "中国" { loc := "" switch { case province != "" && province != "0" && city != "" && city != "0": if city == province || strings.Contains(city, province) { loc = city // 直辖市:"上海市" } else { loc = province + city // "江苏省南京市" } case province != "" && province != "0": loc = province default: loc = "中国" } if isp != "" { loc += " " + isp } return loc } // 国外:国家 + 州/市(英文) loc := country switch { case province != "" && province != "0": loc += " " + province case city != "" && city != "0": loc += " " + city } if isp != "" { loc += " · " + isp } return loc }