必须定义结构体错误类型(如*apperror)实现error接口和is方法,统一用%w包装错误链,fiber中通过errorhandler中间件在http边界层转换错误为json响应,业务层禁止涉及http状态码。

统一错误类型必须实现 error 接口并支持 errors.Is
直接用 fmt.Errorf 或 errors.New 返回的错误无法被下游安全识别,调用方只能靠字符串匹配——一旦文案微调就崩。必须定义结构体错误类型,并实现 Error() 方法和 Is() 方法。
例如定义一个通用业务错误:
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Status int `json:"status"`
}
func (e *AppError) Error() string { return e.Message }
func (e *AppError) Is(target error) bool {
_, ok := target.(*AppError)
return ok
}
- 所有模块返回的错误都应是
*AppError或其子类型(如*ValidationError),不能混用裸error - 跨模块调用时,用
fmt.Errorf("xxx: %w", err)包装,%w才能保留原始错误链;用%v或%s就断了 - 禁止在
Error()方法里拼接敏感字段(如原始 token、密码),日志可能直接输出该字符串
Fiber 中间件里拦截 *AppError 并转成标准 JSON 响应
Fiber 没有内置错误处理中间件,得自己写一个,在 app.Use() 中注册,且必须放在路由注册之前(否则不生效)。
关键点不是“捕获 panic”,而是主动检查 handler 返回的 error 是否为 *AppError:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func ErrorHandler(c *fiber.Ctx, err error) error {
var appErr *AppError
if errors.As(err, &appErr) {
return c.Status(appErr.Status).JSON(fiber.Map{
"code": appErr.Code,
"message": appErr.Message,
"request_id": c.Locals("request_id"), // 若已注入
})
}
// 非 AppError 当作 500 处理,但别暴露堆栈
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"code": 5000,
"message": "internal server error",
})
}
- 注册方式:
app.Use(ErrorHandler),顺序必须在app.Get()等路由注册之前 - 不要在中间件里调用
c.Next()后再处理错误——Fiber 的错误传递机制是 handler 返回error,由框架自动交给 errorHandler - 若用了
recover.New(),它只捕获 panic,对return err无感,别指望它替代你的ErrorHandler
HTTP 边界层才是错误转换的唯一位置
业务层(service、repo)不该生成 HTTP 状态码,也不该调用 c.Status()。错误只携带语义信息(如 Code=1002 表示“用户不存在”),状态码由 *AppError.Status 字段承载,仅在 Fiber 的 errorHandler 中统一映射。
- 业务函数签名应为
func(*User) (*User, error),返回*AppError而非fiber.ErrNotFound - 不要在 service 层写
if err != nil { return fiber.Map{...} }—— 这会污染领域逻辑,也破坏错误可追溯性 - 前端依赖
code做 UI 分支,后端监控依赖Status做告警阈值,二者必须解耦
ctx.Accepts() 不是错误处理,别把它和错误统一混为一谈
有人试图用 ctx.Accepts("application/json") 判断请求头然后手动返回错误,这是错的。这个方法只返回匹配的 MIME 类型字符串,空字符串 ≠ 错误,它本身不抛错也不中断流程。
真正要做的是:在 errorHandler 里统一响应格式,而内容协商由客户端驱动——你只管返回标准 JSON,客户端自己决定要不要接受。
- 如果真要拒绝非 JSON 请求,应在单独中间件里检查
ctx.Get("Accept"),手动return c.Status(406).SendString("not acceptable") - 但这类校验属于「协议合规」,不是「业务错误」,不应塞进
*AppError流程里 - 更常见且合理的做法是:默认支持
application/json,其他格式(如 XML)按需扩展,不做强制
c.JSON() 或硬编码 http.StatusBadRequest,整条链路就散了。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










