package server import ( "bytes" "encoding/json" "github.com/gin-gonic/gin" "io" "kra/internal/biz" "kra/internal/service" "strings" "time" ) type captureWriter struct { gin.ResponseWriter body bytes.Buffer } func (w *captureWriter) Write(data []byte) (int, error) { if w.body.Len() < 32768 { remaining := 32768 - w.body.Len() if len(data) > remaining { _, _ = w.body.Write(data[:remaining]) } else { _, _ = w.body.Write(data) } } return w.ResponseWriter.Write(data) } func redactJSON(raw []byte) string { if len(raw) == 0 { return "" } if len(raw) > 32768 { raw = raw[:32768] } var value any if json.Unmarshal(raw, &value) != nil { return string(raw) } var clean func(any) clean = func(current any) { switch v := current.(type) { case map[string]any: for key, item := range v { lower := strings.ToLower(key) if strings.Contains(lower, "password") || strings.Contains(lower, "token") || strings.Contains(lower, "secret") { v[key] = "******" } else { clean(item) } } case []any: for _, item := range v { clean(item) } } } clean(value) encoded, _ := json.Marshal(value) return string(encoded) } func operationAudit(svc *service.AuditService) gin.HandlerFunc { return func(c *gin.Context) { if c.Request.Method == "GET" || c.Request.Method == "HEAD" || c.Request.Method == "OPTIONS" { c.Next() return } path := c.Request.URL.Path if strings.Contains(path, "sysOperationRecord") || strings.Contains(path, "sysLoginLog") || strings.Contains(path, "dataAccessLog") { c.Next() return } var requestBody []byte if c.Request.Body != nil { requestBody, _ = io.ReadAll(io.LimitReader(c.Request.Body, 32769)) c.Request.Body = io.NopCloser(bytes.NewReader(requestBody)) } writer := &captureWriter{ResponseWriter: c.Writer} c.Writer = writer started := time.Now() c.Next() userID := uint(0) if claims := currentClaims(c); claims != nil { userID = claims.ID } requestID, _ := c.Get("request_id") status := c.Writer.Status() errorMessage := "" if status >= 400 { errorMessage = writer.body.String() } _ = svc.RecordOperation(c.Request.Context(), &biz.OperationRecord{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: redactJSON(requestBody), Response: redactJSON(writer.body.Bytes()), UserID: userID, RequestID: toString(requestID), TraceID: c.GetHeader("traceparent"), DeviceID: c.GetHeader("X-Device-Id")}) } } func toString(value any) string { if text, ok := value.(string); ok { return text } return "" }