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:
Awuqing
2026-08-26 11:32:00 +08:00
parent 95b27af600
commit c06bbec383
189 changed files with 8983 additions and 4264 deletions
+42 -2
View File
@@ -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...)
}
+39
View File
@@ -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)
}
}