Files
kefu_cloud/server/internal/service/visitor_geo.go
T
yml2213 93ef9f2040
CI / Backend (go vet + test) (push) Failing after 9m33s
CI / Frontend (typecheck + lint + build) (push) Successful in 11m57s
fix(widget): 优化访客在线续接与历史地区回填
2026-11-06 18:51:44 +08:00

75 lines
1.9 KiB
Go

package service
import (
"context"
"net"
"strings"
"kefu-cloud/server/internal/geoip"
"kefu-cloud/server/internal/model"
)
const visitorGeoBackfillBatch = 500
var visitorGeoLookup = geoip.Lookup
// BackfillVisitorGeoIP 使用历史会话保存的 IP 补齐旧数据中的国家/省市信息。
func BackfillVisitorGeoIP(ctx context.Context) (int, error) {
var lastID uint
updated := 0
for {
select {
case <-ctx.Done():
return updated, ctx.Err()
default:
}
var sessions []model.Session
if err := model.DB.
Select("id", "visitor_ip").
Where("id > ? AND visitor_ip <> ''", lastID).
Where("visitor_country = '' AND (visitor_region = '' OR visitor_region = ?)", "未知").
Order("id asc").Limit(visitorGeoBackfillBatch).Find(&sessions).Error; err != nil {
return updated, err
}
if len(sessions) == 0 {
return updated, nil
}
for _, session := range sessions {
lastID = session.ID
location, ok := visitorGeoLookup(session.VisitorIP)
if !ok {
if isPrivateIP(session.VisitorIP) {
if err := model.DB.Model(&model.Session{}).Where("id = ?", session.ID).
Update("visitor_region", "内网").Error; err != nil {
return updated, err
}
updated++
}
continue
}
if strings.TrimSpace(location.Region) == "" {
continue
}
if err := model.DB.Model(&model.Session{}).Where("id = ?", session.ID).Updates(map[string]interface{}{
"visitor_region": location.Region,
"visitor_country": location.Country,
"visitor_province": location.Province,
"visitor_city": location.City,
}).Error; err != nil {
return updated, err
}
updated++
}
if len(sessions) < visitorGeoBackfillBatch {
return updated, nil
}
}
}
func isPrivateIP(raw string) bool {
ip := net.ParseIP(strings.TrimSpace(raw))
return ip != nil && (ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast())
}