feat: 排行榜不再展示任何IP,用户列显示地区(ip2region v4 离线库)+浏览器(UA解析);IP仅后台保存,页面上方显示IP+地区
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user