- Updated the logic in the geo enrichment process to fill in geographic coordinates even when they are 0.0, enhancing map visualization. - Modified the GeoIP lookup function to always return latitude and longitude, improving the accuracy of location data for the UI. - Improved comments for clarity regarding the importance of geographic data in the mapping context.
125 lines
2.9 KiB
Go
125 lines
2.9 KiB
Go
package geoip
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/oschwald/geoip2-golang"
|
|
)
|
|
|
|
// Service holds optional GeoLite2 City and/or ASN MMDB readers.
|
|
// Country-only DB не нужен: страна уже есть в City.
|
|
type Service struct {
|
|
city *geoip2.Reader
|
|
asn *geoip2.Reader
|
|
}
|
|
|
|
// Open opens a single MMDB (City or ASN) — для тестов и простых случаев.
|
|
func Open(path string) (*Service, error) {
|
|
r, err := geoip2.Open(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("geoip open %q: %w", path, err)
|
|
}
|
|
return &Service{city: r}, nil
|
|
}
|
|
|
|
// OpenDatabases opens city and/or asn paths; at least one must be non-empty.
|
|
func OpenDatabases(cityPath, asnPath string) (*Service, error) {
|
|
var s Service
|
|
if cityPath != "" {
|
|
r, err := geoip2.Open(cityPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("geoip city open %q: %w", cityPath, err)
|
|
}
|
|
s.city = r
|
|
}
|
|
if asnPath != "" {
|
|
r, err := geoip2.Open(asnPath)
|
|
if err != nil {
|
|
if s.city != nil {
|
|
_ = s.city.Close()
|
|
}
|
|
return nil, fmt.Errorf("geoip asn open %q: %w", asnPath, err)
|
|
}
|
|
s.asn = r
|
|
}
|
|
if s.city == nil && s.asn == nil {
|
|
return nil, fmt.Errorf("geoip: no database paths")
|
|
}
|
|
return &s, nil
|
|
}
|
|
|
|
// Close releases database handles.
|
|
func (s *Service) Close() error {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
var first error
|
|
if s.city != nil {
|
|
if err := s.city.Close(); err != nil {
|
|
first = err
|
|
}
|
|
s.city = nil
|
|
}
|
|
if s.asn != nil {
|
|
if err := s.asn.Close(); err != nil && first == nil {
|
|
first = err
|
|
}
|
|
s.asn = nil
|
|
}
|
|
return first
|
|
}
|
|
|
|
// Result combines City + ASN fields when available.
|
|
type Result struct {
|
|
CountryCode string
|
|
CountryName string
|
|
CityName string
|
|
Latitude float64
|
|
Longitude float64
|
|
ASN uint64
|
|
ASOrg string
|
|
}
|
|
|
|
// Lookup fills geographic and/or ASN data for an IP string (IPv4/IPv6).
|
|
func (s *Service) Lookup(ipStr string) (Result, bool) {
|
|
var out Result
|
|
if s == nil {
|
|
return out, false
|
|
}
|
|
ip := net.ParseIP(ipStr)
|
|
if ip == nil {
|
|
return out, false
|
|
}
|
|
ok := false
|
|
if s.city != nil {
|
|
if rec, err := s.city.City(ip); err == nil {
|
|
out.CountryCode = rec.Country.IsoCode
|
|
if rec.Country.Names != nil {
|
|
out.CountryName = rec.Country.Names["en"]
|
|
}
|
|
if rec.City.Names != nil {
|
|
out.CityName = rec.City.Names["en"]
|
|
}
|
|
// Для карты важны координаты. Даже если MaxMind вернул (0,0),
|
|
// лучше прокидывать их дальше, чем скрывать все точки.
|
|
out.Latitude = rec.Location.Latitude
|
|
out.Longitude = rec.Location.Longitude
|
|
|
|
if out.CountryCode != "" || out.CountryName != "" || out.CityName != "" {
|
|
ok = true
|
|
}
|
|
}
|
|
}
|
|
if s.asn != nil {
|
|
if rec, err := s.asn.ASN(ip); err == nil {
|
|
out.ASN = uint64(rec.AutonomousSystemNumber)
|
|
out.ASOrg = rec.AutonomousSystemOrganization
|
|
if out.ASN != 0 || out.ASOrg != "" {
|
|
ok = true
|
|
}
|
|
}
|
|
}
|
|
return out, ok
|
|
}
|