package middleware import ( "fmt" "time" "github.com/gin-gonic/gin" "be.ems/src/framework/constants" "be.ems/src/framework/database/redis" "be.ems/src/framework/ip2region" "be.ems/src/framework/reqctx" "be.ems/src/framework/resp" "be.ems/src/framework/utils/crypto" ) const ( // LIMIT_GLOBAL 默认策略全局限流 LIMIT_GLOBAL = 1 // LIMIT_IP 根据请求者IP进行限流 LIMIT_IP = 2 // LIMIT_USER 根据用户ID进行限流 LIMIT_USER = 3 ) // LimitOption 请求限流参数 type LimitOption struct { Time int64 `json:"time"` // 限流时间,单位秒 5 Count int64 `json:"count"` // 限流次数,单位次 10 Type int64 `json:"type"` // 限流条件类型,默认LIMIT_GLOBAL } // RateLimit 请求限流 // // 示例参数:middleware.LimitOption{ Time: 5, Count: 10, Type: middleware.LIMIT_IP } // // 参数表示:5秒内,最多请求10次,限制类型为 IP // // 使用 USER 时,请在用户身份授权认证校验后使用 // 以便获取登录用户信息,无用户信息时默认为 LIMIT_GLOBAL func RateLimit(option LimitOption) gin.HandlerFunc { return func(c *gin.Context) { // 初始可选参数数据 if option.Time < 5 { option.Time = 5 } if option.Count < 10 { option.Count = 10 } if option.Type == 0 { option.Type = LIMIT_GLOBAL } // 获取执行函数名称 funcName := c.HandlerName() // 生成限流key limitKey := constants.CACHE_RATE_LIMIT + ":" + crypto.MD5(funcName) // 用户 if option.Type == LIMIT_USER { userId := reqctx.LoginUserToUserID(c) if userId <= 0 { c.JSON(401, resp.CodeMsg(resp.CODE_AUTH_INVALID, "invalid login user information")) c.Abort() // 停止执行后续的处理函数 return } funcMd5 := crypto.MD5(fmt.Sprintf("%d:%s", userId, funcName)) limitKey = constants.CACHE_RATE_LIMIT + ":" + funcMd5 } // IP if option.Type == LIMIT_IP { clientIP := ip2region.ClientIP(c.ClientIP()) funcMd5 := crypto.MD5(fmt.Sprintf("%s:%s", clientIP, funcName)) limitKey = constants.CACHE_RATE_LIMIT + ":" + funcMd5 } // 在Redis查询并记录请求次数 rateCount, err := redis.RateLimit("", limitKey, option.Time, option.Count) if err != nil { c.JSON(200, resp.CodeMsg(resp.CODE_RATELIMIT, resp.MSG_RATELIMIT)) c.Abort() // 停止执行后续的处理函数 return } rateTime, err := redis.GetExpire("", limitKey) if err != nil { c.JSON(200, resp.CodeMsg(resp.CODE_RATELIMIT, resp.MSG_RATELIMIT)) c.Abort() // 停止执行后续的处理函数 return } // 设置响应头中的限流声明字段 c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", option.Count)) // 总请求数限制 c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", option.Count-rateCount)) // 剩余可用请求数 c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+rateTime)) // 重置时间戳 if rateCount >= option.Count { c.JSON(200, resp.CodeMsg(resp.CODE_RATELIMIT, resp.MSG_RATELIMIT)) c.Abort() // 停止执行后续的处理函数 return } // 调用下一个处理程序 c.Next() } }