package middleware import ( "context" "encoding/json" "net" "regexp" "strings" "unicode/utf8" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "easygoadmin/utility/ip2region" ) // ============================== 中间件通用工具 ============================== // cfgBool 读取布尔配置 // 配置缺失时返回默认值 func cfgBool(ctx context.Context, key string, def bool) bool { v := g.Cfg().MustGet(ctx, key) if v.IsNil() { return def } return v.Bool() } // cfgInt 读取整数配置 // 配置缺失时返回默认值(显式配置 0 表示业务含义上的关闭,原样返回) func cfgInt(ctx context.Context, key string, def int) int { v := g.Cfg().MustGet(ctx, key) if v.IsNil() { return def } return v.Int() } // gjsonEncode JSON 序列化 // 序列化失败时返回空串 func gjsonEncode(v any) string { b, err := json.Marshal(v) if err != nil { return "" } return string(b) } // trunc 按字节截断且保证 UTF-8 完整 // // 截断点回退到完整的字符边界,避免 MySQL 严格模式拒绝非法字节 func trunc(s string, n int) string { if len(s) <= n { return s } for n > 0 && !utf8.RuneStart(s[n]) { n-- } return s[:n] + "..." } // maskJSONField 脱敏 JSON 文本中的指定字段 // 将该字段的值替换为 *** var maskReCache = map[string]*regexp.Regexp{} func maskJSONField(s, field string) string { re, ok := maskReCache[field] if !ok { re = regexp.MustCompile(`("` + regexp.QuoteMeta(field) + `"\s*:\s*)"?[^",}]*"?`) maskReCache[field] = re } return re.ReplaceAllString(s, `${1}***`) } // realClientIP 获取客户端真实IP // // 读取配置 api.proxyIps,直连来源为可信代理时, // 依次取 X-Forwarded-For 首段或 X-Real-IP func realClientIP(r *ghttp.Request) string { remote := r.GetRemoteIp() if !isTrustedProxy(r.Context(), remote) { return remote } if xff := r.Header.Get("X-Forwarded-For"); xff != "" { if ip := strings.TrimSpace(strings.Split(xff, ",")[0]); ip != "" { return ip } } if real := strings.TrimSpace(r.Header.Get("X-Real-IP")); real != "" { return real } return remote } // isTrustedProxy 判断来源是否为可信代理 // 支持精确 IP 与 CIDR 两种配置形式 func isTrustedProxy(ctx context.Context, ip string) bool { v := g.Cfg().MustGet(ctx, "api.proxyIps") if v.IsNil() || v.String() == "" { return false } parsed := net.ParseIP(ip) if parsed == nil { return false } for _, item := range strings.Split(v.String(), ",") { item = strings.TrimSpace(item) if item == "" { continue } if item == ip { return true } if _, cidr, err := net.ParseCIDR(item); err == nil && cidr.Contains(parsed) { return true } } return false } // requestLocation 按请求IP解析归属地 // // 基于 ip2region 实现,解析失败时返回 "-" func requestLocation(ctx context.Context, ip string) string { _ = ctx return ip2region.Location(ip) }