feat(log): 多源 clientIP 降级与去重 ip_chain
按 EO→XFF非回环→X-Real→RemoteAddr 选取 ip;访问日志附带去重 ip_chain;登录限流改用 clientIP。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+87
-13
@@ -108,20 +108,24 @@ func (that *Application) initiateGracefulShutdown(drain, shutdown time.Duration)
|
||||
})
|
||||
}
|
||||
|
||||
// clientIP 取客户端真实 IP:X-Real-IP → X-Forwarded-For 第一段 → RemoteAddr
|
||||
func clientIP(req *http.Request) string {
|
||||
if req == nil {
|
||||
// isLoopbackIP 判断是否为回环地址(空串视为不可用)
|
||||
func isLoopbackIP(ip string) bool {
|
||||
ip = strings.TrimSpace(ip)
|
||||
if ip == "" {
|
||||
return true
|
||||
}
|
||||
parsed := net.ParseIP(ip)
|
||||
if parsed == nil {
|
||||
return false
|
||||
}
|
||||
return parsed.IsLoopback()
|
||||
}
|
||||
|
||||
// remoteHost 从 RemoteAddr 取出 host(去掉端口)
|
||||
func remoteHost(req *http.Request) string {
|
||||
if req == nil || req.RemoteAddr == "" {
|
||||
return ""
|
||||
}
|
||||
if ip := strings.TrimSpace(req.Header.Get("X-Real-IP")); ip != "" {
|
||||
return ip
|
||||
}
|
||||
if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
if i := strings.Index(xff, ","); i >= 0 {
|
||||
return strings.TrimSpace(xff[:i])
|
||||
}
|
||||
return strings.TrimSpace(xff)
|
||||
}
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err == nil && host != "" {
|
||||
return host
|
||||
@@ -129,6 +133,72 @@ func clientIP(req *http.Request) string {
|
||||
return req.RemoteAddr
|
||||
}
|
||||
|
||||
// resolveClientIP 多源识别客户端 IP。
|
||||
// best:EO-Connecting-IP → XFF 第一个非回环 → 非回环 X-Real-IP → RemoteAddr
|
||||
// (XFF 先于 X-Real:反代误把 CDN 节点写进 X-Real-IP 时仍能落到客户端)
|
||||
// chain:各源头 IP 按首次出现顺序逗号拼接,同一 IP 只保留一次
|
||||
func resolveClientIP(req *http.Request) (best string, chain string) {
|
||||
if req == nil {
|
||||
return "", ""
|
||||
}
|
||||
eo := strings.TrimSpace(req.Header.Get("EO-Connecting-IP"))
|
||||
xri := strings.TrimSpace(req.Header.Get("X-Real-IP"))
|
||||
xffRaw := req.Header.Get("X-Forwarded-For")
|
||||
ra := remoteHost(req)
|
||||
|
||||
var xffParts []string
|
||||
if xffRaw != "" {
|
||||
for _, p := range strings.Split(xffRaw, ",") {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
xffParts = append(xffParts, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, 4)
|
||||
var ordered []string
|
||||
add := func(ip string) {
|
||||
ip = strings.TrimSpace(ip)
|
||||
if ip == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[ip]; ok {
|
||||
return
|
||||
}
|
||||
seen[ip] = struct{}{}
|
||||
ordered = append(ordered, ip)
|
||||
}
|
||||
add(eo)
|
||||
add(xri)
|
||||
for _, p := range xffParts {
|
||||
add(p)
|
||||
}
|
||||
add(ra)
|
||||
chain = strings.Join(ordered, ",")
|
||||
|
||||
if eo != "" && !isLoopbackIP(eo) {
|
||||
return eo, chain
|
||||
}
|
||||
for _, p := range xffParts {
|
||||
if !isLoopbackIP(p) {
|
||||
return p, chain
|
||||
}
|
||||
}
|
||||
if xri != "" && !isLoopbackIP(xri) {
|
||||
return xri, chain
|
||||
}
|
||||
if ra != "" {
|
||||
return ra, chain
|
||||
}
|
||||
return "", chain
|
||||
}
|
||||
|
||||
// clientIP 取客户端真实 IP(见 resolveClientIP)
|
||||
func clientIP(req *http.Request) string {
|
||||
ip, _ := resolveClientIP(req)
|
||||
return ip
|
||||
}
|
||||
|
||||
// shortID 生成 n 字节随机 hex(n=6 → 12 位)
|
||||
func shortID(n int) string {
|
||||
b := make([]byte, n)
|
||||
@@ -504,13 +574,17 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
defer func() {
|
||||
//是否展示日志
|
||||
if that.WebConnectLog != nil {
|
||||
ip, ipChain := resolveClientIP(req)
|
||||
evt := that.WebConnectLog.Info().
|
||||
Str("ip", clientIP(req)).
|
||||
Str("ip", ip).
|
||||
Str("method", context.Req.Method).
|
||||
Str("sid", sid).
|
||||
Str("request_id", requestId).
|
||||
Float64("cost_ms", ObjToFloat64(time.Now().UnixNano()-nowUnixTime.UnixNano())/1000000.00).
|
||||
Float64("size_kb", ObjToFloat64(context.DataSize)/1000.00)
|
||||
if ipChain != "" && ipChain != ip {
|
||||
evt = evt.Str("ip_chain", ipChain)
|
||||
}
|
||||
if country := req.Header.Get("EO-Client-IPCountry"); country != "" {
|
||||
evt = evt.Str("ip_country", country)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user