package cache import ( "context" "ego/pkg/logger" "fmt" "os" "strconv" "sync" "time" "github.com/redis/go-redis/v9" "go.uber.org/zap" ) var ( RedisClient *redis.Client once sync.Once initErr error ) // RedisConfig Redis连接配置 type RedisConfig struct { Addr string `env:"REDIS_ADDR"` Password string `env:"REDIS_PW"` DB int `env:"REDIS_DB"` MaxRetries int `env:"REDIS_RETRIES"` PoolSize int `env:"REDIS_POOL_SIZE"` ReadTimeout time.Duration `env:"REDIS_READ_TIMEOUT"` } // Redis 初始化Redis连接 func Redis() error { once.Do(func() { initErr = initRedis() }) return initErr } // initRedis 实际的Redis初始化逻辑 func initRedis() error { config, err := loadRedisConfig() if err != nil { logger.Error(nil, "Redis配置加载失败", zap.Error(err)) return fmt.Errorf("redis配置加载失败: %w", err) } // 创建客户端 client := redis.NewClient(&redis.Options{ Addr: config.Addr, Password: config.Password, DB: config.DB, MaxRetries: config.MaxRetries, PoolSize: config.PoolSize, ReadTimeout: config.ReadTimeout, WriteTimeout: config.ReadTimeout * 2, // 添加更多配置选项 DialTimeout: 5 * time.Second, PoolTimeout: 4 * time.Second, MinIdleConns: 2, }) // 验证连接 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := client.Ping(ctx).Err(); err != nil { logger.Error(nil, "Redis连接失败", zap.Error(err), zap.String("addr", config.Addr), zap.Int("db", config.DB)) return fmt.Errorf("Redis连接失败: %w", err) } RedisClient = client logger.Info(nil, "Redis连接成功", zap.String("address", config.Addr), zap.Int("db", config.DB), zap.Int("pool_size", config.PoolSize), zap.Duration("read_timeout", config.ReadTimeout)) return nil } // Close 关闭Redis连接 func Close() error { if RedisClient != nil { err := RedisClient.Close() if err != nil { logger.Error(nil, "关闭Redis失败", zap.Error(err)) return fmt.Errorf("关闭Redis失败: %w", err) } logger.Info(nil, "Redis已关闭") } return nil } // GetClient 获取Redis客户端(用于外部调用) func GetClient() (*redis.Client, error) { if RedisClient == nil { return nil, fmt.Errorf("Redis连接未初始化") } return RedisClient, nil } // IsConnected 检查Redis连接状态 func IsConnected() bool { if RedisClient == nil { return false } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() return RedisClient.Ping(ctx).Err() == nil } // loadRedisConfig 从环境变量加载配置 func loadRedisConfig() (*RedisConfig, error) { // 必要参数校验 addr := getEnvWithDefault("REDIS_ADDR", "localhost:6379") dbStr := getEnvWithDefault("REDIS_DB", "0") db, err := strconv.Atoi(dbStr) if err != nil { return nil, fmt.Errorf("无效的 REDIS_DB 值 '%s': %w", dbStr, err) } // 基础配置 config := &RedisConfig{ Addr: addr, Password: os.Getenv("REDIS_PW"), // 密码可以为空 DB: db, MaxRetries: 5, PoolSize: 10, ReadTimeout: 5 * time.Second, } // 读取扩展配置 if err := loadExtendedConfig(config); err != nil { return nil, fmt.Errorf("加载扩展配置失败: %w", err) } return config, nil } // loadExtendedConfig 加载扩展配置 func loadExtendedConfig(config *RedisConfig) error { // 连接池大小 if val := os.Getenv("REDIS_POOL_SIZE"); val != "" { poolSize, err := strconv.Atoi(val) if err != nil { return fmt.Errorf("无效的 REDIS_POOL_SIZE 值 '%s': %w", val, err) } if poolSize <= 0 { return fmt.Errorf("REDIS_POOL_SIZE 必须大于 0") } config.PoolSize = poolSize } // 重试次数 if val := os.Getenv("REDIS_RETRIES"); val != "" { retries, err := strconv.Atoi(val) if err != nil { return fmt.Errorf("无效的 REDIS_RETRIES 值 '%s': %w", val, err) } if retries < 0 { return fmt.Errorf("REDIS_RETRIES 不能小于 0") } config.MaxRetries = retries } // 读取超时 if val := os.Getenv("REDIS_READ_TIMEOUT"); val != "" { timeout, err := time.ParseDuration(val) if err != nil { return fmt.Errorf("无效的 REDIS_READ_TIMEOUT 值 '%s': %w", val, err) } if timeout <= 0 { return fmt.Errorf("REDIS_READ_TIMEOUT 必须大于 0") } config.ReadTimeout = timeout } return nil } // getEnvWithDefault 获取环境变量,如果不存在则返回默认值 func getEnvWithDefault(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue }