55 "fmt"
66 "log"
77 "net"
8+ "os"
89 "strings"
910 "sync"
1011
@@ -14,11 +15,7 @@ import (
1415//go:embed geoip.db
1516var geoDBFS embed.FS
1617
17- var (
18- dbData []byte
19- err error
20- )
21-
18+ // IPInfo 表示 IP 地理位置信息
2219type IPInfo struct {
2320 Country string `maxminddb:"country"`
2421 CountryName string `maxminddb:"country_name"`
@@ -28,30 +25,104 @@ type IPInfo struct {
2825
2926var (
3027 db * maxminddb.Reader
28+ dbMu sync.RWMutex
3129 dbOnce sync.Once
30+ dbPath string // 当前加载的外部数据库路径
3231)
3332
34- func init () {
35- dbData , err = geoDBFS .ReadFile ("geoip.db" )
36- if err != nil {
37- log .Printf ("NG>> Failed to open geoip database: %v" , err )
33+ // Init 初始化 GeoIP 数据库
34+ // 如果 path 不为空,优先从外部文件加载;否则使用内嵌数据库
35+ func Init (path string ) error {
36+ dbMu .Lock ()
37+ defer dbMu .Unlock ()
38+
39+ // 关闭旧的数据库
40+ if db != nil {
41+ db .Close ()
42+ db = nil
3843 }
39- }
4044
41- // initDB 初始化GeoIP数据库,只执行一次
42- func initDB () {
43- if dbData != nil {
44- var err error
45- db , err = maxminddb .FromBytes (dbData )
45+ // 尝试从外部 MMDB 文件加载
46+ if path != "" {
47+ reader , err := loadFromFile (path )
4648 if err != nil {
47- log .Printf ("NG>> Failed to initialize geoip database: %v" , err )
49+ log .Printf ("NG>> 无法加载外部 GeoIP 数据库 %s: %v,回退到内嵌数据库" , path , err )
50+ } else {
51+ db = reader
52+ dbPath = path
53+ log .Printf ("NG>> 已加载外部 GeoIP 数据库: %s" , path )
54+ return nil
4855 }
4956 }
57+
58+ // 回退到内嵌数据库
59+ reader , err := loadEmbedded ()
60+ if err != nil {
61+ return fmt .Errorf ("无法加载内嵌 GeoIP 数据库: %w" , err )
62+ }
63+ db = reader
64+ dbPath = ""
65+ log .Printf ("NG>> 已加载内嵌 GeoIP 数据库" )
66+ return nil
67+ }
68+
69+ // Reload 重新加载 GeoIP 数据库(用于配置热更新)
70+ func Reload (path string ) error {
71+ return Init (path )
72+ }
73+
74+ // loadFromFile 从外部 MMDB 文件加载数据库
75+ func loadFromFile (path string ) (* maxminddb.Reader , error ) {
76+ if _ , err := os .Stat (path ); err != nil {
77+ return nil , fmt .Errorf ("文件不存在: %w" , err )
78+ }
79+ reader , err := maxminddb .Open (path )
80+ if err != nil {
81+ return nil , fmt .Errorf ("打开 MMDB 文件失败: %w" , err )
82+ }
83+ return reader , nil
84+ }
85+
86+ // loadEmbedded 从内嵌资源加载数据库
87+ func loadEmbedded () (* maxminddb.Reader , error ) {
88+ data , err := geoDBFS .ReadFile ("geoip.db" )
89+ if err != nil {
90+ return nil , fmt .Errorf ("读取内嵌数据失败: %w" , err )
91+ }
92+ reader , err := maxminddb .FromBytes (data )
93+ if err != nil {
94+ return nil , fmt .Errorf ("解析内嵌数据失败: %w" , err )
95+ }
96+ return reader , nil
97+ }
98+
99+ // ensureInit 确保数据库已初始化(兼容未显式调用 Init 的场景)
100+ func ensureInit () {
101+ dbOnce .Do (func () {
102+ dbMu .RLock ()
103+ initialized := db != nil
104+ dbMu .RUnlock ()
105+ if ! initialized {
106+ if err := Init ("" ); err != nil {
107+ log .Printf ("NG>> GeoIP 自动初始化失败: %v" , err )
108+ }
109+ }
110+ })
50111}
51112
113+ // GetDBPath 返回当前加载的数据库路径,空字符串表示使用内嵌数据库
114+ func GetDBPath () string {
115+ dbMu .RLock ()
116+ defer dbMu .RUnlock ()
117+ return dbPath
118+ }
119+
120+ // Lookup 查询 IP 对应的国家/地区代码
52121func Lookup (ip net.IP , record * IPInfo ) (string , error ) {
53- // 确保数据库只初始化一次
54- dbOnce .Do (initDB )
122+ ensureInit ()
123+
124+ dbMu .RLock ()
125+ defer dbMu .RUnlock ()
55126
56127 if db == nil {
57128 return "" , fmt .Errorf ("geoip database not available" )
0 commit comments