package cache import ( "context" "time" json "github.com/goccy/go-json" "github.com/redis/go-redis/v9" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" log "github.com/sirupsen/logrus" ) type redisResponseCache struct { cfg config.ResponseCacheConfig client redis.UniversalClient prefix string stats responseCacheStats } func newRedisResponseCache(cfg config.ResponseCacheConfig, client redis.UniversalClient) *redisResponseCache { prefix := cfg.RedisKeyPrefix if prefix == "" { prefix = "respcache" } return &redisResponseCache{ cfg: cfg, client: client, prefix: "cliproxy:" + prefix, } } func (r *redisResponseCache) Get(ctx context.Context, key string) (*CachedResponse, bool) { data, err := r.client.Get(ctx, r.formatKey(key)).Bytes() if err != nil { r.stats.recordMiss() return nil, false } var entry CachedResponse if err := json.Unmarshal(data, &entry); err != nil { log.Warnf("response cache: unmarshal error: %v", err) r.stats.recordMiss() return nil, false } r.stats.recordHit() return &entry, true } func (r *redisResponseCache) Set(ctx context.Context, key string, resp *CachedResponse, ttl time.Duration) error { data, err := json.Marshal(resp) if err != nil { return err } return r.client.Set(ctx, r.formatKey(key), data, ttl).Err() } func (r *redisResponseCache) Delete(ctx context.Context, pattern string) error { fullPattern := r.prefix + ":" + pattern var cursor uint64 for { keys, next, err := r.client.Scan(ctx, cursor, fullPattern, 100).Result() if err != nil { return err } if len(keys) > 0 { r.client.Del(ctx, keys...) } cursor = next if cursor == 0 { break } } return nil } func (r *redisResponseCache) Stats() CacheStats { return CacheStats{ Hits: r.stats.hits.Load(), Misses: r.stats.misses.Load(), } } func (r *redisResponseCache) Close() error { return nil } func (r *redisResponseCache) formatKey(key string) string { return r.prefix + ":" + key }