84 lines
1.9 KiB
Go
84 lines
1.9 KiB
Go
|
|
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
|
||
|
|
}
|