220 lines
6.0 KiB
Go
220 lines
6.0 KiB
Go
// common/redis/redis.go
|
||
package redis
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/redis/go-redis/v9"
|
||
)
|
||
|
||
// 全局单例Redis客户端(连接池)
|
||
var (
|
||
clientInstance *redis.Client
|
||
once sync.Once
|
||
initErr error
|
||
)
|
||
|
||
// RedisOptions Redis连接配置项
|
||
// 包含基础连接信息+连接池配置,按需配置
|
||
type RedisOptions struct {
|
||
Host string // Redis地址
|
||
Port string // Redis端口
|
||
Password string // Redis密码
|
||
DB int // 数据库索引(建议每个业务一个DB,如user=0, order=1)
|
||
PoolSize int // 连接池最大连接数
|
||
MinIdleConns int // 连接池最小空闲连接数
|
||
ConnTimeout time.Duration // 连接超时时间
|
||
ReadTimeout time.Duration // 读超时时间
|
||
WriteTimeout time.Duration // 写超时时间
|
||
IdleTimeout time.Duration // 连接空闲超时时间
|
||
}
|
||
|
||
// RedisOption 选项模式函数类型
|
||
type RedisOption func(*RedisOptions)
|
||
|
||
// defaultRedisOptions 初始化默认配置
|
||
// 开发环境直接对接根目录docker-compose的redis服务,无需额外配置
|
||
func defaultRedisOptions() *RedisOptions {
|
||
return &RedisOptions{
|
||
Host: "redis", // 匹配Compose服务名
|
||
Port: "6379", // 默认端口
|
||
Password: "", // 开发环境默认无密码
|
||
DB: 0, // 默认DB0
|
||
PoolSize: 50, // 连接池最大连接数
|
||
MinIdleConns: 10, // 最小空闲连接,避免频繁创建连接
|
||
ConnTimeout: 5 * time.Second,
|
||
ReadTimeout: 3 * time.Second,
|
||
WriteTimeout: 3 * time.Second,
|
||
IdleTimeout: 30 * time.Minute,
|
||
}
|
||
}
|
||
|
||
// 以下为配置项设置函数,支持链式调用
|
||
func WithHost(host string) RedisOption {
|
||
return func(o *RedisOptions) { o.Host = host }
|
||
}
|
||
|
||
func WithPort(port string) RedisOption {
|
||
return func(o *RedisOptions) { o.Port = port }
|
||
}
|
||
|
||
func WithPassword(pwd string) RedisOption {
|
||
return func(o *RedisOptions) { o.Password = pwd }
|
||
}
|
||
|
||
func WithDB(db int) RedisOption {
|
||
return func(o *RedisOptions) { o.DB = db }
|
||
}
|
||
|
||
func WithPoolSize(size int) RedisOption {
|
||
return func(o *RedisOptions) { o.PoolSize = size }
|
||
}
|
||
|
||
func WithLogLevel(level redis.LogLevel) RedisOption {
|
||
return func(o *RedisOptions) {
|
||
// 兼容日志级别配置,需配合客户端初始化
|
||
}
|
||
}
|
||
|
||
// InitRedis 初始化Redis单例客户端(连接池)
|
||
// 选项模式配置,仅执行一次,全局复用连接池
|
||
func InitRedis(opts ...RedisOption) (*redis.Client, error) {
|
||
once.Do(func() {
|
||
// 加载默认配置 + 覆盖用户自定义配置
|
||
options := defaultRedisOptions()
|
||
for _, opt := range opts {
|
||
opt(options)
|
||
}
|
||
|
||
// 构建Redis客户端配置
|
||
rdbOpts := &redis.Options{
|
||
Addr: fmt.Sprintf("%s:%s", options.Host, options.Port),
|
||
Password: options.Password,
|
||
DB: options.DB,
|
||
PoolSize: options.PoolSize,
|
||
MinIdleConns: options.MinIdleConns,
|
||
DialTimeout: options.ConnTimeout,
|
||
ReadTimeout: options.ReadTimeout,
|
||
WriteTimeout: options.WriteTimeout,
|
||
IdleTimeout: options.IdleTimeout,
|
||
}
|
||
|
||
// 创建客户端实例
|
||
clientInstance = redis.NewClient(rdbOpts)
|
||
|
||
// 测试连接,确保连接有效
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
defer cancel()
|
||
if _, err := clientInstance.Ping(ctx).Result(); err != nil {
|
||
initErr = fmt.Errorf("redis ping failed: %w", err)
|
||
clientInstance = nil // 连接失败置空实例
|
||
return
|
||
}
|
||
|
||
initErr = nil
|
||
})
|
||
|
||
return clientInstance, initErr
|
||
}
|
||
|
||
// GetRedis 获取全局Redis单例客户端
|
||
// 业务层初始化后,直接调用获取,无需重复初始化
|
||
func GetRedis() (*redis.Client, error) {
|
||
if clientInstance == nil {
|
||
return nil, errors.New("redis not initialized, please call InitRedis first")
|
||
}
|
||
return clientInstance, nil
|
||
}
|
||
|
||
// CloseRedis 关闭Redis连接池(应用退出时调用,优雅释放资源)
|
||
func CloseRedis() error {
|
||
if clientInstance == nil {
|
||
return errors.New("redis not initialized")
|
||
}
|
||
|
||
if err := clientInstance.Close(); err != nil {
|
||
return fmt.Errorf("close redis failed: %w", err)
|
||
}
|
||
|
||
// 重置单例,避免重复关闭
|
||
once = sync.Once{}
|
||
clientInstance = nil
|
||
initErr = errors.New("redis client closed")
|
||
return nil
|
||
}
|
||
|
||
// --------------- 基础操作封装 ---------------
|
||
// 轻量封装高频基础操作,简化业务层调用(无需关注context和错误处理基础逻辑)
|
||
// 复杂操作(如哈希、列表、事务)直接使用原生*redis.Client即可
|
||
|
||
// Ctx 全局默认context,业务层可自定义传入
|
||
var Ctx = context.Background()
|
||
|
||
// Set 封装Set操作,带过期时间
|
||
// key: 键, val: 值, expire: 过期时间(0表示永不过期)
|
||
func Set(key string, val interface{}, expire time.Duration) error {
|
||
rdb, err := GetRedis()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return rdb.Set(Ctx, key, val, expire).Err()
|
||
}
|
||
|
||
// Get 封装Get操作,返回字符串值
|
||
func Get(key string) (string, error) {
|
||
rdb, err := GetRedis()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return rdb.Get(Ctx, key).Result()
|
||
}
|
||
|
||
// Del 封装Del操作,删除一个或多个键
|
||
func Del(keys ...string) (int64, error) {
|
||
rdb, err := GetRedis()
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return rdb.Del(Ctx, keys...).Result()
|
||
}
|
||
|
||
// Exists 检查键是否存在
|
||
func Exists(key string) (bool, error) {
|
||
rdb, err := GetRedis()
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
res, err := rdb.Exists(Ctx, key).Result()
|
||
return res > 0, err
|
||
}
|
||
|
||
// Expire 为键设置过期时间
|
||
func Expire(key string, expire time.Duration) error {
|
||
rdb, err := GetRedis()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return rdb.Expire(Ctx, key, expire).Err()
|
||
}
|
||
|
||
// HSet 封装哈希Set操作,设置单个字段
|
||
func HSet(key, field string, val interface{}) error {
|
||
rdb, err := GetRedis()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return rdb.HSet(Ctx, key, field, val).Err()
|
||
}
|
||
|
||
// HGet 封装哈希Get操作,获取单个字段值
|
||
func HGet(key, field string) (string, error) {
|
||
rdb, err := GetRedis()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return rdb.HGet(Ctx, key, field).Result()
|
||
} |