两个路由级注解中间件。ratelimiter 用滑动窗口 ZSET + Lua 单次 EVAL 原子限流;repeatsubmit 用 SETNX 抢锁 + 响应 code 判定成功/失败防重复提交。两者都依赖 redis.Init 先就绪。
关键导出
ratelimiter.LimitType: LimitTypeGlobal / LimitTypeIP / LimitTypeClusterratelimiter.Limiter / Init()ratelimiter.RateLimiter(window, count, limitType, timeout, message) gin.HandlerFuncratelimiter.RateLimiterWithKeyFunc(window, count, fn func(*gin.Context) string, timeout, message) gin.HandlerFunc(动态维度,闭包替 SpEL)repeatsubmit.Submitter / Init()repeatsubmit.RepeatSubmit(interval, message) gin.HandlerFunc- 常量
defaultInterval=5s / minInterval=1s
典型用法
go
// internal/system/router.go:62
profile.PUT("/update",
oplog.Log(profileLogTitle, enum.BusinessTypeUpdate),
repeatsubmit.RepeatSubmit(0, ""),
handler.ProfileApiApp.UpdateProfile)坑/约定
WARNING
- ratelimiter 滑动窗口用 ZSET + Lua 单次 EVAL 避免「读-判-写」竞态;ZADD 成员必须唯一(同毫秒并发否则覆盖只计一次);Redis 异常时放行(可用性优先);timeout 至少覆盖一个窗口否则键提前消失;
LimitTypeCluster用进程实例 ID(newInstanceID())。 - repeatsubmit 指纹 =
sha256(token + ":" + 请求体 + query)(无 AOP 取不到方法入参);interval < 1s注册期 panic 而非运行期抛异常;须排在encrypt.ApiEncrypt()之后(指纹要用解密明文,密文每次随机 AES 密钥会使指纹漂移);成功(code=200)保留键 interval 内挡重,失败/panic 删键允许重试;panic 路径不 flush 缓冲区(否则c.Writer.Written()=true,Recover 不再渲染 500,客户端收半截响应);release 用context.WithoutCancel(客户端断连会取消原 ctx,但键仍要删)。 :::
相关页:/pkg-reference/middleware、/pkg-reference/captcha-encrypt、/pkg-reference/response-errs。