gin接口限制请求频率

参与的业务模块今天被安全的同学玩坏了。一直疯狂掉用接口,然后服务器就挂了。涉及的接口需要处理大量的数据,并且需要处理很多条件。

有限流和缓存两种解决方案。考虑到正常业务下数据的访问量并不高,而且筛选条件较多,所以选用限流的方式解决。(我让你测)

吃水不忘挖井人,这是参考的博客:SpringBoot限制接口访问频率 - 这些错误千万不能犯

设计

两种设计方案。相同点是均采用访问ip+访问path作为redis的key。不同点是第一种采用stirng类型,第二种采用zset类型。

string

适用场景:某一ip在t时间内访问一个接口1次。
收到接口请求时,首先判断一下该key是否存在。若存在则返回,不存在则将key添加至redis中,并设置过期时间t。
在这里插入图片描述

为什么上面要说访问这种适合访问1次呢,我们将请求次数作为value

现在规定在5s内只能访问两次。我们分别在第1秒和第5s之间访问了一次。在第一访问的时候就已设置了过期时间。那么在第五秒过后该key就会过期。紧接着我又在第6s和第7s访问了两次。正常情况第7s这次不应该访问成功,因为[3,7]之间已经访问了两次5,6!

那如果每次都延长该key过期时间呢?
还是在5s内只能访问两次。我们分别在第1秒和第2秒之间访问了一次,在第6秒的时候该key还没有过期,并且value为2,依旧会被拦截。

所以采用string适合某一ip在t时间内访问一个接口1次的这种情况

zset

将时间戳(s)作为zset的分数,可以很好解决上面的问题。假如还是规定在5s内只能访问两次。那么每次请求只需要统计(当前时间戳-5s,当前时间戳] 区域的分数的个数是否大于2,就可以很好的解决上面的问题。
在这里插入图片描述

如果不考虑缓存回收问题,这样简直完美。
这里采用过期时间+逻辑删除。在每次访问时,可以将ts前的数据删除。并且在新建一个zset时为其添加一个较长的过期时间。如果只采用逻辑删除的话,创建的zset不会被删除,会越积越多。所以需要配合过期时间来回收创建的zset。(回收时间周期比较长,出现上述string类型的问题的概率比较低)

代码实现

定义一个中间件,如果超过次数限制直接拦截

middlewares

适用方法和jwt验证一样,在路由中 r.Use(RequestLimit()) 即可


type Limit struct {
	Count    int64 `json:"count"`	// 次数
	Interval int64 `json:"interval"` // 时间间隔
}

var limit Limit

func RequestLimit() func(c *gin.Context) {
	rdb, _, _ := dao.NewRedis()
	pkg.ScanGlobalConfigValue(context.Background(), "ep_event", "request_limit_config", &limit) // 这里可以删掉,修改为limit的初始化参数
	return func(c *gin.Context) {

		// 获取ip
		clientIp := c.ClientIP()
		path := c.Request.URL.Path
		t := time.Now().Unix()
		key := pkg.RedisRequestLimit + clientIp + path
		has, _ := rdb.Exists(context.Background(), key).Result()
		count, _ := rdb.ZCount(context.Background(), key, fmt.Sprintf("%d", t-limit.Interval), "+inf").Result()
		if has == 0 { // 如果是第一次创建,最长时间不超过1小时
			rdb.Expire(context.Background(), key, 1*time.Hour)
		}
		if count >= limit.Count { // 超出次数,限制
			c.Abort()
			c.JSON(http.StatusBadRequest, model.CommonReply{Code: model.ReplyCodeErr, Msg: "请求过于频繁"})
			return
		} else {
			rdb.ZAdd(context.Background(), key, &redis.Z{Score: float64(t), Member: strconv.Itoa(int(t))})
			// 删除窗口外的数据
			go func() {
				memberToRemove, _ := rdb.ZRangeByScore(context.Background(), key, &redis.ZRangeBy{
					Max: strconv.Itoa(int(t - limit.Interval)),
					Min: "0",
				}).Result()
				if len(memberToRemove) > 0 {
					rdb.ZRem(context.Background(), key, memberToRemove)
				}
			}()
		}
	}
}

