feat(widget): 优化访客在线轨迹、GeoIP与多站点渠道
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"kefu-cloud/server/internal/geoip"
|
||||
"kefu-cloud/server/internal/service"
|
||||
)
|
||||
|
||||
// resolveVisitorIP 优先信任反代头,回落到 Gin ClientIP。
|
||||
@@ -46,9 +48,27 @@ func resolveVisitorIP(c *gin.Context) string {
|
||||
return ip
|
||||
}
|
||||
|
||||
// resolveVisitorRegion 基于 IP 与常见 CDN 国家头做粗粒度地区,无需第三方库。
|
||||
// 私网/本机显示「内网」;有国家头则映射中文名;否则「未知」。
|
||||
func resolveVisitorRegion(c *gin.Context, ip string) string {
|
||||
type visitorLocation struct {
|
||||
Country string
|
||||
Province string
|
||||
City string
|
||||
Region string
|
||||
}
|
||||
|
||||
// resolveVisitorLocation 优先使用离线 GeoIP,未配置时降级到 CDN 国家头。
|
||||
func resolveVisitorLocation(c *gin.Context, ip string) visitorLocation {
|
||||
parsed := net.ParseIP(ip)
|
||||
if parsed != nil &&
|
||||
(parsed.IsLoopback() || parsed.IsPrivate() || parsed.IsLinkLocalUnicast() || parsed.IsLinkLocalMulticast()) {
|
||||
return visitorLocation{Region: "内网"}
|
||||
}
|
||||
if location, ok := geoip.Lookup(ip); ok {
|
||||
return visitorLocation{
|
||||
Country: location.Country, Province: location.Province,
|
||||
City: location.City, Region: location.Region,
|
||||
}
|
||||
}
|
||||
|
||||
// Cloudflare / 部分网关国家码
|
||||
country := strings.ToUpper(strings.TrimSpace(c.GetHeader("CF-IPCountry")))
|
||||
if country == "" {
|
||||
@@ -59,20 +79,15 @@ func resolveVisitorRegion(c *gin.Context, ip string) string {
|
||||
}
|
||||
if country != "" && country != "XX" && country != "T1" {
|
||||
if name, ok := countryNameCN(country); ok {
|
||||
return name
|
||||
return visitorLocation{Country: name, Region: name}
|
||||
}
|
||||
return country
|
||||
return visitorLocation{Country: country, Region: country}
|
||||
}
|
||||
return visitorLocation{Region: "未知"}
|
||||
}
|
||||
|
||||
parsed := net.ParseIP(ip)
|
||||
if parsed == nil {
|
||||
return "未知"
|
||||
}
|
||||
if parsed.IsLoopback() || parsed.IsPrivate() || parsed.IsLinkLocalUnicast() || parsed.IsLinkLocalMulticast() {
|
||||
return "内网"
|
||||
}
|
||||
// 未接入 GeoIP 库时,公网 IP 地区暂标未知(可后续接入离线库)
|
||||
return "未知"
|
||||
func resolveVisitorRegion(c *gin.Context, ip string) string {
|
||||
return resolveVisitorLocation(c, ip).Region
|
||||
}
|
||||
|
||||
func countryNameCN(code string) (string, bool) {
|
||||
@@ -123,13 +138,24 @@ func summarizeUserAgent(ua string) string {
|
||||
return browser + " / " + osName
|
||||
}
|
||||
|
||||
func captureVisitorMeta(c *gin.Context) (ip, region, ua, device string) {
|
||||
ip = resolveVisitorIP(c)
|
||||
region = resolveVisitorRegion(c, ip)
|
||||
ua = c.Request.UserAgent()
|
||||
func captureVisitorMeta(c *gin.Context) service.VisitorMeta {
|
||||
ip := resolveVisitorIP(c)
|
||||
location := resolveVisitorLocation(c, ip)
|
||||
clip := func(value string, max int) string {
|
||||
value = strings.TrimSpace(value)
|
||||
runes := []rune(value)
|
||||
if len(runes) > max {
|
||||
return string(runes[:max])
|
||||
}
|
||||
return value
|
||||
}
|
||||
ua := c.Request.UserAgent()
|
||||
if len(ua) > 500 {
|
||||
ua = ua[:500]
|
||||
}
|
||||
device = summarizeUserAgent(ua)
|
||||
return
|
||||
return service.VisitorMeta{
|
||||
IP: ip, Region: clip(location.Region, 100), Country: clip(location.Country, 100),
|
||||
Province: clip(location.Province, 100), City: clip(location.City, 100),
|
||||
UserAgent: ua, DeviceLabel: summarizeUserAgent(ua),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user