This commit is contained in:
65
backend_v1/app/errorx/app_error.go
Normal file
65
backend_v1/app/errorx/app_error.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package errorx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// AppError 应用错误结构
|
||||
type AppError struct {
|
||||
Code ErrorCode `json:"code"`
|
||||
Message string `json:"message"`
|
||||
StatusCode int `json:"-"`
|
||||
Data any `json:"data,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// 调试信息
|
||||
originalErr error
|
||||
file string
|
||||
params []any
|
||||
sql string
|
||||
}
|
||||
|
||||
// Error 实现 error 接口
|
||||
func (e *AppError) Error() string {
|
||||
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// Unwrap 允许通过 errors.Unwrap 遍历到原始错误
|
||||
func (e *AppError) Unwrap() error { return e.originalErr }
|
||||
|
||||
// WithData 添加数据
|
||||
func (e *AppError) WithData(data any) *AppError {
|
||||
e.Data = data
|
||||
return e
|
||||
}
|
||||
|
||||
// WithMsg 设置消息
|
||||
func (e *AppError) WithMsg(msg string) *AppError {
|
||||
e.Message = msg
|
||||
return e
|
||||
}
|
||||
|
||||
// WithSQL 记录SQL信息
|
||||
func (e *AppError) WithSQL(sql string) *AppError {
|
||||
e.sql = sql
|
||||
return e
|
||||
}
|
||||
|
||||
// WithParams 记录参数信息,并自动获取调用位置
|
||||
func (e *AppError) WithParams(params ...any) *AppError {
|
||||
e.params = params
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
e.file = fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// NewError 创建应用错误
|
||||
func NewError(code ErrorCode, statusCode int, message string) *AppError {
|
||||
return &AppError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
StatusCode: statusCode,
|
||||
}
|
||||
}
|
||||
90
backend_v1/app/errorx/codes.go
Normal file
90
backend_v1/app/errorx/codes.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package errorx
|
||||
|
||||
// ErrorCode 错误码类型
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
// 1000-1099: 数据相关错误
|
||||
CodeRecordNotFound ErrorCode = 1001
|
||||
CodeRecordDuplicated ErrorCode = 1002
|
||||
CodeDataCorrupted ErrorCode = 1003
|
||||
CodeDataTooLarge ErrorCode = 1004
|
||||
CodeDataValidationFail ErrorCode = 1005
|
||||
CodeConstraintViolated ErrorCode = 1006
|
||||
CodeDataExpired ErrorCode = 1007
|
||||
CodeDataLocked ErrorCode = 1008
|
||||
|
||||
// 1100-1199: 请求相关错误
|
||||
CodeBadRequest ErrorCode = 1101
|
||||
CodeMissingParameter ErrorCode = 1102
|
||||
CodeInvalidParameter ErrorCode = 1103
|
||||
CodeParameterTooLong ErrorCode = 1104
|
||||
CodeParameterTooShort ErrorCode = 1105
|
||||
CodeInvalidFormat ErrorCode = 1106
|
||||
CodeUnsupportedMethod ErrorCode = 1107
|
||||
CodeRequestTooLarge ErrorCode = 1108
|
||||
CodeInvalidJSON ErrorCode = 1109
|
||||
CodeInvalidXML ErrorCode = 1110
|
||||
|
||||
// 1200-1299: 认证授权错误
|
||||
CodeUnauthorized ErrorCode = 1201
|
||||
CodeForbidden ErrorCode = 1202
|
||||
CodeTokenExpired ErrorCode = 1203
|
||||
CodeTokenInvalid ErrorCode = 1204
|
||||
CodeTokenMissing ErrorCode = 1205
|
||||
CodePermissionDenied ErrorCode = 1206
|
||||
CodeAccountDisabled ErrorCode = 1207
|
||||
CodeAccountLocked ErrorCode = 1208
|
||||
CodeInvalidCredentials ErrorCode = 1209
|
||||
CodeSessionExpired ErrorCode = 1210
|
||||
|
||||
// 1300-1399: 业务逻辑错误
|
||||
CodeBusinessLogic ErrorCode = 1301
|
||||
CodeWorkflowError ErrorCode = 1302
|
||||
CodeStatusConflict ErrorCode = 1303
|
||||
CodeOperationFailed ErrorCode = 1304
|
||||
CodeResourceConflict ErrorCode = 1305
|
||||
CodePreconditionFailed ErrorCode = 1306
|
||||
CodeQuotaExceeded ErrorCode = 1307
|
||||
CodeResourceExhausted ErrorCode = 1308
|
||||
|
||||
// 1400-1499: 外部服务错误
|
||||
CodeExternalService ErrorCode = 1401
|
||||
CodeServiceUnavailable ErrorCode = 1402
|
||||
CodeServiceTimeout ErrorCode = 1403
|
||||
CodeThirdPartyError ErrorCode = 1404
|
||||
CodeNetworkError ErrorCode = 1405
|
||||
CodeDatabaseError ErrorCode = 1406
|
||||
CodeCacheError ErrorCode = 1407
|
||||
CodeMessageQueueError ErrorCode = 1408
|
||||
|
||||
// 1500-1599: 系统错误
|
||||
CodeInternalError ErrorCode = 1501
|
||||
CodeConfigurationError ErrorCode = 1502
|
||||
CodeFileSystemError ErrorCode = 1503
|
||||
CodeMemoryError ErrorCode = 1504
|
||||
CodeConcurrencyError ErrorCode = 1505
|
||||
CodeDeadlockError ErrorCode = 1506
|
||||
|
||||
// 1600-1699: 限流和频率控制
|
||||
CodeRateLimitExceeded ErrorCode = 1601
|
||||
CodeTooManyRequests ErrorCode = 1602
|
||||
CodeConcurrentLimit ErrorCode = 1603
|
||||
CodeAPIQuotaExceeded ErrorCode = 1604
|
||||
|
||||
// 1700-1799: 文件和上传错误
|
||||
CodeFileNotFound ErrorCode = 1701
|
||||
CodeFileTooBig ErrorCode = 1702
|
||||
CodeInvalidFileType ErrorCode = 1703
|
||||
CodeFileCorrupted ErrorCode = 1704
|
||||
CodeUploadFailed ErrorCode = 1705
|
||||
CodeDownloadFailed ErrorCode = 1706
|
||||
CodeFilePermission ErrorCode = 1707
|
||||
|
||||
// 1800-1899: 加密和安全错误
|
||||
CodeEncryptionError ErrorCode = 1801
|
||||
CodeDecryptionError ErrorCode = 1802
|
||||
CodeSignatureInvalid ErrorCode = 1803
|
||||
CodeCertificateInvalid ErrorCode = 1804
|
||||
CodeSecurityViolation ErrorCode = 1805
|
||||
)
|
||||
105
backend_v1/app/errorx/handler.go
Normal file
105
backend_v1/app/errorx/handler.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package errorx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ErrorHandler 错误处理器
|
||||
type ErrorHandler struct{}
|
||||
|
||||
// NewErrorHandler 创建错误处理器
|
||||
func NewErrorHandler() *ErrorHandler {
|
||||
return &ErrorHandler{}
|
||||
}
|
||||
|
||||
// Handle 处理错误并返回统一格式
|
||||
func (h *ErrorHandler) Handle(err error) *AppError {
|
||||
if appErr, ok := err.(*AppError); ok {
|
||||
return appErr
|
||||
}
|
||||
|
||||
// 处理 Fiber 错误
|
||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||
return h.handleFiberError(fiberErr)
|
||||
}
|
||||
|
||||
// 处理 GORM 错误
|
||||
if appErr := h.handleGormError(err); appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
// 默认内部错误
|
||||
return &AppError{
|
||||
Code: ErrInternalError.Code,
|
||||
Message: err.Error(),
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
originalErr: err,
|
||||
}
|
||||
}
|
||||
|
||||
// handleFiberError 处理 Fiber 错误
|
||||
func (h *ErrorHandler) handleFiberError(fiberErr *fiber.Error) *AppError {
|
||||
var appErr *AppError
|
||||
|
||||
switch fiberErr.Code {
|
||||
case http.StatusBadRequest:
|
||||
appErr = ErrBadRequest
|
||||
case http.StatusUnauthorized:
|
||||
appErr = ErrUnauthorized
|
||||
case http.StatusForbidden:
|
||||
appErr = ErrForbidden
|
||||
case http.StatusNotFound:
|
||||
appErr = ErrRecordNotFound
|
||||
case http.StatusMethodNotAllowed:
|
||||
appErr = ErrUnsupportedMethod
|
||||
case http.StatusRequestEntityTooLarge:
|
||||
appErr = ErrRequestTooLarge
|
||||
case http.StatusTooManyRequests:
|
||||
appErr = ErrTooManyRequests
|
||||
default:
|
||||
appErr = ErrInternalError
|
||||
}
|
||||
|
||||
return &AppError{
|
||||
Code: appErr.Code,
|
||||
Message: fiberErr.Message,
|
||||
StatusCode: fiberErr.Code,
|
||||
originalErr: fiberErr,
|
||||
}
|
||||
}
|
||||
|
||||
// handleGormError 处理 GORM 错误
|
||||
func (h *ErrorHandler) handleGormError(err error) *AppError {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return &AppError{
|
||||
Code: ErrRecordNotFound.Code,
|
||||
Message: ErrRecordNotFound.Message,
|
||||
StatusCode: ErrRecordNotFound.StatusCode,
|
||||
originalErr: err,
|
||||
}
|
||||
}
|
||||
|
||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||
return &AppError{
|
||||
Code: ErrRecordDuplicated.Code,
|
||||
Message: ErrRecordDuplicated.Message,
|
||||
StatusCode: ErrRecordDuplicated.StatusCode,
|
||||
originalErr: err,
|
||||
}
|
||||
}
|
||||
|
||||
if errors.Is(err, gorm.ErrInvalidTransaction) {
|
||||
return &AppError{
|
||||
Code: ErrConcurrencyError.Code,
|
||||
Message: "事务无效",
|
||||
StatusCode: ErrConcurrencyError.StatusCode,
|
||||
originalErr: err,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
38
backend_v1/app/errorx/middleware.go
Normal file
38
backend_v1/app/errorx/middleware.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package errorx
|
||||
|
||||
import "github.com/gofiber/fiber/v3"
|
||||
|
||||
// 全局实例
|
||||
var DefaultSender = NewResponseSender()
|
||||
|
||||
// Middleware 错误处理中间件
|
||||
func Middleware(c fiber.Ctx) error {
|
||||
err := c.Next()
|
||||
if err != nil {
|
||||
return DefaultSender.SendError(c, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 便捷函数
|
||||
func Wrap(err error) *AppError {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if appErr, ok := err.(*AppError); ok {
|
||||
return &AppError{
|
||||
Code: appErr.Code,
|
||||
Message: appErr.Message,
|
||||
StatusCode: appErr.StatusCode,
|
||||
Data: appErr.Data,
|
||||
originalErr: appErr,
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultSender.handler.Handle(err)
|
||||
}
|
||||
|
||||
func SendError(ctx fiber.Ctx, err error) error {
|
||||
return DefaultSender.SendError(ctx, err)
|
||||
}
|
||||
105
backend_v1/app/errorx/predefined.go
Normal file
105
backend_v1/app/errorx/predefined.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package errorx
|
||||
|
||||
import "net/http"
|
||||
|
||||
// 预定义错误 - 数据相关
|
||||
var (
|
||||
ErrRecordNotFound = NewError(CodeRecordNotFound, http.StatusNotFound, "记录不存在")
|
||||
ErrRecordDuplicated = NewError(CodeRecordDuplicated, http.StatusConflict, "记录重复")
|
||||
ErrDataCorrupted = NewError(CodeDataCorrupted, http.StatusBadRequest, "数据损坏")
|
||||
ErrDataTooLarge = NewError(CodeDataTooLarge, http.StatusRequestEntityTooLarge, "数据过大")
|
||||
ErrDataValidationFail = NewError(CodeDataValidationFail, http.StatusBadRequest, "数据验证失败")
|
||||
ErrConstraintViolated = NewError(CodeConstraintViolated, http.StatusConflict, "约束违规")
|
||||
ErrDataExpired = NewError(CodeDataExpired, http.StatusGone, "数据已过期")
|
||||
ErrDataLocked = NewError(CodeDataLocked, http.StatusLocked, "数据已锁定")
|
||||
)
|
||||
|
||||
// 预定义错误 - 请求相关
|
||||
var (
|
||||
ErrBadRequest = NewError(CodeBadRequest, http.StatusBadRequest, "请求错误")
|
||||
ErrMissingParameter = NewError(CodeMissingParameter, http.StatusBadRequest, "缺少必需参数")
|
||||
ErrInvalidParameter = NewError(CodeInvalidParameter, http.StatusBadRequest, "参数无效")
|
||||
ErrParameterTooLong = NewError(CodeParameterTooLong, http.StatusBadRequest, "参数过长")
|
||||
ErrParameterTooShort = NewError(CodeParameterTooShort, http.StatusBadRequest, "参数过短")
|
||||
ErrInvalidFormat = NewError(CodeInvalidFormat, http.StatusBadRequest, "格式无效")
|
||||
ErrUnsupportedMethod = NewError(CodeUnsupportedMethod, http.StatusMethodNotAllowed, "不支持的请求方法")
|
||||
ErrRequestTooLarge = NewError(CodeRequestTooLarge, http.StatusRequestEntityTooLarge, "请求体过大")
|
||||
ErrInvalidJSON = NewError(CodeInvalidJSON, http.StatusBadRequest, "JSON格式错误")
|
||||
ErrInvalidXML = NewError(CodeInvalidXML, http.StatusBadRequest, "XML格式错误")
|
||||
)
|
||||
|
||||
// 预定义错误 - 认证授权
|
||||
var (
|
||||
ErrUnauthorized = NewError(CodeUnauthorized, http.StatusUnauthorized, "未授权")
|
||||
ErrForbidden = NewError(CodeForbidden, http.StatusForbidden, "禁止访问")
|
||||
ErrTokenExpired = NewError(CodeTokenExpired, http.StatusUnauthorized, "Token已过期")
|
||||
ErrTokenInvalid = NewError(CodeTokenInvalid, http.StatusUnauthorized, "Token无效")
|
||||
ErrTokenMissing = NewError(CodeTokenMissing, http.StatusUnauthorized, "Token缺失")
|
||||
ErrPermissionDenied = NewError(CodePermissionDenied, http.StatusForbidden, "权限不足")
|
||||
ErrAccountDisabled = NewError(CodeAccountDisabled, http.StatusForbidden, "账户已禁用")
|
||||
ErrAccountLocked = NewError(CodeAccountLocked, http.StatusLocked, "账户已锁定")
|
||||
ErrInvalidCredentials = NewError(CodeInvalidCredentials, http.StatusUnauthorized, "凭据无效")
|
||||
ErrSessionExpired = NewError(CodeSessionExpired, http.StatusUnauthorized, "会话已过期")
|
||||
)
|
||||
|
||||
// 预定义错误 - 业务逻辑
|
||||
var (
|
||||
ErrBusinessLogic = NewError(CodeBusinessLogic, http.StatusBadRequest, "业务逻辑错误")
|
||||
ErrWorkflowError = NewError(CodeWorkflowError, http.StatusBadRequest, "工作流错误")
|
||||
ErrStatusConflict = NewError(CodeStatusConflict, http.StatusConflict, "状态冲突")
|
||||
ErrOperationFailed = NewError(CodeOperationFailed, http.StatusInternalServerError, "操作失败")
|
||||
ErrResourceConflict = NewError(CodeResourceConflict, http.StatusConflict, "资源冲突")
|
||||
ErrPreconditionFailed = NewError(CodePreconditionFailed, http.StatusPreconditionFailed, "前置条件失败")
|
||||
ErrQuotaExceeded = NewError(CodeQuotaExceeded, http.StatusForbidden, "配额超限")
|
||||
ErrResourceExhausted = NewError(CodeResourceExhausted, http.StatusTooManyRequests, "资源耗尽")
|
||||
)
|
||||
|
||||
// 预定义错误 - 外部服务
|
||||
var (
|
||||
ErrExternalService = NewError(CodeExternalService, http.StatusBadGateway, "外部服务错误")
|
||||
ErrServiceUnavailable = NewError(CodeServiceUnavailable, http.StatusServiceUnavailable, "服务不可用")
|
||||
ErrServiceTimeout = NewError(CodeServiceTimeout, http.StatusRequestTimeout, "服务超时")
|
||||
ErrThirdPartyError = NewError(CodeThirdPartyError, http.StatusBadGateway, "第三方服务错误")
|
||||
ErrNetworkError = NewError(CodeNetworkError, http.StatusBadGateway, "网络错误")
|
||||
ErrDatabaseError = NewError(CodeDatabaseError, http.StatusInternalServerError, "数据库错误")
|
||||
ErrCacheError = NewError(CodeCacheError, http.StatusInternalServerError, "缓存错误")
|
||||
ErrMessageQueueError = NewError(CodeMessageQueueError, http.StatusInternalServerError, "消息队列错误")
|
||||
)
|
||||
|
||||
// 预定义错误 - 系统错误
|
||||
var (
|
||||
ErrInternalError = NewError(CodeInternalError, http.StatusInternalServerError, "内部错误")
|
||||
ErrConfigurationError = NewError(CodeConfigurationError, http.StatusInternalServerError, "配置错误")
|
||||
ErrFileSystemError = NewError(CodeFileSystemError, http.StatusInternalServerError, "文件系统错误")
|
||||
ErrMemoryError = NewError(CodeMemoryError, http.StatusInternalServerError, "内存错误")
|
||||
ErrConcurrencyError = NewError(CodeConcurrencyError, http.StatusInternalServerError, "并发错误")
|
||||
ErrDeadlockError = NewError(CodeDeadlockError, http.StatusInternalServerError, "死锁错误")
|
||||
)
|
||||
|
||||
// 预定义错误 - 限流
|
||||
var (
|
||||
ErrRateLimitExceeded = NewError(CodeRateLimitExceeded, http.StatusTooManyRequests, "请求频率超限")
|
||||
ErrTooManyRequests = NewError(CodeTooManyRequests, http.StatusTooManyRequests, "请求过多")
|
||||
ErrConcurrentLimit = NewError(CodeConcurrentLimit, http.StatusTooManyRequests, "并发数超限")
|
||||
ErrAPIQuotaExceeded = NewError(CodeAPIQuotaExceeded, http.StatusTooManyRequests, "API配额超限")
|
||||
)
|
||||
|
||||
// 预定义错误 - 文件处理
|
||||
var (
|
||||
ErrFileNotFound = NewError(CodeFileNotFound, http.StatusNotFound, "文件不存在")
|
||||
ErrFileTooBig = NewError(CodeFileTooBig, http.StatusRequestEntityTooLarge, "文件过大")
|
||||
ErrInvalidFileType = NewError(CodeInvalidFileType, http.StatusBadRequest, "文件类型无效")
|
||||
ErrFileCorrupted = NewError(CodeFileCorrupted, http.StatusBadRequest, "文件损坏")
|
||||
ErrUploadFailed = NewError(CodeUploadFailed, http.StatusInternalServerError, "上传失败")
|
||||
ErrDownloadFailed = NewError(CodeDownloadFailed, http.StatusInternalServerError, "下载失败")
|
||||
ErrFilePermission = NewError(CodeFilePermission, http.StatusForbidden, "文件权限不足")
|
||||
)
|
||||
|
||||
// 预定义错误 - 安全相关
|
||||
var (
|
||||
ErrEncryptionError = NewError(CodeEncryptionError, http.StatusInternalServerError, "加密错误")
|
||||
ErrDecryptionError = NewError(CodeDecryptionError, http.StatusInternalServerError, "解密错误")
|
||||
ErrSignatureInvalid = NewError(CodeSignatureInvalid, http.StatusUnauthorized, "签名无效")
|
||||
ErrCertificateInvalid = NewError(CodeCertificateInvalid, http.StatusUnauthorized, "证书无效")
|
||||
ErrSecurityViolation = NewError(CodeSecurityViolation, http.StatusForbidden, "安全违规")
|
||||
)
|
||||
127
backend_v1/app/errorx/response.go
Normal file
127
backend_v1/app/errorx/response.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user