owpengram-server/internal/store/redisstore/ratelimit.go
2026-06-04 01:37:39 +08:00

57 lines
1.3 KiB
Go

package redisstore
import (
"context"
"fmt"
"math"
"time"
"github.com/redis/go-redis/v9"
)
// RateLimiter 用 Redis INCR + TTL 实现固定窗口限流。
type RateLimiter struct {
c *redis.Client
}
// NewRateLimiter 创建 Redis-backed RateLimiter。
func NewRateLimiter(c *redis.Client) *RateLimiter {
return &RateLimiter{c: c}
}
func rateLimitKey(key string) string {
return "ratelimit:" + key
}
func (l *RateLimiter) Allow(ctx context.Context, key string, limit int, window time.Duration) (bool, int, error) {
if limit <= 0 {
return true, 0, nil
}
if window <= 0 {
window = time.Second
}
if l == nil || l.c == nil {
return false, 0, fmt.Errorf("redis rate limiter: nil client")
}
redisKey := rateLimitKey(key)
count, err := l.c.Incr(ctx, redisKey).Result()
if err != nil {
return false, 0, fmt.Errorf("redis incr rate limit: %w", err)
}
if count == 1 {
if err := l.c.Expire(ctx, redisKey, window).Err(); err != nil {
return false, 0, fmt.Errorf("redis expire rate limit: %w", err)
}
}
if count <= int64(limit) {
return true, 0, nil
}
ttl, err := l.c.TTL(ctx, redisKey).Result()
if err != nil {
return false, 0, fmt.Errorf("redis ttl rate limit: %w", err)
}
if ttl <= 0 {
ttl = window
}
return false, int(math.Ceil(ttl.Seconds())), nil
}