123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- package geolite
- import (
- "bytes"
- _ "embed"
- "fmt"
- "io"
- "net"
- "sync"
- "github.com/oschwald/geoip2-golang"
- "github.com/ulikunitz/xz"
- "github.com/uozi-tech/cosy/geoip"
- )
- //go:embed GeoLite2-City.mmdb.xz
- var embeddedCityDB []byte
- type IPLocation struct {
- RegionCode string `json:"region_code"`
- Province string `json:"province"`
- City string `json:"city"`
- }
- type Service struct {
- cityDB *geoip2.Reader
- }
- var (
- instance *Service
- once sync.Once
- initErr error
- )
- func GetService() (*Service, error) {
- once.Do(func() {
- instance = &Service{}
- initErr = instance.init()
- })
- return instance, initErr
- }
- func (s *Service) init() error {
- // Load embedded compressed database
- if len(embeddedCityDB) > 0 {
- if err := s.loadEmbeddedCityDB(); err != nil {
- return fmt.Errorf("failed to load embedded city database: %v", err)
- }
- return nil
- }
- return fmt.Errorf("no embedded GeoLite2 City database available")
- }
- func (s *Service) loadEmbeddedCityDB() error {
- // Decompress the embedded database
- xzReader, err := xz.NewReader(bytes.NewReader(embeddedCityDB))
- if err != nil {
- return fmt.Errorf("failed to create xz reader: %v", err)
- }
- decompressedData, err := io.ReadAll(xzReader)
- if err != nil {
- return fmt.Errorf("failed to decompress database: %v", err)
- }
- // Create geoip2 reader from decompressed data
- cityDB, err := geoip2.FromBytes(decompressedData)
- if err != nil {
- return fmt.Errorf("failed to create geoip2 reader: %v", err)
- }
- s.cityDB = cityDB
- return nil
- }
- func (s *Service) Search(ipStr string) (*IPLocation, error) {
- if s.cityDB == nil {
- return nil, fmt.Errorf("no databases loaded")
- }
- ip := net.ParseIP(ipStr)
- if ip == nil {
- return nil, fmt.Errorf("invalid IP address: %s", ipStr)
- }
- loc := &IPLocation{}
- // Use cosy geoip for country code
- loc.RegionCode = geoip.ParseIP(ipStr)
- // Use city database for detailed information
- if record, err := s.cityDB.City(ip); err == nil {
- // Override country code from city database if cosy didn't provide it
- if loc.RegionCode == "" {
- loc.RegionCode = record.Country.IsoCode
- }
- if len(record.Subdivisions) > 0 {
- loc.Province = record.Subdivisions[0].Names["en"]
- }
- loc.City = record.City.Names["en"]
- // Get Chinese names for Chinese regions
- if loc.RegionCode == "CN" || loc.RegionCode == "HK" ||
- loc.RegionCode == "MO" || loc.RegionCode == "TW" {
- if len(record.Subdivisions) > 0 {
- if cnRegion := record.Subdivisions[0].Names["zh-CN"]; cnRegion != "" {
- loc.Province = cnRegion
- }
- } else {
- // If it's a Chinese IP but has no province, mark it as "其它"
- loc.Province = "其它"
- }
- if cnCity := record.City.Names["zh-CN"]; cnCity != "" {
- loc.City = cnCity
- }
- loc.RegionCode = "CN"
- }
- return loc, nil
- }
- // If city database lookup fails, return minimal info with country code
- if loc.RegionCode != "" {
- return loc, nil
- }
- return nil, fmt.Errorf("no location data found for IP: %s", ipStr)
- }
- func (s *Service) SearchWithISO(ipStr string) (*IPLocation, error) {
- // This method specifically returns English names and ISO codes
- if s.cityDB == nil {
- return nil, fmt.Errorf("no databases loaded")
- }
- ip := net.ParseIP(ipStr)
- if ip == nil {
- return nil, fmt.Errorf("invalid IP address: %s", ipStr)
- }
- loc := &IPLocation{}
- // Use cosy geoip for country code
- loc.RegionCode = geoip.ParseIP(ipStr)
- // Use city database for detailed information
- if record, err := s.cityDB.City(ip); err == nil {
- // Override country code from city database if cosy didn't provide it
- if loc.RegionCode == "" {
- loc.RegionCode = record.Country.IsoCode
- }
- if len(record.Subdivisions) > 0 {
- loc.RegionCode = record.Subdivisions[0].Names["en"]
- }
- loc.RegionCode = record.City.Names["en"]
- return loc, nil
- }
- // If city database lookup fails, return minimal info with country code
- if loc.RegionCode != "" {
- return loc, nil
- }
- return nil, fmt.Errorf("no location data found for IP: %s", ipStr)
- }
- func (s *Service) Close() {
- if s.cityDB != nil {
- s.cityDB.Close()
- s.cityDB = nil
- }
- }
- func IsChineseIP(loc *IPLocation) bool {
- return loc != nil && (loc.RegionCode == "CN" ||
- loc.RegionCode == "HK" ||
- loc.RegionCode == "MO" ||
- loc.RegionCode == "TW")
- }
- func IsChineseRegion(regionCode string) bool {
- chineseRegionCodes := []string{"CN", "HK", "MO", "TW"}
- for _, region := range chineseRegionCodes {
- if regionCode == region {
- return true
- }
- }
- return false
- }
|