mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-09-04 06:57:30 +08:00
refactor: simplify architecture and harden lifecycle
Remove obsolete implementations, centralize background task ownership and terminal-state recovery, consolidate frontend routing and log streaming, and enforce project-wide verification in CI.
This commit is contained in:
@@ -2,13 +2,15 @@ package response
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"backupx/server/internal/apperror"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const loggerContextKey = "backupx.response.logger"
|
||||
|
||||
type Envelope struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
@@ -19,12 +21,50 @@ func Success(c *gin.Context, data any) {
|
||||
c.JSON(http.StatusOK, Envelope{Code: "OK", Message: "success", Data: data})
|
||||
}
|
||||
|
||||
// SetLogger attaches the application logger to a request so response helpers
|
||||
// can report failures without relying on a process-global logger.
|
||||
func SetLogger(c *gin.Context, logger *zap.Logger) {
|
||||
if logger != nil {
|
||||
c.Set(loggerContextKey, logger)
|
||||
}
|
||||
}
|
||||
|
||||
func Error(c *gin.Context, err error) {
|
||||
fmt.Printf("HTTP Error: %v\n", err)
|
||||
var appErr *apperror.AppError
|
||||
if errors.As(err, &appErr) {
|
||||
logError(c, appErr.Status, appErr.Code, err)
|
||||
c.JSON(appErr.Status, Envelope{Code: appErr.Code, Message: appErr.Message})
|
||||
return
|
||||
}
|
||||
logError(c, http.StatusInternalServerError, "INTERNAL_ERROR", err)
|
||||
c.JSON(http.StatusInternalServerError, Envelope{Code: "INTERNAL_ERROR", Message: "服务器内部错误"})
|
||||
}
|
||||
|
||||
func logError(c *gin.Context, status int, code string, err error) {
|
||||
value, exists := c.Get(loggerContextKey)
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
logger, ok := value.(*zap.Logger)
|
||||
if !ok || logger == nil {
|
||||
return
|
||||
}
|
||||
method := ""
|
||||
path := ""
|
||||
if c.Request != nil {
|
||||
method = c.Request.Method
|
||||
path = c.Request.URL.Path
|
||||
}
|
||||
fields := []zap.Field{
|
||||
zap.Int("status", status),
|
||||
zap.String("code", code),
|
||||
zap.String("method", method),
|
||||
zap.String("path", path),
|
||||
zap.Error(err),
|
||||
}
|
||||
if status >= http.StatusInternalServerError {
|
||||
logger.Error("http request failed", fields...)
|
||||
return
|
||||
}
|
||||
logger.Warn("http request rejected", fields...)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest/observer"
|
||||
)
|
||||
|
||||
func TestErrorLogsInternalFailureAndHidesDetail(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/test", nil)
|
||||
core, observed := observer.New(zap.WarnLevel)
|
||||
SetLogger(ctx, zap.New(core))
|
||||
|
||||
Error(ctx, errors.New("database password should stay private"))
|
||||
|
||||
if recorder.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusInternalServerError)
|
||||
}
|
||||
if strings.Contains(recorder.Body.String(), "database password") {
|
||||
t.Fatalf("internal detail leaked in response: %s", recorder.Body.String())
|
||||
}
|
||||
entries := observed.All()
|
||||
if len(entries) != 1 || entries[0].Level != zap.ErrorLevel {
|
||||
t.Fatalf("observed logs = %#v, want one error", entries)
|
||||
}
|
||||
context := entries[0].ContextMap()
|
||||
if context["code"] != "INTERNAL_ERROR" || context["path"] != "/api/test" {
|
||||
t.Fatalf("unexpected log context: %#v", context)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user