Revel中必须使用RenderJson而非裸写c.Response.Write,因响应生命周期由revel.Result控制,直接操作Response会绕过中间件、日志、错误处理等机制,导致RenderError失效、traceId断链、CORS不生效。

Revel 中必须用 RenderJson,不能裸写 c.Response.Write
Revel 的响应生命周期由 revel.Result 控制,所有返回值必须是其实现类型。直接操作 c.Response.Write 或手动设置 Content-Type + WriteHeader 会绕过框架的中间件、日志、错误处理等机制,导致 RenderError 失效、traceId 断链、CORS 不生效。你看到的 “返回空白” 或 “状态码始终是 200”,往往就是这个原因。
✅ 正确路径只有一条:RenderJson —— 它自动:设置 Content-Type: application/json、调用 json.Marshal、写入响应体、返回 revel.Result 实例。
- 它不关心你传的是
struct、map[string]interface{}还是[]int,只要能被标准库序列化就行 - 若需格式化输出(如调试),在
conf/app.conf中设results.pretty = true - 不要在
RenderJson前调用c.Response.WriteHeader,它内部已处理 HTTP 状态码
统一响应结构体必须导出字段并带 json 标签
裸用 map[string]interface{}(比如 RenderJson(map[string]interface{}{"code": 0, "msg": "ok"}))看似快,但会导致三个实际问题:
- 字段名拼写不一致:今天写
success,明天写result,前端无法写通用拦截器 - 空值失控:
nil指针、零值time.Time全部原样输出,前端可能期望忽略或转为空字符串 - 无法复用逻辑:每个 handler 都要重复写
if err != nil { return c.RenderJSON(...) }
✅ 推荐定义一个导出结构体:
type ApiResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
func Success(data interface{}) *ApiResponse {
return &ApiResponse{Code: 0, Msg: "success", Data: data}
}
func Fail(code int, msg string) *ApiResponse {
return &ApiResponse{Code: code, Msg: msg, Data: nil}
}
用法:return c.RenderJson(Success(user)) 或 return c.RenderJson(Fail(4001, "参数缺失"))。
HTTP 状态码和业务 code 必须分开控制
很多人误以为 RenderJson 返回的 JSON 里有 "code": 500 就等于 HTTP 500,这是错的。浏览器 Network 面板显示的状态码,只取决于 c.Response.Status,跟 JSON body 无关。
- ✅ 成功时:保持默认 200,用
Success()包装数据 - ✅ 客户端错误(如参数校验失败):显式设
c.Response.Status = http.StatusBadRequest,再RenderJson(Fail(4001, "...")) - ✅ 服务端错误:设
c.Response.Status = http.StatusInternalServerError,再RenderJson(Fail(5000, "..."))
注意:不要把 http.StatusBadRequest 和业务码 4001 混为一谈。前者决定网络层行为(如 Axios 的 error.response.status),后者供前端 UI 展示或做分支判断。
别在同一个请求里混用 RenderJson 和 RenderTemplate
Revel 的 Render 和 RenderJson 是互斥的响应策略。一旦你调用了 RenderJson,就不要再调用 Render 或 RenderTemplate,反之亦然。框架不会报错,但后续调用会被静默忽略,且可能引发 http: multiple response.WriteHeader calls 错误。
常见陷阱:
- 在 if/else 分支中,一个走
RenderJson,另一个走Render(比如渲染登录页) - 封装了通用错误处理函数,里面调了
RenderJson,但外层又写了return c.RenderJson(...) - 使用
revel.Interceptor注入公共逻辑,拦截器里提前写了RenderJson,控制器又写了一次
解决方案:所有 API 接口统一走 RenderJson;页面类接口(如后台管理首页)统一走 Render。两者路由层级最好物理隔离(如 /api/xxx vs /admin/xxx)。


















