Revel中不存在“公共控制器”语法,所谓公共控制器实为通过包导入共享的app/controllers/下结构体,需嵌入*revel.Controller并用指针继承基类,模块控制器须满足路径规范与显式启用条件。

公共控制器不是独立类型,而是通过继承和包组织实现
Revel 没有 public controller 这种语法或内置概念。所谓“公共控制器”,通常指被多个模块或子路由共用的控制器逻辑,核心实现方式是:把控制器定义在共享包中,再通过 Go 包导入 + Revel 的控制器发现机制自动注册。
常见错误是试图在 app/controllers/ 外新建一个“公共目录”并期望 Revel 自动加载——它不会。Revel 只扫描 app/controllers/ 下的文件(以及已启用模块中的同路径)。
- 把复用逻辑抽成普通 Go 结构体(不嵌入
*revel.Controller),放在app/utils/或app/lib/中,由各控制器按需组合 - 若需统一行为(如鉴权、日志),优先用
revel.Interceptor,而非复制控制器代码 - 跨模块复用时,确保模块自身也包含
app/controllers/目录,且其init()函数调用了revel.InterceptFunc或正确注册了控制器
如何让多个控制器共享基础方法和字段
Revel 控制器本质是 Go struct,所以标准的嵌入(embedding)和接口组合完全适用。关键点在于:所有控制器必须最终嵌入 *revel.Controller,否则框架无法识别和注入上下文。
例如,定义一个基类控制器:
package app
import "github.com/revel/revel"
type BaseController struct {
*revel.Controller
}
func (c *BaseController) RequireLogin() revel.Result {
if c.Session.Get("user_id") == nil {
c.Flash.Error("请先登录")
return c.Redirect("/login")
}
return nil
}
然后在具体控制器中嵌入它:
type AppController struct {
*BaseController
}
func (c AppController) Index() revel.Result {
if result := c.RequireLogin(); result != nil {
return result
}
return c.Render()
}
- 必须用指针嵌入
*BaseController,否则RequireLogin内部访问的c.Controller会是 nil - 不要在
BaseController中定义Init方法——它会被 Revel 跳过,初始化逻辑应放在具体控制器的Init或拦截器中 - 如果基类需要初始化字段(如数据库句柄),应在具体控制器的
Init方法里手动赋值,Revel 不会自动调用嵌入类型的Init
模块中定义的控制器如何被主应用识别
模块里的控制器要生效,必须满足两个硬性条件:模块已启用、模块路径结构合规。Revel 不会扫描任意包路径下的控制器。
启用模块需在 conf/app.conf 中显式声明:
module.auth = github.com/myorg/revel-auth-module
且该模块仓库必须包含标准 Revel 布局,尤其是:
-
app/controllers/目录存在 - 控制器文件中结构体类型名以大写字母开头(如
AuthController) - 控制器方法签名符合
func(c *AuthController) ActionName() revel.Result
常见失败场景:
- 模块路径写错(如漏掉
github.com/前缀),导致go build报import not found - 模块未打 tag 或
go.mod版本不匹配,revel run启动时报cannot find module - 模块中控制器文件名是
auth_controller.go(含下划线),Go 编译器忽略该文件(Go 规范要求小写字母+数字+下划线的文件名会被视为非源码)
为什么直接复用 *revel.Controller 字段会 panic
当你在自定义结构体里保存 *revel.Controller 并试图在方法中调用 c.Render() 或 c.Session.Get() 时,如果该结构体不是由 Revel 实例化(即不是通过框架反射创建的控制器),c.Controller 内部字段(如 c.Request, c.Response, c.Params)全为 nil,调用任何方法都会 panic。
根本原因:Revel 在每次请求时,会为每个控制器实例填充运行时上下文,这个过程不可绕过。
- 不要手动 new 一个控制器实例并传入 request/response —— 这破坏了 Revel 的生命周期管理
- 若需封装 HTTP 工具函数,应接收
*revel.Controller作为参数,而非持有它:func LogRequest(c *revel.Controller) { revel.INFO.Printf("IP: %s", c.Request.RemoteAddr) } - 拦截器(
revel.BEFORE)是安全共享逻辑的首选位置,它天然拥有完整上下文且可全局注册
真正难处理的是跨模块状态传递和控制器间依赖注入——Revel 没有 DI 容器,靠手动传参或全局变量极易失控。这时候与其强行“公共控制器”,不如拆出纯业务 service 层,让控制器只负责粘合。


















