Prepare() 是控制器真正入口,Finish() 不是 defer 替代品;Prepare() 每次请求由框架强制调用以保障权限校验等预处理,Finish() 执行不可靠,仅适合非关键收尾。

Prepare() 是控制器真正入口,Finish() 不是 defer 替代品——这两个方法的执行时机、保障程度和适用场景完全不同,混用会导致权限漏校验或资源泄露。
Prepare() 为什么必须在每次请求时调用,而不是结构体初始化时
Beego 的控制器实例是每次 HTTP 请求(或 WebSocket 连接)新建的,Prepare() 由框架在路由匹配成功后、动作方法(如 Get()、Post())之前强制调用。它不是 Go 的构造函数,也不在 new(UserController) 时触发。
- 常见错误现象:
this.Ctx.Input.Param(":id")在Prepare()中 panic,因为该参数依赖路由匹配,而直调控制器(如单元测试中手动 new 后直接调Get())会跳过路由解析 - 正确使用场景:统一登录检查 ——
if this.GetSession("uid") == nil { this.Redirect("/login", 302); return } - 注意:WebSocket 连接建立时也会触发
Prepare(),别只写 HTTP 条件判断 - 性能影响:所有逻辑都在请求链路早期执行,适合轻量预处理;避免在其中做 DB 查询或远程调用,否则阻塞整个请求
Finish() 执行不可靠,不能用来保底清理关键资源
Finish() 确实在响应写出之后运行,但它不保证一定执行。一旦发生未捕获 panic、连接提前关闭、或显式调用了 this.StopRun(),Finish() 就会被跳过。
- 容易踩的坑:把审计日志、事务提交、文件句柄关闭写在
Finish()里,结果线上偶发丢失 - 和
defer混用危险:比如在Get()中defer func() { m["done"] = true }(),又在Finish()改同一 map,执行顺序不确定 - 正确做法:DB 连接、缓存 client、HTTP 客户端等应由业务层显式管理生命周期;
Finish()只适合非关键收尾,比如打点统计:beego.Trace("user_get_duration", time.Since(start))
Prepare() 和 Finish() 的典型组合陷阱
有人想用 Prepare() 开启计时器、Finish() 打印耗时,看起来合理,但实际有隐患。
- 如果中间件或动作方法 panic,
Finish()不执行,计时器没回收(虽是临时变量,但若存了 channel 或 goroutine 就泄漏) -
Prepare()里设的this.Data["start_time"] = time.Now(),在Finish()里读不到——因为this.Data是请求级上下文,但Finish()的执行环境可能已部分销毁 - 更稳妥的做法:用中间件(如
BeforeExec/AfterExec过滤器)替代,它们由框架统一调度,保障性更高
真正要注意的是:Prepare() 是你控制请求入口的唯一可靠钩子,Finish() 是个“尽力而为”的尾巴——别把它当保险丝用,也别在它里面做任何不可逆操作。


















