prepare() 是控制器真正入口,每次请求或 websocket 连接时由框架调用,早于动作方法;finish() 在响应写出后执行,不保证一定运行;getcontrollerandaction() 返回路由解析名而非运行时字段值。

Prepare() 是控制器的真正入口,不是构造函数
Beego 控制器的 Prepare() 方法在每次请求到达时被调用,且早于 Get()、Post() 等动作方法。它不是 Go 的结构体初始化函数,也不是 Init() 或 New() —— 即使你手动 new 一个控制器实例,Prepare() 也不会自动执行,必须由框架调度触发。
常见错误现象:
- 在
Prepare()里依赖this.Ctx.Input.Param(":id"),但实际没走路由匹配(比如直调控制器),导致 panic - 把权限校验逻辑写在结构体字段赋值或构造函数中,结果被绕过
- 误以为
Prepare()只对 HTTP 请求有效,忽略了它在 WebSocket 连接建立时也会被调用
正确使用场景:
- 统一做登录态检查:
if this.GetSession("uid") == nil { this.Redirect("/login", 302); return } - 预加载共享数据(如当前用户、租户信息)到
this.Data,供模板复用 - 设置响应头:
this.Ctx.Output.Header("X-Frame-Options", "DENY")
Finish() 在响应写出后执行,不是 defer 替代品
Finish() 是控制器生命周期最后一个钩子,它在 HTTP 响应已写入连接、且所有中间件收尾逻辑完成之后才被调用。它不保证一定能执行(例如 panic 未被捕获、连接提前断开、或调用了 this.StopRun())。
容易踩的坑:
- 在
Finish()里做关键日志记录或审计,结果因 panic 而丢失 - 试图用它清理数据库连接或关闭文件句柄——这些资源应由业务层显式管理,而非绑定到请求生命周期
- 和
defer混用:比如在Get()里defer func(){...}()和Finish()都改了同一个 map,顺序不可控
合理用途:
- 统计耗时:
duration := time.Since(this.StartTime)(需提前在Prepare()记录StartTime) - 异步上报性能指标(不阻塞主流程)
- 清理临时上下文缓存(如
this.Data["tmp_cache"] = nil),避免内存泄漏
GetControllerAndAction() 返回的是解析结果,不是运行时状态
GetControllerAndAction() 返回两个字符串:controllerName 和 actionName,它们来自路由匹配阶段的解析,不是当前控制器实例的反射名称。也就是说,即使你在 Prepare() 中修改了 this.controllerName,GetControllerAndAction() 仍返回原始解析值。
参数差异与影响:
- 路由定义为
beego.Router("/u", &user.ProfileController{}, "get:Show")→ 返回"ProfileController"和"Show" - 若在
Prepare()中执行this.controllerName = "Custom"→GetControllerAndAction()不变,但this.controllerName字段已变 - 反向路由
urlfor依赖的是注册时的控制器类型名,而非this.controllerName字段
典型使用场景:
- 通用中间件记录访问路径:
log.Printf("%s.%s: %s", controllerName, actionName, this.Ctx.Request.URL.Path) - 权限白名单控制:
if !allowedActions[controllerName+"."+actionName] { ... } - 调试时快速定位当前执行点,比
runtime.Caller更轻量
手动调用控制器方法必须补全上下文链
绕过路由直接调用控制器方法(如单元测试、后台任务)时,不能只 new 结构体再 call 方法。Beego 的 Get() 等方法隐式依赖 this.Ctx、this.Data、this.TplName 等字段,而这些字段由框架在路由分发时注入。
最小可行做法:
- 用
beego.BeeApp.Handlers获取注册的 handler 映射(仅限已注册控制器) - 手动构造
*context.Context+http.Request+http.ResponseWriter - 调用
controller.Init(),传入 context、controller name、action name、app config - 再调用
controller.Prepare()和目标方法(如controller.Get())
关键注意点:
-
controller.Init()必须在Prepare()之前调用,否则this.Ctx为 nil - 模板渲染(
this.TplName)在直调时无效,除非你额外挂载模板引擎 - 中间件(如
InsertFilter)不会自动触发,需手动模拟
最简示例片段:
req, _ := http.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
ctx := &context.Context{Request: req, ResponseWriter: w}
c := &controllers.UserController{}
c.Init(ctx, "UserController", "Get", beego.AppConfig)
c.Prepare()
c.Get()
复杂点在于上下文完整性和副作用隔离——比如 session、cookie、CSRF token 都是绑定在原始 Ctx 上的,直调时若复用真实 context,可能污染其他请求。











