Go Web错误处理需显式传递、分层包装、统一响应;直接return err语法错误,http.Error丢失错误链/码/上下文;应返回(interface{}, error),用fmt.Errorf("%w")包装、errors.Is判断、AppError结构体区分业务错误,中间件统一设Content-Type并按Code映射状态码。

Go Web 错误处理不是“捕获异常”,而是显式传递、分层包装、统一响应——不按这个逻辑做,90% 的 panic 和 500 错误都源于错误被忽略或裸返回。
为什么不能直接 return err 在 handler 里?
Go 的 http.HandlerFunc 签名不支持返回 error,写 return err 是语法错误;更常见的是误用 http.Error(w, err.Error(), 500),这会丢失三样关键东西:原始错误链(无法用 errors.Is 判断)、业务错误码(如“用户不存在”该是 404 还是 1002)、上下文信息(比如哪步解析失败)。
- 正确做法:handler 返回
(interface{}, error),由中间件统一解包并渲染 - 错误必须带上下文:用
fmt.Errorf("get user %q: %w", id, err),不是fmt.Errorf("get user: %s", err) - 禁止在 handler 里调用
log.Printf——日志应由中间件或AppError.Error()统一输出,含 traceID 和路径
如何用 errors.Is 安全判断 sql.ErrNoRows?
数据库查不到记录(sql.ErrNoRows)是正常业务流,不是 bug。但很多人写 if err == sql.ErrNoRows,结果一包装就失效——因为 fmt.Errorf("load: %w", err) 后,err 已不是原始指针,而是包装后的错误链。
- 永远用
errors.Is(err, sql.ErrNoRows),它能穿透任意层数的%w包装 - 不要把
sql.ErrNoRows再包装成 “not found” 错误向上抛——它本身已是标准、可预测、可测试的信号 - 其他 DB 错误(如
context.DeadlineExceeded)要单独判断,该重试就重试,不该暴露给前端
中间件怎么同时处理 panic 和业务 error?
一个中间件只做一件事:recover panic 是底线,但不能止步于此。真正健壮的错误中间件,要能识别 handler 返回的 *AppError 并映射到对应 HTTP 状态码,而不是一律打成 500。
本书将PHP开发与MySQL应用相结合,分别对PHP和MySQL做了深入浅出的分析,不仅介绍PHP和MySQL的一般概念,而且对PHP和MySQL的Web应用做了较全面的阐述,并包括几个经典且实用的例子。 本书是第4版,经过了全面的更新、重写和扩展,包括PHP5.3最新改进的特性(例如,更好的错误和异常处理),MySQL的存储过程和存储引擎,Ajax技术与Web2.0以及Web应用需要注意的安全
立即学习“go语言免费学习笔记(深入)”;
- 定义
type AppError struct { Code int; Message string; Err error },Err字段用json:"-"隐藏,避免泄露 - 中间件中用
errors.As(err, &appErr)提取业务错误,再按appErr.Code设置w.WriteHeader() - 对未识别的 panic 或非
*AppError错误,统一记日志 + 返回http.StatusInternalServerError,不暴露堆栈
writeError 函数为什么必须显式设 Content-Type?
很多接口返回错误时出现乱码或前端解析失败,根本原因是没设 header。Go 的 json.NewEncoder 不会自动设 Content-Type,而某些反向代理或浏览器在缺失时会按 text/plain 解析 JSON 字符串。
- 必须在
writeError开头加w.Header().Set("Content-Type", "application/json; charset=utf-8") -
details字段要判空:Details: details若传nil,JSON 编码后是"details": null,前端可能崩溃;建议用指针或预处理 - 状态码顺序不能错:先
w.WriteHeader(),再json.NewEncoder().Encode(),否则可能写入失败且无提示
最易被忽略的一点:错误包装只应在**职责边界**发生一次——DAO 层返回原始 os.PathError,Service 层包装为 "failed to load config: %w",Handler 层只负责映射和响应,不再二次包装。多包一层,就多一层解析成本和歧义风险。









