当前位置:首页 > 文章列表 > Golang > Go问答 > Go 接口返回值怎么设计:用结构化错误让调用方区分重试与修复

Go 接口返回值怎么设计:用结构化错误让调用方区分重试与修复

来源:17golang原创 2026-08-26 03:20:57 0浏览 收藏

订单查询接口上线后,最让调用方难受的不是请求失败,而是失败时只收到一段“请求处理失败”。客户端不知道是订单号不存在、参数格式不对,还是服务端暂时不可用,于是有人选择无限重试,有人直接把错误弹给用户,监控里的失败率也失去了行动指向。

Go 接口的错误返回应该让调用方能稳定回答三个问题:要不要重试、该不该让用户修改输入、这次失败是否需要服务端告警。为此,错误信息要拆成稳定的错误类型、面向人的消息和可选的上下文,而不是把内部错误字符串直接透传。

实践要点
  • HTTP 状态表达协议层结果,业务错误码表达可判断的业务语义。
  • 客户端按稳定的错误类型决定重试或修复,不要匹配易变的文案。
  • 新增字段要兼容旧客户端,错误响应本身也要有版本边界。

先把接口目标说清楚:失败也要能行动

假设接口是 GET /v1/orders/{orderID}。调用方包含网页端、移动端和一个定时同步程序,它们对同一个失败的处理方式并不一样:网页端要提示用户,移动端可能展示订单不存在,同步程序则只应对临时故障重试。

因此“返回一个 error 字段”不是完整目标。接口至少要提供一组稳定的判断依据:

  • 协议层:请求是否成功到达、是否需要重新认证、是否可以再次发送。
  • 业务层:订单不存在、状态不允许、参数冲突等是否属于调用方可修复的问题。
  • 运维层:是否应该计入服务端异常、是否需要关联 request ID 排查。

这里的关键取舍是:错误消息可以改,错误类型和处理契约不能随意改。

调用方真正需要的是动作,而不是一串文字

客户端最容易踩的坑,是写出类似 strings.Contains(message, "timeout") 的判断。文案一旦被翻译、润色或换成上游错误,重试策略就会悄悄失效。

更稳妥的做法是把错误分成有限几类,并为每一类规定动作。例如:

错误类型调用方动作是否告警
参数无效停止重试,提示修改输入通常不告警
资源不存在结束当前流程不告警
临时不可用指数退避后重试按比例告警
服务端异常有限次数重试并记录请求号告警

这张表不是让所有接口使用同一套业务码,而是提醒我们先定义“错误到动作”的映射,再决定 JSON 字段。

参数设计要区分稳定字段和诊断字段

一个可落地的错误响应可以保持这样的形状:

{
  "error": {
    "type": "order_not_found",
    "message": "订单不存在",
    "request_id": "req_7f31a2",
    "retryable": false
  }
}

type 是给程序判断的稳定标识,message 面向人阅读,request_id 用于把用户反馈接到日志,retryable 则把服务端对重试边界的判断显式化。生产接口还可以增加字段,但不要把数据库错误、堆栈和内部主机名放进响应。

参数也要有相同的边界意识。例如 retryable 表示“这一次请求是否适合再次尝试”,不是承诺“重试一定成功”;对带副作用的 POST 接口,还需要单独定义幂等键,不能只因为字段写了 true 就自动重放。

在 Go 里用哨兵错误保留可判断性

服务内部可以用哨兵错误表达领域语义,再由 HTTP 层统一映射。这样业务代码不必知道 JSON 的字段布局:

var (
    ErrOrderNotFound = errors.New("order not found")
    ErrTemporary      = errors.New("temporary failure")
)

func loadOrder(ctx context.Context, id string) (Order, error) {
    order, err := repo.Find(ctx, id)
    if errors.Is(err, sql.ErrNoRows) {
        return Order{}, ErrOrderNotFound
    }
    if err != nil {
        return Order{}, fmt.Errorf("find order %q: %w", id, ErrTemporary)
    }
    return order, nil
}

处理器再集中做映射:

func writeOrderError(w http.ResponseWriter, err error, requestID string) {
    status := http.StatusInternalServerError
    typ := "internal_error"
    retryable := false

    switch {
    case errors.Is(err, ErrOrderNotFound):
        status, typ = http.StatusNotFound, "order_not_found"
    case errors.Is(err, ErrTemporary):
        typ, retryable = "temporary_failure", true
    }

    w.WriteHeader(status)
    _ = json.NewEncoder(w).Encode(map[string]any{
        "error": map[string]any{
            "type": typ, "message": publicMessage(typ),
            "request_id": requestID, "retryable": retryable,
        },
    })
}

