package store import ( "context" "log" "time" "github.com/redis/go-redis/v9" ) // Redis 持有 CacheDB 连接(Session / Rate Limit)。 // rdb 为 nil 表示降级模式(连接失败时放行,不限流)。 type Redis struct { rdb *redis.Client } // OpenRedis 连接 CacheDB。连接失败不 fatal:返回降级实例(限流放行)。 func OpenRedis(addr string) *Redis { rdb := redis.NewClient(&redis.Options{Addr: addr}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() if err := rdb.Ping(ctx).Err(); err != nil { log.Printf("[store] redis 不可用,降级运行(不限流): %v", err) _ = rdb.Close() return &Redis{} } log.Println("[store] redis connected") return &Redis{rdb: rdb} } // Enabled 报告是否处于真实限流模式。 func (r *Redis) Enabled() bool { return r.rdb != nil } // Allow 滑动窗口计数限流:在 window 内对 key 累加,超过 limit 即拒绝。 // 降级模式(rdb==nil)始终放行。 func (r *Redis) Allow(ctx context.Context, key string, limit int64, window time.Duration) (bool, error) { if r.rdb == nil { return true, nil } rk := "sundynix:ratelimit:" + key n, err := r.rdb.Incr(ctx, rk).Result() if err != nil { return true, err // 限流后端故障时放行,不阻断业务 } if n == 1 { _ = r.rdb.Expire(ctx, rk, window).Err() // 首次计数设置窗口过期 } return n <= limit, nil } // Close 释放底层连接。 func (r *Redis) Close() { if r.rdb != nil { _ = r.rdb.Close() } }