125 lines
3.1 KiB
Go
125 lines
3.1 KiB
Go
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
|
||
}
|