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
|
|||
|
}
|