Files
zhilv 10a200b96c
Some checks failed
Release / build-and-release (push) Failing after 1m31s
初始提交: ScriptForge 脚本快速转运行链接服务
- 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>
2026-05-28 23:48:19 +08:00

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()
}
}