Files
2026-05-30 12:29:45 +03:30

120 lines
3.1 KiB
Go

package redis
import (
"context"
"fmt"
"time"
"tm/pkg/logger"
"github.com/redis/go-redis/v9"
)
// Config holds Redis configuration
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Password string `json:"password"`
DB int `json:"db"`
PoolSize int `json:"pool_size"`
}
// Client represents a Redis client interface
type Client interface {
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
Get(ctx context.Context, key string) (string, error)
IncrWithExpire(ctx context.Context, key string, expiration time.Duration) (int64, error)
Del(ctx context.Context, keys ...string) error
Exists(ctx context.Context, keys ...string) (int64, error)
Close() error
}
// client implements the Client interface
type client struct {
rdb *redis.Client
}
// NewClient creates a new Redis client
func NewClient(config *Config, logger logger.Logger) (Client, error) {
if config == nil {
return nil, fmt.Errorf("redis config is required")
}
addr := fmt.Sprintf("%s:%d", config.Host, config.Port)
rdb := redis.NewClient(&redis.Options{
Addr: addr,
Password: config.Password,
DB: config.DB,
PoolSize: config.PoolSize,
})
// Test the connection
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := rdb.Ping(ctx).Err(); err != nil {
logger.Error("Failed to connect to Redis", map[string]interface{}{
"host": config.Host,
"port": config.Port,
"error": err.Error(),
})
return nil, fmt.Errorf("failed to connect to Redis: %w", err)
}
logger.Info("Successfully connected to Redis", map[string]interface{}{
"host": config.Host,
"port": config.Port,
"db": config.DB,
})
return &client{rdb: rdb}, nil
}
const incrWithExpireScript = `
local current = redis.call("INCR", KEYS[1])
if current == 1 then
redis.call("EXPIRE", KEYS[1], ARGV[1])
else
local ttl = redis.call("TTL", KEYS[1])
if ttl == -1 then
redis.call("EXPIRE", KEYS[1], ARGV[1])
end
end
return current
`
// IncrWithExpire atomically increments a counter and ensures the key has a TTL.
func (c *client) IncrWithExpire(ctx context.Context, key string, expiration time.Duration) (int64, error) {
seconds := int64(expiration.Seconds())
if seconds < 1 {
seconds = 1
}
return c.rdb.Eval(ctx, incrWithExpireScript, []string{key}, seconds).Int64()
}
// Set sets a key-value pair with expiration
func (c *client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
return c.rdb.Set(ctx, key, value, expiration).Err()
}
// Get retrieves a value by key
func (c *client) Get(ctx context.Context, key string) (string, error) {
return c.rdb.Get(ctx, key).Result()
}
// Del deletes one or more keys
func (c *client) Del(ctx context.Context, keys ...string) error {
return c.rdb.Del(ctx, keys...).Err()
}
// Exists checks if one or more keys exist
func (c *client) Exists(ctx context.Context, keys ...string) (int64, error) {
return c.rdb.Exists(ctx, keys...).Result()
}
// Close closes the Redis connection
func (c *client) Close() error {
return c.rdb.Close()
}