feat: 添加应用错误处理结构及预定义错误码

This commit is contained in:
Rogee
2025-09-11 11:44:26 +08:00
parent bfcc1327d2
commit ced4202dc9
9 changed files with 645 additions and 161 deletions

View File

@@ -0,0 +1,127 @@
package errorx
import (
"errors"
"fmt"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/binder"
"github.com/gofiber/utils/v2"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
// ResponseSender 响应发送器
type ResponseSender struct {
handler *ErrorHandler
}
// NewResponseSender 创建响应发送器
func NewResponseSender() *ResponseSender {
return &ResponseSender{
handler: NewErrorHandler(),
}
}
// SendError 发送错误响应
func (s *ResponseSender) SendError(ctx fiber.Ctx, err error) error {
appErr := s.handler.Handle(err)
//
s.logError(appErr)
// Content-Type
return s.sendResponse(ctx, appErr)
}
// logError 记录错误日志
func (s *ResponseSender) logError(appErr *AppError) {
// ID便
if appErr.ID == "" {
appErr.ID = uuid.NewString()
}
//
chain := make([]map[string]any, 0, 4)
var e error = appErr
for e != nil {
entry := map[string]any{
"type": fmt.Sprintf("%T", e),
"error": e.Error(),
}
switch v := e.(type) {
case *AppError:
entry["code"] = v.Code
entry["statusCode"] = v.StatusCode
if v.file != "" {
entry["file"] = v.file
}
if len(v.params) > 0 {
entry["params"] = v.params
}
if v.sql != "" {
entry["sql"] = v.sql
}
if v.ID != "" {
entry["id"] = v.ID
}
case *fiber.Error:
entry["statusCode"] = v.Code
entry["message"] = v.Message
}
// GORM
if errors.Is(e, gorm.ErrRecordNotFound) {
entry["gorm"] = "record_not_found"
} else if errors.Is(e, gorm.ErrDuplicatedKey) {
entry["gorm"] = "duplicated_key"
} else if errors.Is(e, gorm.ErrInvalidTransaction) {
entry["gorm"] = "invalid_transaction"
}
chain = append(chain, entry)
e = errors.Unwrap(e)
}
root := chain[len(chain)-1]["error"]
logEntry := log.WithFields(log.Fields{
"id": appErr.ID,
"code": appErr.Code,
"statusCode": appErr.StatusCode,
"file": appErr.file,
"sql": appErr.sql,
"params": appErr.params,
"error_chain": chain,
"root_error": root,
})
if appErr.originalErr != nil {
logEntry = logEntry.WithError(appErr.originalErr)
}
//
if appErr.StatusCode >= 500 {
logEntry.Error("系统错误: ", appErr.Message)
} else if appErr.StatusCode >= 400 {
logEntry.Warn("客户端错误: ", appErr.Message)
} else {
logEntry.Info("应用错误: ", appErr.Message)
}
}
// sendResponse 发送响应
func (s *ResponseSender) sendResponse(ctx fiber.Ctx, appErr *AppError) error {
contentType := utils.ToLower(utils.UnsafeString(ctx.Request().Header.ContentType()))
contentType = binder.FilterFlags(utils.ParseVendorSpecificContentType(contentType))
switch contentType {
case fiber.MIMETextXML, fiber.MIMEApplicationXML:
return ctx.Status(appErr.StatusCode).XML(appErr)
case fiber.MIMETextHTML, fiber.MIMETextPlain:
return ctx.Status(appErr.StatusCode).SendString(appErr.Message)
default:
return ctx.Status(appErr.StatusCode).JSON(appErr)
}
}