geolite.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package geolite
  2. import (
  3. "fmt"
  4. "net"
  5. "sync"
  6. "github.com/oschwald/geoip2-golang"
  7. "github.com/uozi-tech/cosy/geoip"
  8. )
  9. type IPLocation struct {
  10. RegionCode string `json:"region_code"`
  11. Province string `json:"province"`
  12. City string `json:"city"`
  13. }
  14. type Service struct {
  15. cityDB *geoip2.Reader
  16. }
  17. var (
  18. instance *Service
  19. once sync.Once
  20. initErr error
  21. )
  22. func GetService() (*Service, error) {
  23. once.Do(func() {
  24. instance = &Service{}
  25. initErr = instance.init()
  26. })
  27. return instance, initErr
  28. }
  29. func (s *Service) init() error {
  30. // Load database from file (memory-mapped for efficiency)
  31. dbPath := GetDBPath()
  32. if !DBExists() {
  33. return fmt.Errorf("GeoLite2 database not found at %s. Please download it first", dbPath)
  34. }
  35. if err := s.loadFromFile(dbPath); err != nil {
  36. return fmt.Errorf("failed to load GeoLite2 database: %v", err)
  37. }
  38. return nil
  39. }
  40. func (s *Service) loadFromFile(path string) error {
  41. // Open database file with memory mapping (more efficient than loading into memory)
  42. cityDB, err := geoip2.Open(path)
  43. if err != nil {
  44. return fmt.Errorf("failed to open GeoLite2 database: %v", err)
  45. }
  46. s.cityDB = cityDB
  47. return nil
  48. }
  49. func (s *Service) Search(ipStr string) (*IPLocation, error) {
  50. if s.cityDB == nil {
  51. return nil, fmt.Errorf("no databases loaded")
  52. }
  53. ip := net.ParseIP(ipStr)
  54. if ip == nil {
  55. return nil, fmt.Errorf("invalid IP address: %s", ipStr)
  56. }
  57. loc := &IPLocation{}
  58. // Use cosy geoip for country code
  59. loc.RegionCode = geoip.ParseIP(ipStr)
  60. // Use city database for detailed information
  61. if record, err := s.cityDB.City(ip); err == nil {
  62. // Override country code from city database if cosy didn't provide it
  63. if loc.RegionCode == "" {
  64. loc.RegionCode = record.Country.IsoCode
  65. }
  66. if len(record.Subdivisions) > 0 {
  67. loc.Province = record.Subdivisions[0].Names["en"]
  68. }
  69. loc.City = record.City.Names["en"]
  70. // Get Chinese names for Chinese regions
  71. if loc.RegionCode == "CN" || loc.RegionCode == "HK" ||
  72. loc.RegionCode == "MO" || loc.RegionCode == "TW" {
  73. if len(record.Subdivisions) > 0 {
  74. if cnRegion := record.Subdivisions[0].Names["zh-CN"]; cnRegion != "" {
  75. loc.Province = cnRegion
  76. }
  77. } else {
  78. // If it's a Chinese IP but has no province, mark it as "其它"
  79. loc.Province = "其它"
  80. }
  81. if cnCity := record.City.Names["zh-CN"]; cnCity != "" {
  82. loc.City = cnCity
  83. }
  84. loc.RegionCode = "CN"
  85. }
  86. return loc, nil
  87. }
  88. // If city database lookup fails, return minimal info with country code
  89. if loc.RegionCode != "" {
  90. return loc, nil
  91. }
  92. return nil, fmt.Errorf("no location data found for IP: %s", ipStr)
  93. }
  94. func (s *Service) SearchWithISO(ipStr string) (*IPLocation, error) {
  95. // This method specifically returns English names and ISO codes
  96. if s.cityDB == nil {
  97. return nil, fmt.Errorf("no databases loaded")
  98. }
  99. ip := net.ParseIP(ipStr)
  100. if ip == nil {
  101. return nil, fmt.Errorf("invalid IP address: %s", ipStr)
  102. }
  103. loc := &IPLocation{}
  104. // Use cosy geoip for country code
  105. loc.RegionCode = geoip.ParseIP(ipStr)
  106. // Use city database for detailed information
  107. if record, err := s.cityDB.City(ip); err == nil {
  108. // Override country code from city database if cosy didn't provide it
  109. if loc.RegionCode == "" {
  110. loc.RegionCode = record.Country.IsoCode
  111. }
  112. if len(record.Subdivisions) > 0 {
  113. loc.RegionCode = record.Subdivisions[0].Names["en"]
  114. }
  115. loc.RegionCode = record.City.Names["en"]
  116. return loc, nil
  117. }
  118. // If city database lookup fails, return minimal info with country code
  119. if loc.RegionCode != "" {
  120. return loc, nil
  121. }
  122. return nil, fmt.Errorf("no location data found for IP: %s", ipStr)
  123. }
  124. func (s *Service) Close() {
  125. if s.cityDB != nil {
  126. s.cityDB.Close()
  127. s.cityDB = nil
  128. }
  129. }
  130. func IsChineseIP(loc *IPLocation) bool {
  131. return loc != nil && (loc.RegionCode == "CN" ||
  132. loc.RegionCode == "HK" ||
  133. loc.RegionCode == "MO" ||
  134. loc.RegionCode == "TW")
  135. }
  136. func IsChineseRegion(regionCode string) bool {
  137. chineseRegionCodes := []string{"CN", "HK", "MO", "TW"}
  138. for _, region := range chineseRegionCodes {
  139. if regionCode == region {
  140. return true
  141. }
  142. }
  143. return false
  144. }