【直播切片工作台】第16章:统一响应封装

第16章:统一响应封装

本章分析 internal/pkg/response------只有 89 行却定义了全 API 的对外契约:{code, message, data} 信封、语义化错误构造函数、分页结构。前端 src/services/http.ts 与 businessCodes.ts 逐字段对齐这个信封(第 48 章),它是前后端协作的宪法。

流程

所有 handler 的响应出口都收敛到 response 包。成功路径:service 返回业务数据 → handler 调 response.Success(c, data) → 输出 {"code":0,"message":"success","data":...}。失败路径:handler 判定错误类别 → 调对应语义函数(BadRequest/NotFound/Conflict/Unauthorized/Forbidden/InternalError)→ 同时设置 HTTP 状态码与业务 code。

信封里存在两层编码:HTTP status (网关、缓存、通用客户端关心)与 body.code (前端业务逻辑关心)。规则很朴素:400/401/403/404/409/500 各自对应同名函数,业务 code 与 HTTP 码取相同数值(Fail(c, http.StatusBadRequest, 400, msg)),唯一的例外是 Conflict------409 的 HTTP 状态可携带自定义业务 code(默认 409),用于「重复创建」「状态冲突」等需要前端精确分支的场景(FailWithData 还能在失败时附 data,如提示哪条记录冲突)。

分页统一为 PageData{list, total, page, page_size},所有列表接口(素材、项目、任务、提示词)共用,前端表格组件因此只需一种分页模型。

实现

设计上的几个要点:

  • Data interface{} + omitempty:成功必有 data(可为 null 表示无返回体),失败时省略 data 字段而不是返回 null------前端判空逻辑统一。
  • code=0 表示成功 :与「0 是零值」的直觉一致,前端 code !== 0 一条判断即可分流;错误码目前与 HTTP 码同值,未来分化业务码(如 1xxxx 系列)不需要改信封结构。
  • 函数语义化而非参数化 :handler 里写 response.BadRequest(c, "名称不能为空") 而非 response.Fail(c, 400, 400, "...")------错误类别在调用点即读即懂,防止随手传 500 掩盖真实错误类别。
  • Message 全部中文业务描述:错误信息直接面向最终用户(前端 toast 直接展示原文),因此书写规范是「可展示的中文短句」而非英文技术串。这与很多「错误码+前端查表翻译」的方案不同,选择了零翻译直通------简单,代价是国际化需要时全量改造。
  • 包零依赖 (只 import gin 与 net/http):可被 middleware、handler、任何层引用而不引入循环依赖;response_test.go 用 httptest 验证 JSON 序列化形态。

值得注意的是鉴权失败路径:middleware/auth.go 里 response.Unauthorized(c, "Token 无效或已过期") + c.Abort()------中间件层同样走统一信封,前端拦截器因此能用同一套逻辑识别会话过期(第 48 章的 isSessionExpiredCode 恰好检查 code 401 / 12010)。

📌 设计决策

  • 双层编码(HTTP + body code)而非仅 HTTP:浏览器/代理只认 HTTP;前端业务分支需要稳定语义码,两者各司其职。
  • 中文 message 直通 UI:省一张错误码翻译表;适合单语产品,多语言需引入 message key。
  • 分页三件套(page/page_size/total)不带排序参数:排序固定在仓储层(如任务固定 created_at DESC),减少 API 面积。

代码示例

信封与成功/失败构造的完整形态:

go 复制代码
// internal/pkg/response/response.go(节选)
// Body 统一响应结构体。
type Body struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

func Success(c *gin.Context, data interface{}) {
    c.JSON(http.StatusOK, Body{Code: 0, Message: "success", Data: data})
}

func Fail(c *gin.Context, httpStatus, code int, message string) {
    c.JSON(httpStatus, Body{Code: code, Message: message})
}

// Conflict 返回 409 冲突错误(业务 code 可自定义)。
func Conflict(c *gin.Context, code int, message string, data interface{}) {
    FailWithData(c, http.StatusConflict, code, message, data)
}

分页结构与典型 handler 用法:

go 复制代码
// PageData 分页数据结构。
type PageData struct {
    List     interface{} `json:"list"`
    Total    int64       `json:"total"`
    Page     int         `json:"page"`
    PageSize int         `json:"page_size"`
}

// handler 中的典型收尾(示意)
items, total, err := svc.List(ctx, filter, page, pageSize)
if err != nil {
    response.InternalError(c, "查询任务列表失败")
    return
}
response.Success(c, response.PageData{
    List: items, Total: total, Page: page, PageSize: pageSize,
})

小结

  • {code, message, data} 信封 + 语义化错误函数 + PageData 分页 = API 对外契约三件套。
  • HTTP 码与业务码同值、code=0 成功、中文 message 直通前端 toast。
  • 中间件与 handler 共用信封,前端会话过期识别因此统一。

思考题

  1. 若引入错误码分级(参数错 4xxxx / 业务冲突 5xxxx),Confict 的自定义 code 通道应如何扩展成通用机制?
  2. Data 的 omitempty 在 Go 空切片时会把 data 序列化成 null 还是省略?如何保证前端拿到 \[\]?

项目信息

相关推荐
QXWZ_IA12 分钟前
测绘单位采购RTK:设备价格、账号年费、售后,用时间轴分权重
网络·人工智能·安全
kaixin_啊啊42 分钟前
【零基础学AI】第 29 章——第一次调用大模型 API
人工智能
甲维斯44 分钟前
Qwen4 被嘲笑了!国模被啥锁死了?
人工智能
夕阳_醉了1 小时前
Codex 客户端从装好到跑通:第三方 API 接入与配置避坑指南
人工智能·gpt·openai·codex·ai 编程
熟的生菜1 小时前
从零造一个腱绳驱动灵巧手——仿真器篇5:姿势串与手势工程——从文本协议到握拳、OK 与多姿势插值
人工智能
武子康2 小时前
攻击动作做对了,游戏规则就对了吗?H3-World 与三条控制路线的分工
大数据·人工智能·agent
探求虚无2 小时前
用 AI 在 Unity 里做一款 RTS:最难的不是生成代码,而是证明它真的能玩
人工智能·unity·游戏引擎
vivo互联网技术2 小时前
AI Agent 技术全景解析 —— 从提示词工程到高级智能体构建
人工智能·agent
武子康2 小时前
H3-World 的动作能按时发生,为何还不能据此判断实时互动?
人工智能·深度学习·agent