135 lines
3.3 KiB
Go
135 lines
3.3 KiB
Go
|
|
package ipgeo
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"net"
|
|||
|
|
"path/filepath"
|
|||
|
|
"strings"
|
|||
|
|
"hyapi-server/internal/domains/security/entities"
|
|||
|
|
|
|||
|
|
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
|||
|
|
"go.uber.org/zap"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Location IP解析后的地理信息
|
|||
|
|
type Location struct {
|
|||
|
|
Country string
|
|||
|
|
Province string
|
|||
|
|
City string
|
|||
|
|
ISP string
|
|||
|
|
Region string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Locator IP地理定位器
|
|||
|
|
type Locator struct {
|
|||
|
|
logger *zap.Logger
|
|||
|
|
searcher *xdb.Searcher
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewLocator 创建定位器,优先读取 resources/ipgeo/ip2region.xdb
|
|||
|
|
func NewLocator(logger *zap.Logger) *Locator {
|
|||
|
|
locator := &Locator{logger: logger}
|
|||
|
|
dbPath := filepath.Join("resources", "ipgeo", "ip2region.xdb")
|
|||
|
|
|
|||
|
|
cBuff, err := xdb.LoadContentFromFile(dbPath)
|
|||
|
|
if err != nil {
|
|||
|
|
logger.Warn("加载ip2region库失败,将使用降级定位", zap.String("db_path", dbPath), zap.Error(err))
|
|||
|
|
return locator
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
header, err := xdb.LoadHeaderFromBuff(cBuff)
|
|||
|
|
if err != nil {
|
|||
|
|
logger.Warn("读取ip2region头信息失败,将使用降级定位", zap.Error(err))
|
|||
|
|
return locator
|
|||
|
|
}
|
|||
|
|
version, err := xdb.VersionFromHeader(header)
|
|||
|
|
if err != nil {
|
|||
|
|
logger.Warn("解析ip2region版本失败,将使用降级定位", zap.Error(err))
|
|||
|
|
return locator
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
searcher, err := xdb.NewWithBuffer(version, cBuff)
|
|||
|
|
if err != nil {
|
|||
|
|
logger.Warn("初始化ip2region搜索器失败,将使用降级定位", zap.Error(err))
|
|||
|
|
return locator
|
|||
|
|
}
|
|||
|
|
locator.searcher = searcher
|
|||
|
|
|
|||
|
|
logger.Info("ip2region定位器初始化成功", zap.String("db_path", dbPath))
|
|||
|
|
return locator
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// LookupByIP 根据IP定位,失败返回 false
|
|||
|
|
func (l *Locator) LookupByIP(ip string) (Location, bool) {
|
|||
|
|
if ip == "" || isPrivateOrLocalIP(ip) || l.searcher == nil {
|
|||
|
|
return Location{}, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
region, err := l.searcher.SearchByStr(ip)
|
|||
|
|
if err != nil {
|
|||
|
|
l.logger.Debug("ip2region查询失败", zap.String("ip", ip), zap.Error(err))
|
|||
|
|
return Location{}, false
|
|||
|
|
}
|
|||
|
|
loc := parseRegion(region)
|
|||
|
|
if loc.Region == "" {
|
|||
|
|
return Location{}, false
|
|||
|
|
}
|
|||
|
|
return loc, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ToGeoPoint 将记录转换为地球飞线起点
|
|||
|
|
func (l *Locator) ToGeoPoint(record entities.SuspiciousIPRecord) (fromName string, lng float64, lat float64) {
|
|||
|
|
// 默认降级坐标:北京
|
|||
|
|
const defaultLng = 116.4074
|
|||
|
|
const defaultLat = 39.9042
|
|||
|
|
|
|||
|
|
loc, ok := l.LookupByIP(record.IP)
|
|||
|
|
if !ok {
|
|||
|
|
return record.IP, defaultLng, defaultLat
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cityName := strings.TrimSpace(loc.City)
|
|||
|
|
if cityName == "" || cityName == "0" {
|
|||
|
|
cityName = strings.TrimSpace(loc.Province)
|
|||
|
|
}
|
|||
|
|
if cityName == "" || cityName == "0" {
|
|||
|
|
return record.IP, defaultLng, defaultLat
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
coord, exists := CityCoordinates[cityName]
|
|||
|
|
if !exists {
|
|||
|
|
// 降级:未命中城市映射,回默认坐标
|
|||
|
|
return cityName, defaultLng, defaultLat
|
|||
|
|
}
|
|||
|
|
return cityName, coord.Lng, coord.Lat
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func parseRegion(region string) Location {
|
|||
|
|
parts := strings.Split(region, "|")
|
|||
|
|
for len(parts) < 5 {
|
|||
|
|
parts = append(parts, "")
|
|||
|
|
}
|
|||
|
|
return Location{
|
|||
|
|
Country: normalizeField(parts[0]),
|
|||
|
|
Region: normalizeField(parts[1]),
|
|||
|
|
Province: normalizeField(parts[2]),
|
|||
|
|
City: normalizeField(parts[3]),
|
|||
|
|
ISP: normalizeField(parts[4]),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func normalizeField(s string) string {
|
|||
|
|
s = strings.TrimSpace(s)
|
|||
|
|
if s == "0" {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func isPrivateOrLocalIP(ip string) bool {
|
|||
|
|
parsed := net.ParseIP(ip)
|
|||
|
|
if parsed == nil {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
return parsed.IsLoopback() || parsed.IsPrivate() || parsed.IsUnspecified() || parsed.IsLinkLocalUnicast()
|
|||
|
|
}
|