resolver.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package geoip
  2. import (
  3. "log"
  4. "net"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "sync"
  9. "github.com/lionsoul2014/ip2region/binding/golang/service"
  10. )
  11. // Resolver 根据 IP 解析大致地理位置(离线)。
  12. type Resolver interface {
  13. Lookup(ip string) string
  14. Close()
  15. }
  16. // NoopResolver 未配置 xdb 时使用。
  17. type NoopResolver struct{}
  18. func (NoopResolver) Lookup(string) string { return "" }
  19. func (NoopResolver) Close() {}
  20. type ip2regionResolver struct {
  21. svc *service.Ip2Region
  22. }
  23. func (r *ip2regionResolver) Lookup(ip string) string {
  24. ip = strings.TrimSpace(ip)
  25. if ip == "" {
  26. return ""
  27. }
  28. if host, _, err := net.SplitHostPort(ip); err == nil {
  29. ip = host
  30. }
  31. parsed := net.ParseIP(ip)
  32. if parsed == nil {
  33. return ""
  34. }
  35. if parsed.IsLoopback() || parsed.IsPrivate() || parsed.IsLinkLocalUnicast() {
  36. return "内网"
  37. }
  38. region, err := r.svc.Search(ip)
  39. if err != nil || strings.TrimSpace(region) == "" {
  40. return ""
  41. }
  42. return FormatRegion(region)
  43. }
  44. func (r *ip2regionResolver) Close() {
  45. if r.svc != nil {
  46. r.svc.Close()
  47. }
  48. }
  49. var (
  50. globalResolver Resolver = NoopResolver{}
  51. globalMu sync.Mutex
  52. )
  53. // Get 返回全局 Resolver(未初始化时为 Noop)。
  54. func Get() Resolver {
  55. globalMu.Lock()
  56. defer globalMu.Unlock()
  57. return globalResolver
  58. }
  59. // InitFromEnv 按环境变量与默认路径加载 ip2region;失败则降级为 Noop,不阻塞启动。
  60. // IP2REGION_DISABLED=true 时跳过;IP2REGION_V4_XDB 指定 v4 库路径;IP2REGION_V6_XDB 可选 IPv6。
  61. func InitFromEnv() {
  62. globalMu.Lock()
  63. defer globalMu.Unlock()
  64. if old, ok := globalResolver.(*ip2regionResolver); ok {
  65. old.Close()
  66. }
  67. globalResolver = NoopResolver{}
  68. if strings.EqualFold(strings.TrimSpace(os.Getenv("IP2REGION_DISABLED")), "true") {
  69. log.Println("ℹ️ IP2REGION_DISABLED=true,跳过访客 IP 地理位置解析")
  70. return
  71. }
  72. v4Path := strings.TrimSpace(os.Getenv("IP2REGION_V4_XDB"))
  73. v6Path := strings.TrimSpace(os.Getenv("IP2REGION_V6_XDB"))
  74. if v4Path == "" {
  75. v4Path = findDefaultXDB("ip2region_v4.xdb")
  76. }
  77. if v6Path == "" {
  78. v6Path = findDefaultXDB("ip2region_v6.xdb")
  79. }
  80. if v4Path == "" && v6Path == "" {
  81. log.Println("⚠️ 未找到 ip2region xdb,访客「位置」将保持为空;请放置 backend/data/ip2region_v4.xdb(及可选 v6)或设置 IP2REGION_V4_XDB / IP2REGION_V6_XDB")
  82. return
  83. }
  84. svc, err := service.NewIp2RegionWithPath(v4Path, v6Path)
  85. if err != nil {
  86. log.Printf("⚠️ 初始化 ip2region 失败: %v", err)
  87. return
  88. }
  89. globalResolver = &ip2regionResolver{svc: svc}
  90. if v4Path != "" {
  91. log.Printf("✅ ip2region 已加载 (v4: %s)", v4Path)
  92. }
  93. if v6Path != "" {
  94. log.Printf("✅ ip2region 已加载 (v6: %s)", v6Path)
  95. } else {
  96. log.Println("ℹ️ 未加载 ip2region IPv6 库;访客若为 IPv6 地址将无法解析位置,可下载 ip2region_v6.xdb")
  97. }
  98. }
  99. // LookupLocation 解析 IP;已有 location 非空时原样返回。
  100. func LookupLocation(ip, currentLocation string) string {
  101. if strings.TrimSpace(currentLocation) != "" {
  102. return currentLocation
  103. }
  104. return Get().Lookup(ip)
  105. }
  106. func findDefaultXDB(filename string) string {
  107. candidates := []string{
  108. "data/" + filename,
  109. "backend/data/" + filename,
  110. "/app/data/" + filename,
  111. }
  112. if wd, err := os.Getwd(); err == nil {
  113. candidates = append(candidates,
  114. filepath.Join(wd, "data", filename),
  115. filepath.Join(wd, "..", "backend", "data", filename),
  116. )
  117. }
  118. for _, p := range candidates {
  119. if st, err := os.Stat(p); err == nil && !st.IsDir() {
  120. abs, err := filepath.Abs(p)
  121. if err == nil {
  122. return abs
  123. }
  124. return p
  125. }
  126. }
  127. return ""
  128. }