统一错误响应
如何定义统一错误响应结构?
type ErrorResponse struct {
Code string `json:"code"`
Message string `json:"message"`
RequestID string `json:"request_id"`
Details map[string]any `json:"details,omitempty"`
}
- status: HTTP 语义; 客户端可先按 4xx/5xx 分类;
- code: 稳定业务标识; 不直接使用 Go error 文本;
- message: 面向调用方的安全描述; 不泄露 SQL、路径或堆栈;
- request_id: 连接响应、日志与 Trace;
- details: 字段校验等结构化信息; 不作为任意调试数据容器;
如何把领域结果映射为 HTTP 状态?
| 领域结果 | HTTP | code |
|---|---|---|
| 参数无效 | 400 | INVALID_ARGUMENT |
| 未认证 | 401 | UNAUTHENTICATED |
| 无权限 | 403 | PERMISSION_DENIED |
| 文章不存在 | 404 | ARTICLE_NOT_FOUND |
| 版本或唯一键冲突 | 409 | CONFLICT |
| 限流 | 429 | RATE_LIMITED |
| 未分类内部错误 | 500 | INTERNAL |
如何保留错误根因并安全返回?
if errors.Is(err, article.ErrNotFound) {
writeError(c, 404, "ARTICLE_NOT_FOUND", "article not found")
return
}
logger.Error("get article failed", zap.Error(err))
writeError(c, 500, "INTERNAL", "internal server error")
- 领域错误: Service 暴露可分类的 sentinel 或类型;
- 基础设施错误: Repository 使用
%w包装,保留根因; - 记录一次: 最接近 HTTP 边界且掌握 request_id 的层记录完整错误;
- panic: recovery 兜底为 500; panic 不替代正常错误处理;