【直播切片工作台】第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 还是省略?如何保证前端拿到 \[\]?

项目信息

相关推荐
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】ChatServer 路由规则绑定
网络·c++·人工智能·学习·ai·架构
二川bro1 小时前
AI可编辑PPT产品线技术方案调研与落地思考
人工智能
KKKlucifer1 小时前
数据安全管控平台运营化关键技术:从策略下发到闭环治理
大数据·人工智能·产品运营
我喜欢就喜欢1 小时前
DeepSeek 大模型多场景落地应用指南
人工智能·系统架构
火山引擎和TA的超级拍档们1 小时前
产品情报局 06 | 短剧 Agent:多 Agent 协同,重塑 AI 短剧工业化生产流程
人工智能
可涵不会debug1 小时前
机器人行业的下一道门槛,不是更大模型,而是更短的数据闭环
人工智能·机器人
隔窗听雨眠1 小时前
小模型大分工:小语言模型如何重塑RAG系统的架构逻辑
人工智能·语言模型·架构
马剑威(威哥爱编程)1 小时前
【HarmonyOS 7 小艺智能体实战】02:动工前的清单——账号、资质、工具链一次备齐
人工智能·华为·harmonyos