<think>好的,我现在需要帮助用户解决在Gin框架中使用中间件实现路由拦截的问题。首先,我得回忆一下Gin中间件的基本概念和使用方法。根据用户提供的引用内容,中间件在Gin中是用来在请求处理前后添加功能的,比如日志记录或权限校验。 用户想要具体的示例代码,所以我应该先展示一个基本的中间件结构。比如,一个记录请求信息的中间件,然后在路由中使用它。接下来,可能需要实现权限校验的中间件,这里需要处理验证逻辑,比如检查请求头中的Token,如果无效则中断请求并返回错误。 然后,用户可能想知道如何将中间件应用到特定的路由组上,这样可以对不同路由进行不同的拦截处理。例如,创建一个需要认证的路由组,并应用权限校验中间件。同时,公共路由可能不需要这个中间件,这样可以区分处理。 接下来,我需要考虑中间件的执行顺序,因为Gin中的中间件是按照添加顺序执行的。所以,在代码示例中应该明确中间件的注册顺序,确保权限校验在需要的时候生效。同时,要提到如何通过c.Abort()来终止后续处理,这对路由拦截非常重要。 用户还可能关心如何测试这些中间件,所以给出一个简单的测试方法,比如使用curl命令或者Postman发送带有和不带Token的请求,验证拦截是否有效。此外,可以提及中间件的其他应用场景,比如日志记录、跨域处理等,以扩展用户的思路。 最后,需要确保代码示例符合Go语言的语法,并且正确使用Gin框架的接口,比如Use方法注册中间件,Group方法创建路由组。同时,引用用户提供的参考资料,特别是引用[1]和[2],以增强回答的可信度。 总结一下,回答的结构应该是先介绍中间件的基本概念,然后分步骤给出权限校验的中间件示例,路由组的应用,执行顺序说明,测试方法,最后是其他应用场景。这样用户能够逐步理解和实现路由拦截的功能。</think>### Gin 框架中使用中间件实现路由拦截的步骤 #### 1. 中间件基础概念 中间件是 Gin 框架中在请求处理前后执行逻辑的组件,常用于路由拦截、权限校验、日志记录等[^1]。其核心是通过 `c.Next()` 控制流程,或 `c.Abort()` 中断请求。 --- #### 2. 实现路由拦截的中间件示例 ##### 权限校验中间件 ```go func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 假设从 Header 获取 Token token := c.GetHeader("Authorization") if token != "valid_token" { // 拦截请求并返回 401 未授权 c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) return } // 验证通过则继续执行后续逻辑 c.Next() } } ``` ##### 路由配置中应用中间件 ```go func main() { r := gin.Default() // 全局中间件(所有路由生效) r.Use(gin.Logger()) // 示例:日志记录中间件 // 公共路由(无需拦截) r.GET("/public", func(c *gin.Context) { c.String(200, "Public Content") }) // 创建需要拦截的路由组 privateGroup := r.Group("/private") privateGroup.Use(AuthMiddleware()) // 应用权限校验中间件 { privateGroup.GET("/data", func(c *gin.Context) { c.String(200, "Sensitive Data") }) } r.Run(":8080") } ``` --- #### 3. 关键机制解析 - **执行顺序**:中间件按注册顺序执行,若某中间件调用 `c.Abort()`,后续中间件和路由处理函均不会执行。 - **路由组隔离**:通过 `Group` + `Use` 可针对特定路由组拦截,避免全局污染。 - **上下文传递**:可通过 `c.Set("key", value)` 在中间件间共享据(如用户信息)。 --- #### 4. 测试验证方法 ```bash # 测试公共路由(无需 Token) curl http://localhost:8080/public # 测试受保护路由(未提供 Token) curl http://localhost:8080/private/data # 测试受保护路由(提供有效 Token) curl -H "Authorization: valid_token" http://localhost:8080/private/data ``` --- #### 5. 扩展应用场景 1. **请求日志记录**:记录请求方法、路径、耗时等。 2. **跨域处理**:通过 `c.Writer.Header().Set("Access-Control-Allow-Origin", "*")` 添加 CORS 头。 3. **限流控制**:结合令牌桶算法限制接口访问频率。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值