195 lines
4.6 KiB
Go
195 lines
4.6 KiB
Go
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
|
||
}
|