Chapter 04

响应

掌握 JSON/XML/String/HTML 各类响应、状态码语义、重定向、SSE 流式输出,并封装项目统一响应结构。

4.1 响应相关名词

ResponseWriter
标准库写响应的接口,Gin 在其上封装了状态码追踪、字节计数、Content-Type 设置等。
c.JSON / c.XML / c.String
Gin 提供的渲染方法,自动设置 Content-Type 并序列化数据后写回。
状态码(HTTP Status Code)
2xx 成功、3xx 重定向、4xx 客户端错误、5xx 服务端错误。用 net/http 常量(如 http.StatusOK)保持可读。
gin.H
map[string]any 别名,构造临时 JSON 对象最方便。
c.Redirect
返回 3xx 状态并设置 Location 头,让浏览器跳转。
SSE(Server-Sent Events)
基于 HTTP 的单向流式推送,Content-Type 为 text/event-stream,适合实时日志、AI 逐字输出。
统一响应结构
约定所有接口返回同一外层格式(code/message/data),前端解析一致,是工程规范。

4.2 各类渲染

// JSON(最常用)
c.JSON(http.StatusOK, gin.H{"user": "alice"})

// 缩进美化的 JSON(调试用,生产别开,浪费带宽)
c.IndentedJSON(200, obj)

// 防 JSON 劫持前缀 while(1);
c.SecureJSON(200, arr)

// 纯文本 / 格式化
c.String(200, "hello %s", name)

// XML
c.XML(200, gin.H{"code": 0})

// YAML / ProtoBuf 也支持
c.YAML(200, obj)

// 原始字节 + 自定义 Content-Type
c.Data(200, "image/png", pngBytes)

4.3 状态码语义

场景状态码常量
查询成功200http.StatusOK
创建成功201http.StatusCreated
成功无返回体204http.StatusNoContent
参数错误400http.StatusBadRequest
未认证401http.StatusUnauthorized
无权限403http.StatusForbidden
资源不存在404http.StatusNotFound
服务端错误500http.StatusInternalServerError

4.4 重定向

// 外部/内部跳转(302)
c.Redirect(http.StatusFound, "https://gufacode.com")

// 永久重定向(301)
c.Redirect(http.StatusMovedPermanently, "/new-path")

// 内部路由转发(不改 URL,重新进入路由)
c.Request.URL.Path = "/v2/users"
r.HandleContext(c)

4.5 SSE 流式响应

func stream(c *gin.Context) {
    c.Header("Content-Type", "text/event-stream")
    c.Header("Cache-Control", "no-cache")

    tokens := []string{"古", "法", "编", "程"}
    c.Stream(func(w io.Writer) bool {
        if len(tokens) == 0 {
            return false // 返回 false 结束流
        }
        c.SSEvent("message", tokens[0])
        tokens = tokens[1:]
        time.Sleep(300 * time.Millisecond)
        return true // 返回 true 继续
    })
}
流式适合 AI 场景

逐 token 输出大模型回复、实时日志、进度条都用 SSE。注意关闭中间缓冲(如 Nginx 的 proxy_buffering off),否则客户端会一次性收到全部内容。

4.6 统一响应结构封装

真实项目里,让所有接口返回一致外层格式,前端更好处理:

// pkg/resp/resp.go
package resp

import "github.com/gin-gonic/gin"

type Response struct {
    Code int    `json:"code"`          // 业务码,0 表示成功
    Msg  string `json:"message"`       // 提示信息
    Data any    `json:"data,omitempty"` // 业务数据
}

func OK(c *gin.Context, data any) {
    c.JSON(200, Response{Code: 0, Msg: "success", Data: data})
}

func Fail(c *gin.Context, httpStatus, code int, msg string) {
    c.JSON(httpStatus, Response{Code: code, Msg: msg})
}
// handler 里干净利落
func getUser(c *gin.Context) {
    u, err := svc.FindUser(c.Param("id"))
    if err != nil {
        resp.Fail(c, 404, 10001, "用户不存在")
        return
    }
    resp.OK(c, u)
}
// 成功: {"code":0,"message":"success","data":{...}}
// 失败: {"code":10001,"message":"用户不存在"}
HTTP 码 vs 业务码

两者可并存:HTTP 状态码给网关/缓存/监控用(是否成功、要不要重试);业务 code 给前端做细粒度分支。也有团队坚持「HTTP 200 + 业务码」以简化前端,取舍看团队约定。

4.7 响应最佳实践小结