Files
app-api/pkg/encode/client_ip.go
T

84 lines
1.9 KiB
Go
Raw Normal View History

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
}