feat: 优化web
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// PerIPMinute 按客户端 IP 的固定窗口速率(每分钟 perMinute 次,burst 取 perMinute 与 64 的较小值)。
|
||||
// 进程内 map 可能随 IP 数增长,多实例部署请在网关侧限流。
|
||||
func PerIPMinute(enabled bool, perMinute int) gin.HandlerFunc {
|
||||
if !enabled || perMinute <= 0 {
|
||||
return func(c *gin.Context) { c.Next() }
|
||||
}
|
||||
burst := perMinute
|
||||
if burst > 64 {
|
||||
burst = 64
|
||||
}
|
||||
if burst < 5 {
|
||||
burst = 5
|
||||
}
|
||||
lim := rate.Limit(float64(perMinute) / 60.0)
|
||||
var mu sync.Mutex
|
||||
limiters := make(map[string]*rate.Limiter)
|
||||
return func(c *gin.Context) {
|
||||
ip := clientIP(c)
|
||||
mu.Lock()
|
||||
limiter, ok := limiters[ip]
|
||||
if !ok {
|
||||
limiter = rate.NewLimiter(lim, burst)
|
||||
limiters[ip] = limiter
|
||||
}
|
||||
mu.Unlock()
|
||||
if !limiter.Allow() {
|
||||
c.AbortWithStatusJSON(429, gin.H{"error": "rate_limit_exceeded"})
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func clientIP(c *gin.Context) string {
|
||||
if xff := c.GetHeader("X-Forwarded-For"); xff != "" {
|
||||
parts := strings.Split(xff, ",")
|
||||
if len(parts) > 0 {
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
}
|
||||
host, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
|
||||
if err != nil {
|
||||
return c.Request.RemoteAddr
|
||||
}
|
||||
return host
|
||||
}
|
||||
Reference in New Issue
Block a user