不要把 err.Error() 直接作为 message。内部错误常常包含表名、上游地址或实现细节,既不适合用户,也可能泄露不该公开的信息。

HTTP 状态和业务错误码如何各司其职

HTTP 状态不是业务码的替代品。404 可以表达资源不存在,但它不能告诉调用方这是订单不存在、附件不存在,还是接口路径写错。反过来,所有失败都返回 200,又会让网关、监控和通用 SDK 误判。

建议先遵守协议层的基本语义,再在响应体中提供业务类型:参数格式错误用 400,未认证用 401,无权限用 403,资源不存在用 404,限流用 429,临时故障或服务端异常使用合适的 5xx。具体选择要和网关、客户端 SDK 的约定一起验收。

429 这类明确的临时限制,可以增加 Retry-After。但不要为所有 5xx 都承诺固定等待时间;退避上限、最大次数和幂等条件应由调用方策略控制。

兼容策略:错误响应也需要演进规则

最安全的新增方式是只增加字段,不改变已有 type 的含义,也不删除旧字段。旧客户端会忽略它不认识的字段,新客户端则可以读取 request_idretryable

如果必须改变错误语义,新增一个明确的错误类型,而不是复用旧值。例如过去的 temporary_failure 混合了限流和依赖故障,那么可以新增 rate_limited,并在一段兼容期内保留旧客户端能理解的 HTTP 状态。

测试不能只断言“请求失败”。至少要覆盖 HTTP 状态、type、消息是否可读、请求号是否存在,以及客户端遇到可重试错误时是否真的遵守次数和退避上限。

用一个最小客户端验证错误契约

客户端可以把错误解析成自己的类型,但要把未知错误当成保守分支处理:

type APIError struct {
    Type      string `json:"type"`
    Message   string `json:"message"`
    RequestID string `json:"request_id"`
    Retryable bool   `json:"retryable"`
}

func shouldRetry(err *APIError, status int, attempt int) bool {
    if err == nil || attempt >= 3 {
        return false
    }
    return err.Retryable && status >= 500
}

实际项目还要考虑响应体损坏、网关替换错误页、网络连接建立失败等情况。解析失败时不要默认无限重试,应该受总耗时和次数限制,并把原始请求号、状态码和接口路径写入本地日志。

常见误区与上线前检查

只返回业务码,不返回 HTTP 状态

这会损失通用基础设施能理解的协议语义。保留 HTTP 状态,再补充稳定的业务类型。

把所有错误都标成可重试

参数错误和权限错误会被放大成重试风暴。只有服务端确认适合再次尝试,或者客户端有明确的网络层退避策略时,才允许重试。

把数据库错误原样放进 message

对外消息应该稳定、可翻译、可读;详细原因留在服务端日志,并用 request ID 关联。

上线前可以按下面的顺序验收:先用无效订单号检查 404/order_not_found,再模拟依赖超时检查 5xx/temporary_failure,最后确认旧客户端忽略新增字段仍能完成原有流程。这个过程比单独看接口文档更容易发现契约漂移。

延伸问答

错误类型应该使用数字还是字符串?

只要稳定、可文档化并且不会和展示文案混用,两者都能工作。字符串通常更容易在日志和跨语言 SDK 中阅读,数字则要额外维护映射表。

是否要把重试次数放进错误响应?

通常不需要。服务端可以通过 Retry-After 给出明确等待建议,客户端仍应设置自己的总次数和总耗时上限。

内部服务也需要这一套结构吗?

需要,但字段可以更精简。只要存在多个调用方或跨进程边界,就值得把可判断的错误语义固定下来,避免每个调用方各自猜测。

好的错误响应不是把字段堆得更满,而是把“这次失败之后能做什么”讲清楚。先固定错误类型和动作映射,再补充诊断字段,最后用兼容测试守住演进边界,Go 接口才能在调用方变多以后仍然可控。

Go 接口结构化错误将参数问题、临时故障和服务端异常映射到不同调用动作的示意图

Go API 错误契约从 HTTP 状态、业务错误类型到请求号诊断字段的分层示意图

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Postman 如何设置请求超时:Settings、请求级配置与响应结果核对Postman 如何设置请求超时:Settings、请求级配置与响应结果核对
上一篇
Postman 如何设置请求超时:Settings、请求级配置与响应结果核对
RAG 检索结果为空怎么办:查询改写、分块粒度与召回验证
下一篇
RAG 检索结果为空怎么办:查询改写、分块粒度与召回验证
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    5274次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    4790次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    4737次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    4995次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    4942次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码