直接嵌入beego.controller不够用,因需重复编写权限校验、日志等逻辑且无法自动注入用户/请求id/db等上下文;应定义公开的basecontroller基类匿名嵌入web.controller,覆盖prepare实现统一处理,业务控制器须继承该基类(如type usercontroller struct{basecontroller}),并确保包名、结构体名、方法名符合autorouter反射规则。

为什么直接嵌入 beego.Controller 不够用
因为每个控制器都要重复写权限校验、日志记录、统一错误包装、跨域头设置等逻辑,硬编码到每个 Get/Post 方法里会导致大量复制粘贴。Beego 的 beego.Controller 本身不提供可继承的“业务基类”抽象层,它的 Prepare 是钩子,但无法自动注入业务上下文(比如当前用户、请求ID、DB事务句柄)。
如何定义可复用的控制器基类
必须满足 Beego 的反射注册规则:新基类仍需匿名嵌入 beego.Controller,且所有业务控制器必须继承该基类(而非直接嵌入 beego.Controller)。关键点在于结构体嵌入顺序和方法可见性:
- 基类结构体必须是公开的(首字母大写),例如
BaseController - 基类中定义的
Prepare方法会被 Beego 自动调用,适合放鉴权、日志、DB 初始化 - 业务控制器必须用
type UserController struct { BaseController }形式,不能跳过嵌入 - 不要重写
Init;Prepare是唯一推荐覆盖的生命周期方法
示例:
package controllers
import (
"github.com/beego/beego/v2/server/web"
)
type BaseController struct {
web.Controller
}
func (c *BaseController) Prepare() {
// 统一添加 X-Request-ID
c.Ctx.ResponseWriter.Header().Set("X-Request-ID", c.Ctx.Input.Header("X-Request-ID"))
// 检查登录态(假设从 cookie 解析 user_id)
if uid := c.Ctx.GetCookie("user_id"); uid == "" {
c.Abort("401")
return
}
// 注入到 Data,供模板使用
c.Data["CurrentUser"] = uid
}
路由识别失败的三个常见原因
即使基类写对了,自定义控制器仍可能被 AutoRouter 忽略——这不是代码问题,而是 Beego 的反射约束导致的:
-
controllers/目录下文件的包名必须是controllers,不能是main或其他 - 业务控制器结构体名必须首字母大写(如
UserController),小写(userController)不会被扫描 - 方法名必须严格为
Get、Post、Delete等 HTTP 动词开头,且首字母大写;get或HandleGet均无效 - 如果手动注册路由(如
web.Router("/user", &controllers.UserController{})),控制器类型必须已初始化(不能是 nil 指针)
在基类中安全传递 DB 实例或上下文
Beego 的 Controller 生命周期短,每次请求新建实例,所以不能在基类字段里缓存全局 DB 句柄,但可以按需初始化并挂载到 c.Data 或自定义字段:
- 避免在
BaseController结构体里声明db *sql.DB字段(会随实例销毁,无意义) - 推荐在
Prepare中调用orm.NewOrm()并存入c.Data["Orm"],模板或方法内用c.Data["Orm"].(orm.Ormer)取出 - 若需事务,应在
Prepare开启并在Finish钩子中提交/回滚(注意Finish不保证执行,异常时可能跳过) - 更稳妥的做法是:在具体方法里按需获取 ORM,用
defer关闭,不依赖基类字段
真正容易被忽略的是:Beego 的 AutoRouter 不会为基类生成路由,只认 controllers/ 下的“最终类型”。哪怕你把 BaseController 放进该目录,它也不会被注册——它只是个中间结构体,不是控制器终点。











