beego中防重token需手动在controller生成并存redis:get方法用uuid.newv4().string()生成,设5分钟过期;模板中以隐藏字段注入;post方法内用redis.del原子校验并销毁,返回值非1则拒绝请求。

Beego 中如何生成和下发防重 Token
Beego 没有内置的全局 Token 生成中间件,需手动在 Controller 中生成并注入到模板。关键点是:Token 必须一次性、有时效、且服务端可原子校验。
推荐用 uuid.NewV4().String() 生成,存入 Redis(不要用 Beego 的 Session,默认基于文件或内存,不满足分布式要求):
func (c *OrderController) Get() {
token := uuid.NewV4().String()
redisClient.Set(token, "1", 300*time.Second) // 5分钟过期
c.Data["Token"] = token
c.TplName = "order.tpl"
}
模板里写成隐藏字段:<input type="hidden" name="token" value="{{.Token}}">。注意别漏掉 c.Data 赋值,否则模板取不到值。
Beego 接收请求时如何校验并销毁 Token
校验逻辑不能放在业务方法里手写 if 判断——容易遗漏、不可复用。应封装为 CheckToken 方法,并在 Post 方法开头强制调用:
- 先用
c.GetString("token")取值,为空直接返回 400 - 调用
redisClient.Del(token),用Del而非Get+Del,避免竞态:如果两个请求几乎同时进来,Get都成功,但只有一个能Del成功;而Del返回1表示键存在并被删,返回0就是已失效 - 判断
Del返回值是否为1,不是则拒绝请求,返回 429 或自定义错误码
示例:
func (c *OrderController) Post() {
token := c.GetString("token")
if token == "" {
c.Abort("400")
return
}
n, _ := redisClient.Del(token)
if n != 1 {
c.Abort("429")
return
}
// 后续执行下单逻辑
}
为什么不能只依赖 Beego 的 Filter 做统一 Token 校验
Filter 看似方便,但 Beego 的 Filter 是按路径注册的,无法动态区分哪些 Action 需要 Token、哪些不需要(比如登录页要发 Token,但登录提交又要校验)。硬塞进全局 Filter 容易误伤 GET 接口或健康检查接口。
更严重的是:Filter 执行时机在路由匹配之后、Action 执行之前,但 Beego 的 c.GetString 在 Filter 里不可靠——参数可能还没绑定好,尤其当表单含文件或 Content-Type 为 multipart/form-data 时,c.Input.RequestBody 还没解析,GetString 会返回空。
所以必须在具体 Action 内部做校验,确保上下文完整、参数可用。
Redis 连接和超时配置容易被忽略的细节
Beego 项目常把 Redis 初始化写在 main.go,但要注意两点:
-
redis.DialTimeout和redis.DialReadTimeout必须显式设置,否则默认 0(阻塞),网络抖动时整个请求卡住 - Token 过期时间(如 300 秒)和 Redis 连接池
MaxIdle要匹配:如果并发高、连接池小,大量Del请求排队,可能导致 Token 实际失效延迟,用户感知为“点了没反应”
建议生产环境设 MaxIdle=30,DialTimeout=500*time.Millisecond,并监控 Redis 的 del 命令耗时。











