Revel中请求上下文是每次请求新建的*revel.Controller实例,应通过自定义App基类字段+BEFORE拦截器注入服务,而非全局变量;c.Params仅适合存字符串值,存对象必须用预定义字段。

Revel 中的请求上下文本质是 c 对象,不是全局变量
Revel 的控制器方法签名形如 func (c App) Index(),其中 c 是 *revel.Controller 实例,它在每次 HTTP 请求进入时被新建,天然携带请求级生命周期。所谓“注入全局共享数据”,实际是指把跨控制器复用的服务(如数据库连接、配置对象、认证信息)安全地挂载到这个 c 上,而非污染全局作用域或滥用 init() 单例。
用 revel.InterceptFunc 在请求开始时注入数据
Revel 提供了 filter 机制,在请求进入控制器前统一处理。最常用的是 revel.BEFORE 阶段,配合 InterceptFunc 把准备好的实例塞进 c 的字段或 c.Params:
- 不能直接往
c添加未声明字段(Go 不支持),需提前在自定义 Controller 基类中定义字段,例如:type App struct { *revel.Controller DB *gorm.DB Config *Config } - 在
app/init.go中注册拦截器:revel.InterceptFunc(func(c *revel.Controller, fc revel.FilterConfig) revel.Result { // 假设 DBMain 已在 init() 中初始化好 c.App.DB = DBMain c.App.Config = globalConfig return nil }, revel.BEFORE) - 注意顺序:拦截器必须在
revel.Run()前注册,且确保DBMain等依赖已初始化完成,否则会 panic
c.Params.Set() 可临时存取键值对,但不适用于复杂对象
c.Params 是 Revel 内置的参数容器,支持 Set(key, value) 和 Get(key)。它适合存字符串、数字等基本类型,比如请求 ID、用户角色标识:
-
c.Params.Set("request_id", uuid.New().String())后可在任意后续 controller 方法里用c.Params.Get("request_id")取出 - 但不要用它存
*gorm.DB或结构体指针——c.Params内部用map[string]string实现,存入非字符串值会触发强制转换,导致数据损坏或 panic - 若真要存对象,必须用自定义 Controller 字段(见上一条),这是唯一安全方式
避免踩坑:别碰 revel.AppController 或全局变量
有人试图在 revel.AppController(框架内置基类)上加字段,或直接声明包级 var DB *gorm.DB,这会导致严重问题:
-
revel.AppController是框架内部类型,修改它会破坏升级兼容性,且字段无法被你的App类型继承 - 全局
DB变量在并发请求下可能被意外覆盖,尤其当你在 filter 中动态切换分片库时——GORM 连接池虽线程安全,但*gorm.DB实例本身不是请求隔离的 - 真正需要“每请求一库”的场景(如多租户分库),必须结合 Revel 的 filter + 自定义 Controller 字段 + GORM 的
Session(&sessionOpts)或Scopes()显式传参,而不是靠全局变量“猜”该用哪个库
最易被忽略的一点:Revel 的 c 对象在 filter 链和 controller 方法间是同一个实例,但它的字段(如自定义的 DB)必须在 BEFORE 阶段就完成赋值;如果在某个 controller 方法里才去初始化,下游 controller 就拿不到,而你很难立刻发现——因为 Go 不报错,只是得到 nil 指针 panic。


















