192 lines
5.7 KiB
Go
192 lines
5.7 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/kms/api-key-service/internal/config"
|
|
"github.com/kms/api-key-service/internal/errors"
|
|
)
|
|
|
|
// RedisCache implements CacheProvider using Redis
|
|
type RedisCache struct {
|
|
client *redis.Client
|
|
config config.ConfigProvider
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewRedisCache creates a new Redis cache provider
|
|
func NewRedisCache(config config.ConfigProvider, logger *zap.Logger) (CacheProvider, error) {
|
|
// Redis configuration
|
|
redisAddr := config.GetString("REDIS_ADDR")
|
|
if redisAddr == "" {
|
|
redisAddr = "localhost:6379"
|
|
}
|
|
|
|
redisPassword := config.GetString("REDIS_PASSWORD")
|
|
redisDB := config.GetInt("REDIS_DB")
|
|
|
|
// Create Redis client
|
|
client := redis.NewClient(&redis.Options{
|
|
Addr: redisAddr,
|
|
Password: redisPassword,
|
|
DB: redisDB,
|
|
PoolSize: config.GetInt("REDIS_POOL_SIZE"),
|
|
MinIdleConns: config.GetInt("REDIS_MIN_IDLE_CONNS"),
|
|
MaxRetries: config.GetInt("REDIS_MAX_RETRIES"),
|
|
DialTimeout: config.GetDuration("REDIS_DIAL_TIMEOUT"),
|
|
ReadTimeout: config.GetDuration("REDIS_READ_TIMEOUT"),
|
|
WriteTimeout: config.GetDuration("REDIS_WRITE_TIMEOUT"),
|
|
})
|
|
|
|
// Test connection
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := client.Ping(ctx).Err(); err != nil {
|
|
logger.Error("Failed to connect to Redis", zap.Error(err))
|
|
return nil, errors.NewInternalError("Failed to connect to Redis").WithInternal(err)
|
|
}
|
|
|
|
logger.Info("Connected to Redis successfully", zap.String("addr", redisAddr))
|
|
|
|
return &RedisCache{
|
|
client: client,
|
|
config: config,
|
|
logger: logger,
|
|
}, nil
|
|
}
|
|
|
|
// Get retrieves a value from Redis cache
|
|
func (r *RedisCache) Get(ctx context.Context, key string) ([]byte, error) {
|
|
r.logger.Debug("Getting value from Redis cache", zap.String("key", key))
|
|
|
|
result, err := r.client.Get(ctx, key).Result()
|
|
if err != nil {
|
|
if err == redis.Nil {
|
|
return nil, errors.NewNotFoundError("cache key")
|
|
}
|
|
r.logger.Error("Failed to get value from Redis", zap.Error(err))
|
|
return nil, errors.NewInternalError("Failed to get cached value").WithInternal(err)
|
|
}
|
|
|
|
return []byte(result), nil
|
|
}
|
|
|
|
// Set stores a value in Redis cache with TTL
|
|
func (r *RedisCache) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
|
|
r.logger.Debug("Setting value in Redis cache",
|
|
zap.String("key", key),
|
|
zap.Duration("ttl", ttl))
|
|
|
|
err := r.client.Set(ctx, key, value, ttl).Err()
|
|
if err != nil {
|
|
r.logger.Error("Failed to set value in Redis", zap.Error(err))
|
|
return errors.NewInternalError("Failed to cache value").WithInternal(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete removes a value from Redis cache
|
|
func (r *RedisCache) Delete(ctx context.Context, key string) error {
|
|
r.logger.Debug("Deleting value from Redis cache", zap.String("key", key))
|
|
|
|
err := r.client.Del(ctx, key).Err()
|
|
if err != nil {
|
|
r.logger.Error("Failed to delete value from Redis", zap.Error(err))
|
|
return errors.NewInternalError("Failed to delete cached value").WithInternal(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Exists checks if a key exists in Redis cache
|
|
func (r *RedisCache) Exists(ctx context.Context, key string) (bool, error) {
|
|
count, err := r.client.Exists(ctx, key).Result()
|
|
if err != nil {
|
|
r.logger.Error("Failed to check key existence in Redis", zap.Error(err))
|
|
return false, errors.NewInternalError("Failed to check cache key existence").WithInternal(err)
|
|
}
|
|
|
|
return count > 0, nil
|
|
}
|
|
|
|
// Clear removes all values from Redis cache (use with caution)
|
|
func (r *RedisCache) Clear(ctx context.Context) error {
|
|
r.logger.Warn("Clearing Redis cache - this will remove ALL cached data")
|
|
|
|
err := r.client.FlushDB(ctx).Err()
|
|
if err != nil {
|
|
r.logger.Error("Failed to clear Redis cache", zap.Error(err))
|
|
return errors.NewInternalError("Failed to clear cache").WithInternal(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes the Redis connection
|
|
func (r *RedisCache) Close() error {
|
|
r.logger.Info("Closing Redis connection")
|
|
return r.client.Close()
|
|
}
|
|
|
|
// SetNX sets a key only if it doesn't exist (Redis-specific operation)
|
|
func (r *RedisCache) SetNX(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error) {
|
|
r.logger.Debug("Setting value in Redis cache with NX",
|
|
zap.String("key", key),
|
|
zap.Duration("ttl", ttl))
|
|
|
|
result, err := r.client.SetNX(ctx, key, value, ttl).Result()
|
|
if err != nil {
|
|
r.logger.Error("Failed to set NX value in Redis", zap.Error(err))
|
|
return false, errors.NewInternalError("Failed to cache value with NX").WithInternal(err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Expire sets TTL for an existing key
|
|
func (r *RedisCache) Expire(ctx context.Context, key string, ttl time.Duration) error {
|
|
r.logger.Debug("Setting TTL for Redis key",
|
|
zap.String("key", key),
|
|
zap.Duration("ttl", ttl))
|
|
|
|
result, err := r.client.Expire(ctx, key, ttl).Result()
|
|
if err != nil {
|
|
r.logger.Error("Failed to set TTL in Redis", zap.Error(err))
|
|
return errors.NewInternalError("Failed to set key TTL").WithInternal(err)
|
|
}
|
|
|
|
if !result {
|
|
return errors.NewNotFoundError("cache key")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TTL returns the remaining time to live for a key
|
|
func (r *RedisCache) TTL(ctx context.Context, key string) (time.Duration, error) {
|
|
ttl, err := r.client.TTL(ctx, key).Result()
|
|
if err != nil {
|
|
r.logger.Error("Failed to get TTL from Redis", zap.Error(err))
|
|
return 0, errors.NewInternalError("Failed to get key TTL").WithInternal(err)
|
|
}
|
|
|
|
return ttl, nil
|
|
}
|
|
|
|
// Keys returns all keys matching a pattern
|
|
func (r *RedisCache) Keys(ctx context.Context, pattern string) ([]string, error) {
|
|
keys, err := r.client.Keys(ctx, pattern).Result()
|
|
if err != nil {
|
|
r.logger.Error("Failed to get keys from Redis", zap.Error(err))
|
|
return nil, errors.NewInternalError("Failed to get cache keys").WithInternal(err)
|
|
}
|
|
|
|
return keys, nil
|
|
}
|