DeployHelper/internal/cache/cache.go

195 lines
4.6 KiB
Go
Raw Normal View History

2025-08-01 16:38:08 +08:00
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
}