Beego中间件无法直接修改c.Data["json"],因其属于controller实例字段且中间件仅持有*context.Context;统一响应应通过基类controller的Finish()方法包装c.Data["json"],并配合路径级过滤器/beego.InsertFilter("/api/", ...)限定作用范围。

中间件里不能直接改 c.Data["json"] 的值
很多人想在中间件里统一塞 c.Data["json"] = map[string]interface{}{"code": 0, "data": ...},结果发现没用——因为 c.Data 是 controller 实例的字段,而中间件接收的是 *context.Context,它压根不包含 Data 字段。Beego 的 c.Data 只在 controller 的生命周期中存在,中间件拿不到、也改不了。
真正能统一控制响应体的地方,是拦截 ctx.ResponseWriter 或在 controller 返回后做“包装”。但 Beego 没有类似 Express 的 res.json() 统一出口,所以得换思路:
- 所有 API controller 必须显式调用
c.ServeJSON()(或c.ServeXML()),这是唯一触发 JSON 序列化的入口 - 中间件无法覆盖响应体,但可以在
c.ServeJSON()执行前,把原始数据暂存到ctx.Input.Data(一个 map[string]interface{})中,再由 controller 主动读取并包装 - 更稳妥的做法:不依赖中间件改响应,而是封装一个基类 controller,让所有 API controller 继承它,并在基类的
ServeResult()方法里统一格式
用 beego.InsertFilter 注册路径级中间件,只处理 /api/ 开头请求
统一响应格式通常只针对 API 接口,不该影响静态资源或页面渲染。必须避免全局注册,否则会干扰 /static/、/favicon.ico 等路径。
正确注册方式(放在 main.go 的 init() 或 main() 开头,且在 beego.Run() 之前):
beego.InsertFilter("/api/", beego.BeeApp.FilterType, apiResponseWrapper)
注意结尾斜杠:/api/ 匹配 /api/v1/users 和 /api/login,但不会误伤 /apixxx;写成 /api 则可能匹配到 /api123。
这个过滤点(beego.BeeApp.FilterType)会在路由匹配后、controller 初始化前执行,适合注入上下文或预处理,但仍无法访问 controller 的 Data 字段。
中间件内如何安全地读取和包装原始响应数据
Beego 不提供 response body hook,所以没法像 Gin 那样用 ResponseWriter 包装器截获输出。可行路径只有两个:
- 要求所有 API controller 把业务数据先存进
c.Ctx.Input.Data["raw"] = yourStruct,然后在中间件末尾检查该 key 是否存在,存在就调用c.ServeJSON()并传入包装结构体——但这需要 controller 主动配合,且c.ServeJSON()不能在中间件里调(会 panic) - 更实际的做法:中间件只做「标记」和「预处理」,比如设置
c.Ctx.Input.Data["shouldWrap"] = true,然后在每个 controller 的Finish()方法里判断并统一包装(Finish()在 controller 方法返回后、响应写出前执行) - 如果 controller 已用
c.Data["json"],可在Finish()中读取它、清空、再重新赋值为包装后结构体,例如:c.Data["json"] = map[string]interface{}{"code": 0, "msg": "ok", "data": c.Data["json"]}
示例 Finish() 写法:
func (c *BaseController) Finish() {
if v, ok := c.Data["json"]; ok {
c.Data["json"] = map[string]interface{}{
"code": 0,
"msg": "success",
"data": v,
}
}
}
别忽略 Content-Type 和状态码的副作用
统一格式中间件容易踩的坑不止数据结构,还有 HTTP 头和状态码:
-
c.ServeJSON()默认设Content-Type: application/json; charset=utf-8,但如果中间件里手动ctx.ResponseWriter.WriteHeader(400)后又让 controller 调c.ServeJSON(),最终状态码可能是 200(因为ServeJSON()会重置状态码) - 错误响应(如参数校验失败)常需返回
400或401,这时不能只改Data["json"],还得在 controller 里显式调c.Abort("400")或c.Ctx.ResponseWriter.WriteHeader(400) - 如果用了
beego.BConfig.CopyRequestBody = true(爬虫上报场景常见),中间件里读ctx.Input.RequestBody不会影响后续 controller,但若中间件提前解析并修改了 body,controller 再解析就会失败
最稳的组合:基类 controller + Finish() 包装 + 所有错误走 c.Abort(),而不是靠中间件兜底。


















