beego的filter即中间件,需用beego.insertfilter注册,执行时机必须选对:权限/日志逻辑应放beforeexec(路由已匹配、参数已解析、session已初始化),而非beforerouter(此时参数和body均不可用)。

Beego 的“中间件”就是 Filter,没有额外抽象层,直接用 beego.InsertFilter 注册函数即可生效。它不依赖第三方库,也不需要包装 handler,但执行时机、参数解析顺序和 session 初始化时机必须对齐,否则会拿不到 ctx.Input.Param、读不到 body 或校验失效。
Filter 执行时机选错会导致参数为空或 session 未初始化
Beego 的 Filter 可在四个生命周期点插入:BeforeRouter、BeforeExec、AfterExec、FinishRouter。多数权限/日志逻辑应放在 BeforeExec,而不是 BeforeRouter:
-
BeforeRouter阶段:路由尚未匹配,ctx.Input.Param(":id")为空,ctx.Input.RequestBody也未被解析(body 还没读) -
BeforeExec阶段:路由已匹配、参数已解析、session 已初始化(前提是没在AfterStatic之前注册),此时能安全取ctx.Input.Session("uid")和ctx.Input.Param - 若你用了
beego.InsertFilter("/*", beego.BeforeRouter, f)却发现ctx.Input.Session返回 nil,大概率是 session 初始化还没开始——改用beego.BeforeExec就行
Filter 函数里怎么安全读取请求参数和 body
Filter 函数签名是 func(*context.Context),但 Beego 默认只在 Controller 的 Prepare() 或具体方法(如 Get())中自动解析 body 和表单。Filter 中要手动触发:
- 读 JSON body:先调用
ctx.Input.Bind(nil, "")或ctx.Input.RequestBody(注意:该字段只在第一次读时有效,后续再读是空) - 读表单字段:用
ctx.Input.Query("key")(URL 参数)或ctx.Input.Post("key")(POST 表单),但后者依赖ctx.Input.ParseForm()已执行——它通常在BeforeExec后才调用,所以更稳妥的方式是在BeforeExec里显式调一次ctx.Input.ParseForm() - 避免重复读 body:如果 Filter 里已调
ctx.Input.RequestBody,Controller 里再读就会失败;建议只在 Filter 中提取必要字段(如 token、灰度 tag),存到ctx.Input.Data["xxx"]供 Controller 复用
Filter 中做权限校验的三个硬性前提
RBAC 权限判断不是写个 if 就完事,必须满足以下三点,否则会出现“明明有权限却跳登录页”或“越权访问成功”:
- 路径标准化:用
strings.TrimSuffix(ctx.Request.URL.Path, "/")统一去除末尾斜杠,否则/user和/user/被视为不同资源 - Method 大写化:
strings.ToUpper(ctx.Request.Method),因为权限配置通常按GET/POST区分,而 HTTP 方法大小写不敏感但字符串匹配严格 - resource:action 映射准确:比如
POST /api/v1/users→user:create,GET /api/v1/users/:id→user:read;不能直接拿完整 URL 做 key,否则接口一变权限就断
Filter 注册后不生效?检查 skip 参数和 pattern 匹配
beego.InsertFilter(pattern, position, filter, skip) 的 skip 参数默认为 false,表示“只要响应已有内容(比如已调 ctx.Abort() 或 ctx.Redirect()),后续 Filter 就跳过”。这在登录拦截中很关键:
- 如果你的 Filter 里写了
ctx.Redirect(302, "/login")但页面没跳转,可能是skip = true导致后续 Filter(比如日志)终止了,但跳转本身仍生效;更可能是你没调ctx.StopRun(),导致跳转后 Controller 仍继续执行 -
pattern支持通配符和正则,但注意:"*"不匹配任何路径,要用"/*";"/user/:id([0-9]+)"这种写法只在 Beego 内置路由支持,Filter 自身不解析命名参数,它只是字符串匹配,所以实际生效的是路径前缀匹配,不是结构化路由匹配 - 调试技巧:在 Filter 开头加
beego.Debug("filter hit:", ctx.Request.RequestURI),确认是否真的被触发
Filter 的本质是函数指针注册 + 固定生命周期钩子,没有魔法,但 Beego 的路由解析和上下文初始化是分阶段的。最容易忽略的,是以为“注册了就能拿到所有数据”,结果在 BeforeRouter 里拼命 cast session,却忘了 session 根本还没加载。











