应封装 writejson 函数统一处理 content-type、状态码和 json 编码:先设 header,再 writeheader,最后用 json.newencoder.encode 安全输出;配合 success/error 函数区分响应类型,并通过业务码映射 http 状态码。

直接用 http.ResponseWriter 写响应不难,但统一格式、状态码、错误处理和 Content-Type 很容易散落在各处——最终导致前端解析失败、日志难以追踪、错误返回不一致。
为什么不能直接用 w.Write() 返回 JSON?
手动拼 JSON 字符串或用 json.Marshal 后调用 w.Write() 会漏掉关键步骤:
- 没显式设置
Content-Type: application/json; charset=utf-8,某些浏览器或客户端可能解析为文本 - 没统一处理 HTTP 状态码,比如业务错误该返回 400 还是 500 容易混淆
- 没封装错误结构,
err.Error()直接暴露给前端可能泄露敏感信息 - 没做
defer w.Header().Set("Content-Type", ...)之类的预设,每次都要重复写
WriteJSON 封装函数必须包含的三件事
一个最小可用的统一封装函数,核心就三点:
- 先调用
w.Header().Set("Content-Type", "application/json; charset=utf-8") - 再调用
w.WriteHeader(statusCode)(不能省!否则默认 200) - 最后用
json.NewEncoder(w).Encode(v),比json.Marshal+w.Write更安全(自动处理流式写入、避免内存拷贝)
示例:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func WriteJSON(w http.ResponseWriter, statusCode int, v interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(statusCode)
if err := json.NewEncoder(w).Encode(v); err != nil {
// 此时已.WriteHeader,不能再改状态码,只能记录日志
log.Printf("json encode error: %v", err)
}
}
如何区分成功响应与错误响应?
别在 handler 里反复判断 if err != nil 后手写 WriteJSON(w, 400, map[string]string{"error": err.Error()})。推荐定义两个固定结构:
- 成功响应:用
type Response struct { Code int `json:"code"` Data interface{} `json:"data"` Message string `json:"message"` } - 错误响应:用
type ErrorResponse struct { Code int `json:"code"` Message string `json:"message"` },且Code映射到 HTTP 状态码(如 40001 → 400) - 提供
Success(w, data)和Error(w, code, msg)两个函数,内部自动调用WriteJSON并传对应状态码
注意:ErrorResponse 的 Code 字段是业务码,不是 HTTP 状态码;HTTP 状态码应由函数内部根据业务码查表映射(例如 400xx → 400,500xx → 500),避免前端误把业务码当 HTTP 码处理。
容易被忽略的边界情况
封装看似简单,但以下几点常被跳过:
-
http.ResponseWriter不是线程安全的,别在 goroutine 里直接传给封装函数(除非你确保只读或已加锁) - 如果 handler 中提前调用了
w.WriteHeader(200),后续再调WriteJSON(w, 400, ...)就失效了——HTTP 头已发,状态码无法更改 - 不要在封装函数里做
log或metrics,那属于中间件职责;封装只管「写什么」,不管「记什么」 - 如果响应体很大(比如导出 CSV),
json.NewEncoder仍适用,但要注意客户端是否支持流式接收;此时更应关注Flush()而非封装逻辑
真正麻烦的从来不是怎么写 JSON,而是谁来决定状态码、谁来过滤敏感字段、谁来统一添加 trace-id ——这些得靠封装层之上再叠一层 middleware 或 context 拦截,而不是塞进 WriteJSON 里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










