Some checks failed
Release / build-and-release (push) Failing after 1m31s
- Go 后端 (Gin + GORM + SQLite) 提供 API 和纯文本脚本服务 - Vite + React + TypeScript + Tailwind 前端 - 单二进制部署 (Go embed 前端静态文件) - Gitea Actions CI/CD: 打标签自动构建多平台 Release - 支持 bash/zsh/sh/fish/python3/node/ruby/php 8种运行环境 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
1.6 KiB
Go
90 lines
1.6 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type visitor struct {
|
|
count int
|
|
lastSeen time.Time
|
|
}
|
|
|
|
type rateLimiter struct {
|
|
visitors map[string]*visitor
|
|
mu sync.Mutex
|
|
limit int
|
|
window time.Duration
|
|
}
|
|
|
|
func newRateLimiter(limit int, window time.Duration) *rateLimiter {
|
|
rl := &rateLimiter{
|
|
visitors: make(map[string]*visitor),
|
|
limit: limit,
|
|
window: window,
|
|
}
|
|
go rl.cleanup()
|
|
return rl
|
|
}
|
|
|
|
func (rl *rateLimiter) allow(ip string) bool {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
v, exists := rl.visitors[ip]
|
|
if !exists {
|
|
rl.visitors[ip] = &visitor{count: 1, lastSeen: time.Now()}
|
|
return true
|
|
}
|
|
|
|
if time.Since(v.lastSeen) > rl.window {
|
|
v.count = 1
|
|
v.lastSeen = time.Now()
|
|
return true
|
|
}
|
|
|
|
v.count++
|
|
v.lastSeen = time.Now()
|
|
return v.count <= rl.limit
|
|
}
|
|
|
|
func (rl *rateLimiter) cleanup() {
|
|
for {
|
|
time.Sleep(10 * time.Minute)
|
|
rl.mu.Lock()
|
|
for ip, v := range rl.visitors {
|
|
if time.Since(v.lastSeen) > rl.window*2 {
|
|
delete(rl.visitors, ip)
|
|
}
|
|
}
|
|
rl.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
var (
|
|
createLimiter = newRateLimiter(10, 1*time.Minute)
|
|
generalLimiter = newRateLimiter(60, 1*time.Minute)
|
|
)
|
|
|
|
func RateLimit(limit int) gin.HandlerFunc {
|
|
if limit == 10 {
|
|
return func(c *gin.Context) {
|
|
if !createLimiter.allow(c.ClientIP()) {
|
|
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
return func(c *gin.Context) {
|
|
if !generalLimiter.allow(c.ClientIP()) {
|
|
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|