verifyreq和filter均不适合直接防重:verifyreq仅校验值,无消费标记、过期与ip限频;filter无法动态区分接口、参数未绑定时取值不可靠,且易误伤健康检查等非写操作接口。

Beego 里防重复请求不能只靠前端按钮禁用或 Axios 拦截器,服务端必须做幂等控制;否则抓包重放、网关重试、恶意脚本都能绕过,订单/支付类接口极易出现资损。
为什么 VerifyReq 和 Filter 都不适合直接做防重
Beego 自带的 captcha.VerifyReq 只校验值,不校验是否已消费、是否超时、是否同 IP 频繁刷——它压根不是为防重设计的。而全局 Filter(比如 BeforeExec)在参数绑定完成前不可靠:c.GetString("token") 可能取不到值;更关键的是,Filter 无法区分哪些 Action 需要防重(如 /order/create),哪些不需要(如 /health 或登录页本身要发 token)。硬塞进去,健康检查都可能被 429 拦住。
推荐做法:用 Redis 实现 Token + 单次消费校验
核心是「客户端提交时带 token,服务端原子性校验并删除」,避免竞态和重放:
- 前端在表单加载时调用
/api/token接口获取一次性 token,并写入 hidden 字段或 header - 后端生成 token 时存入 Redis:
SETEX token:abc123 300 "{\"ip\":\"1.2.3.4\",\"ts\":1726887120}"(TTL 5 分钟) - 提交时在 Controller 中执行:
GETDEL token:abc123;返回 nil 则说明已用过或过期,直接c.Abort("400") - 不要用
GET + DEL两步操作——并发请求下可能两个都拿到值,必须用 Redis 6.2+ 的GETDEL或 Lua 脚本保证原子性 - token 键名建议加业务前缀,如
submit:order:abc123,避免和其他模块冲突
上传和长耗时接口要额外加连接级限流
单纯 token 校验挡不住 Slowloris 或大量空连接占满 fd。Beego 的 LimitRouter 是伪限流,运行在 Controller 内,连接早已建立。真正有效的做法是下沉到 HTTP 层:
- 禁用 Beego 自动启动:
beego.BConfig.Listen.EnableHTTP = false - 手动构造
http.Server,包装net.Listener,在Accept()后用atomic.LoadInt64(&connCount)判断是否超限(如 > 1000),超限则conn.Close() - 对上传接口,在路由匹配后、
Prepare()前插入:r.Body = http.MaxBytesReader(w, r.Body, 50,防止攻击者发小表单+大文件绕过 <code>MaxMemory
Token 过期时间必须由 Redis TTL 控制,别在 Go 里算时间再写入;IP 提取要用 X-Forwarded-For(注意伪造风险),而不是直接信 RemoteAddr;最关键的是——所有写操作接口(下单、支付、改库存)必须统一走这套逻辑,漏一个就可能出问题。











