Files
Denozordec f710847320
Publish telemt-api gateway Docker image / test (push) Successful in 25s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m1s
Refactor GeoIP data handling to ensure consistent coordinate propagation
- 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.
2026-03-30 15:31:26 +07:00

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
}