良好的错误处理习惯能让代码更健壮,问题排查更高效。
集中定义所有错误:
package errors
import "errors"
var (
ErrUserNotFound = errors.New("用户不存在")
ErrUserDisabled = errors.New("用户已被禁用")
ErrPasswordWrong = errors.New("密码错误")
ErrTokenExpired = errors.New("登录已过期")
ErrTokenInvalid = errors.New("无效的登录凭证")
ErrPermissionDenied = errors.New("权限不足")
)
type BizError struct {
Code int
Message string
Details interface{}
}
func (e *BizError) Error() string {
return e.Message
}
func NewBizError(code int, message string, details ...interface{}) *BizError {
e := &BizError{
Code: code,
Message: message,
}
if len(details) > 0 {
e.Details = details[0]
}
return e
}
const (
CodeSuccess = 0
CodeBadRequest = 400
CodeUnauthorized = 401
CodeForbidden = 403
CodeNotFound = 404
CodeConflict = 409
CodeInternalError = 500
CodeUserNotFound = 10001
CodeUserDisabled = 10002
CodePasswordWrong = 10003
CodeTokenExpired = 10004
CodeOrderNotFound = 20001
CodeOrderPaid = 20002
CodeOrderCancelled = 20003
)
var codeMessages = map[int]string{
CodeSuccess: "成功",
CodeBadRequest: "请求参数错误",
CodeUnauthorized: "未授权",
CodeForbidden: "禁止访问",
CodeNotFound: "资源不存在",
CodeUserNotFound: "用户不存在",
CodeUserDisabled: "用户已被禁用",
CodePasswordWrong: "密码错误",
CodeTokenExpired: "登录已过期",
CodeOrderNotFound: "订单不存在",
CodeOrderPaid: "订单已支付",
CodeOrderCancelled: "订单已取消",
}
func GetMessage(code int) string {
if msg, ok := codeMessages[code]; ok {
return msg
}
return "未知错误"
}
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Errors []ErrorItem `json:"errors,omitempty"`
}
type ErrorItem struct {
Field string `json:"field,omitempty"`
Message string `json:"message"`
}
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{
Code: CodeSuccess,
Message: GetMessage(CodeSuccess),
Data: data,
})
}
func Fail(c *gin.Context, code int, errors ...ErrorItem) {
c.JSON(http.StatusOK, Response{
Code: code,
Message: GetMessage(code),
Errors: errors,
})
}
r.GET("/user/:id", func(c *gin.Context) {
user, err := getUser(c.Param("id"))
if err != nil {
Fail(c, CodeUserNotFound)
return
}
Success(c, user)
})
import "errors"
func WrapError(err error, message string) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %w", message, err)
}
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, WrapError(err, "查询用户失败")
}
return user, nil
}
r.GET("/user/:id", func(c *gin.Context) {
user, err := GetUser(c.Param("id"))
if err != nil {
log.Printf("获取用户失败: %v", err)
Fail(c, CodeUserNotFound)
return
}
Success(c, user)
})
func LogError(c *gin.Context, err error, fields ...interface{}) {
requestID, _ := c.Get("requestID")
entry := log.NewEntry(log.StandardLogger())
entry = entry.WithField("request_id", requestID)
entry = entry.WithField("path", c.Request.URL.Path)
entry = entry.WithField("method", c.Request.Method)
for i := 0; i < len(fields); i += 2 {
if i+1 < len(fields) {
entry = entry.WithField(fields[i].(string), fields[i+1])
}
}
entry.Error(err.Error())
}
r.GET("/test", func(c *gin.Context) {
user, err := getUser("123")
if err != nil {
LogError(c, err, "user_id", "123", "action", "get_user")
Fail(c, CodeUserNotFound)
return
}
Success(c, user)
})
func RecoveryMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
requestID, _ := c.Get("requestID")
log.Printf("[PANIC] request_id=%s, error=%v\n%s",
requestID, err, debug.Stack())
c.AbortWithStatusJSON(http.StatusInternalServerError, Response{
Code: CodeInternalError,
Message: "服务器内部错误",
})
}
}()
c.Next()
}
}
func ErrorHandlerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
var errors []ErrorItem
for _, e := range c.Errors.ByType(gin.ErrorTypePublic) {
errors = append(errors, ErrorItem{
Message: e.Error(),
})
}
if len(errors) > 0 {
c.JSON(http.StatusBadRequest, Response{
Code: CodeBadRequest,
Message: "请求处理失败",
Errors: errors,
})
}
}
}
}
func HandleValidationError(c *gin.Context, err error) {
var errors []ErrorItem
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, e := range validationErrors {
errors = append(errors, ErrorItem{
Field: e.Field(),
Message: getValidationMessage(e),
})
}
} else {
errors = append(errors, ErrorItem{
Message: err.Error(),
})
}
Fail(c, CodeBadRequest, errors...)
}
func getValidationMessage(e validator.FieldError) string {
switch e.Tag() {
case "required":
return e.Field() + "为必填项"
case "email":
return "邮箱格式不正确"
case "min":
return e.Field() + "最小长度为" + e.Param()
case "max":
return e.Field() + "最大长度为" + e.Param()
default:
return e.Field() + "验证失败"
}
}
r.POST("/user", func(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
HandleValidationError(c, err)
return
}
Success(c, nil)
})
良好的错误处理需要:统一错误定义、规范错误码、统一响应格式、合理记录日志。错误信息要对用户友好,同时保留足够的调试信息。使用中间件统一处理错误,避免代码重复。