Files
web_huakui_server/web/middleware/apilimitHandler/aiplimit.go
T
rootandClaude Opus 4.7 85c0071fd8 feat: 从黄果 NET hgdjnet-server 9bfbafe 导入 HKDJ 官网后端
来源:hgdjnet-server 9bfbafe10d417deea959bd53117bc88a10e60a83
项目身份 HKDJ / JHA-216,独立官网仓库,Jenkins 守卫键 hkdjnet
测试目录 /server/hkdj_net,App/Web API 30331/30332

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-16 11:20:04 +08:00

85 lines
1.6 KiB
Go

package apilimitHandler
import (
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
)
type counter struct {
Count int
WindowStart time.Time
}
type RouteHourlyLimiter struct {
mu sync.Mutex
limit int
window time.Duration
counters map[string]*counter
}
func NewRouteHourlyLimiter(limit int) *RouteHourlyLimiter {
l := &RouteHourlyLimiter{
limit: limit,
window: time.Hour,
counters: make(map[string]*counter),
}
go l.cleaner()
return l
}
func (l *RouteHourlyLimiter) Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 用 FullPath 做“接口维度”,例如 /api/user/:id
route := c.FullPath()
if route == "" {
// 路由未匹配时兜底
route = c.Request.URL.Path
}
now := time.Now()
l.mu.Lock()
defer l.mu.Unlock()
item, ok := l.counters[route]
if !ok || now.Sub(item.WindowStart) >= l.window {
l.counters[route] = &counter{
Count: 1,
WindowStart: now,
}
c.Next()
return
}
if item.Count >= l.limit {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"code": http.StatusTooManyRequests,
"message": "rate limit exceeded: max 100 requests per route per hour",
})
return
}
item.Count++
c.Next()
}
}
// 定时清理过期窗口,避免 map 无限增长
func (l *RouteHourlyLimiter) cleaner() {
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
for now := range ticker.C {
l.mu.Lock()
for k, v := range l.counters {
if now.Sub(v.WindowStart) >= l.window {
delete(l.counters, k)
}
}
l.mu.Unlock()
}
}