package encode import ( "net" "strings" "github.com/gin-gonic/gin" ) // ClientPublicIP 按优先级从多个来源获取客户端公网 IP // 优先级:CF-Connecting-IP > X-Real-IP > X-Forwarded-For 第一个 > gin.ClientIP() 兜底 func ClientPublicIP(c *gin.Context) string { // 1. CloudFlare 真实 IP if cfIP := strings.TrimSpace(c.GetHeader("CF-Connecting-IP")); cfIP != "" { if ip := parseIP(cfIP); ip != "" { return ip } } // 2. Nginx 等代理设置的真实 IP if realIP := strings.TrimSpace(c.GetHeader("X-Real-IP")); realIP != "" { if ip := parseIP(realIP); ip != "" { return ip } } // 3. X-Forwarded-For:取第一个(最原始的客户端 IP) if xff := strings.TrimSpace(c.GetHeader("X-Forwarded-For")); xff != "" { // X-Forwarded-For: client, proxy1, proxy2 parts := strings.SplitN(xff, ",", 2) if ip := parseIP(strings.TrimSpace(parts[0])); ip != "" { return ip } } // 4. Gin 内置兜底(会按 TrustedProxies 配置解析) return parseIP(c.ClientIP()) } // parseIP 从 host:port 或纯 IP 字符串中提取合法的 IP 地址 func parseIP(addr string) string { // 尝试解析为 host:port if host, _, err := net.SplitHostPort(addr); err == nil { addr = host } ip := net.ParseIP(addr) if ip == nil { return "" } // 过滤内网地址,只返回公网 IP if !isPrivateIP(ip) { return addr } // 内网地址也返回(开发环境或代理内网转发场景) return addr } // isPrivateIP 判断是否为内网/保留 IP func isPrivateIP(ip net.IP) bool { privateRanges := []struct { cidr string }{ {"10.0.0.0/8"}, {"172.16.0.0/12"}, {"192.168.0.0/16"}, {"127.0.0.0/8"}, {"169.254.0.0/16"}, {"::1/128"}, {"fc00::/7"}, {"fe80::/10"}, } for _, r := range privateRanges { _, network, _ := net.ParseCIDR(r.cidr) if network.Contains(ip) { return true } } return false }