package logger import ( "fmt" "net/url" "path/filepath" "strings" "github.com/bytedance/sonic" ) // AccessPolicy 是敏感路由的安全摘要策略。 type AccessPolicy struct { Name string Sensitive bool PresenceOnly bool SafeFields map[string]struct{} ResultFields map[string]struct{} FileFields map[string]struct{} } var ( loginPolicy = AccessPolicy{ Name: "login_token", Sensitive: true, PresenceOnly: true, SafeFields: fieldSet("username", "user_id", "account_id", "success", "result_code"), ResultFields: fieldSet("success", "result_code", "status"), } paymentPolicy = AccessPolicy{ Name: "payment", Sensitive: true, PresenceOnly: true, SafeFields: fieldSet("order_no", "payment_no", "channel", "payment_method", "result_code", "amount", "status"), ResultFields: fieldSet("result_code", "status"), } wecomPolicy = AccessPolicy{ Name: "wecom_callback", Sensitive: true, SafeFields: fieldSet("event_type", "resource_type", "resource_id", "result_code", "status"), } configPolicy = AccessPolicy{ Name: "sensitive_config", Sensitive: true, PresenceOnly: true, SafeFields: fieldSet("config_key", "value_type", "module", "readonly", "sensitive", "configured", "status", "result_code"), ResultFields: fieldSet("readonly", "sensitive", "configured", "status", "result_code"), } filePolicy = AccessPolicy{ Name: "file_export", Sensitive: true, SafeFields: fieldSet("content_type", "file_size", "size", "count", "task_id", "status", "result_code"), FileFields: fieldSet("file_name", "filename", "name"), } defaultPolicy = AccessPolicy{Name: "default_json"} ) func fieldSet(fields ...string) map[string]struct{} { result := make(map[string]struct{}, len(fields)) for _, field := range fields { result[field] = struct{}{} } return result } func policyForPath(path string) AccessPolicy { normalized := strings.ToLower(path) switch { case strings.Contains(normalized, "/login"), strings.Contains(normalized, "token"): return loginPolicy case strings.Contains(normalized, "/system-configs"), strings.Contains(normalized, "/wechat-configs"): return configPolicy case strings.Contains(normalized, "/callback") && (strings.Contains(normalized, "wecom") || strings.Contains(normalized, "wework")): return wecomPolicy case strings.Contains(normalized, "payment"), strings.Contains(normalized, "wechat-pay"), strings.Contains(normalized, "alipay"), strings.Contains(normalized, "fuiou-pay"): return paymentPolicy case strings.Contains(normalized, "/storage"), strings.Contains(normalized, "/upload"), strings.Contains(normalized, "/download"), strings.Contains(normalized, "/export"): return filePolicy default: return defaultPolicy } } func sanitizeWithPolicy(raw []byte, contentType string, policy AccessPolicy) SanitizedContent { if len(raw) == 0 { return SanitizedContent{} } if !policy.Sensitive { return sanitizeBody(raw, BodyPolicyJSON) } summary := make(map[string]any) normalizedContentType := strings.ToLower(contentType) switch { case strings.Contains(normalizedContentType, "json"): var payload any if sonic.Unmarshal(raw, &payload) == nil { collectSafeFields(payload, policy, summary) } case strings.Contains(normalizedContentType, "x-www-form-urlencoded"): if values, err := url.ParseQuery(string(raw)); err == nil { for key, items := range values { collectSafeScalar(key, strings.Join(items, ","), policy, summary) } } } content := "[仅记录安全摘要]" if len(summary) > 0 { if encoded, err := sonic.Marshal(summary); err == nil { content, _ = truncateBody(encoded, MaxBodyLogSize) } } return SanitizedContent{Content: content, Size: len(raw), SHA256: digest(raw), Truncated: len(raw) > MaxBodyLogSize} } func collectSafeFields(value any, policy AccessPolicy, output map[string]any) { switch typed := value.(type) { case map[string]any: for key, item := range typed { switch scalar := item.(type) { case string: collectSafeScalar(key, scalar, policy, output) case float64, bool: if policy.PresenceOnly { if _, isResult := policy.ResultFields[strings.ToLower(key)]; isResult { output[key] = scalar } else { output[key] = map[string]any{"present": true, "length": len(fmt.Sprint(scalar))} } } else if _, allowed := policy.SafeFields[strings.ToLower(key)]; allowed { output[key] = scalar } default: collectSafeFields(item, policy, output) } } case []any: for _, item := range typed { collectSafeFields(item, policy, output) } } } func collectSafeScalar(key, value string, policy AccessPolicy, output map[string]any) { normalized := strings.ToLower(key) if policy.PresenceOnly { if _, isResult := policy.ResultFields[normalized]; isResult { output[key] = value return } output[key] = map[string]any{"present": true, "length": len(value)} return } if _, isFileName := policy.FileFields[normalized]; isFileName { base := filepath.Base(value) hash := digest([]byte(base)) output[key] = "sha256:" + hash[:16] return } if _, allowed := policy.SafeFields[normalized]; allowed { output[key] = value } }