DeployHelper/internal/cache/cache.go

195 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}