feat(auth): upgrade casbin to v3 and enhance connection pool configurations
Some checks failed
Build / build (push) Successful in 2m23s
Build / build-docker (push) Failing after 1m37s

- Upgrade casbin from v2 to v3 across go.mod and pkg/auth/casbin.go
- Add slide captcha verification to registration flow (CheckVerified, ConsumeVerified)
- Add DB wrapper with connection pool statistics and health checks
- Add Redis connection pool optimizations with stats and health monitoring
- Add new config options: ConnMaxLifetime, HealthCheckInterval, EnableRetryOnError
- Optimize slow query threshold from 200ms to 100ms
- Add ping with retry mechanism for database and Redis connections
This commit is contained in:
2026-02-25 19:00:50 +08:00
parent 1da0ee1ed1
commit a111872b32
15 changed files with 534 additions and 93 deletions

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"sync"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v3"
gormadapter "github.com/casbin/gorm-adapter/v3"
"go.uber.org/zap"
"gorm.io/gorm"

View File

@@ -65,18 +65,21 @@ type DatabaseConfig struct {
// RedisConfig Redis配置
type RedisConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
Database int `mapstructure:"database"`
PoolSize int `mapstructure:"pool_size"` // 连接池大小
MinIdleConns int `mapstructure:"min_idle_conns"` // 最小空闲连接数
MaxRetries int `mapstructure:"max_retries"` // 最大重试次数
DialTimeout time.Duration `mapstructure:"dial_timeout"` // 连接超时
ReadTimeout time.Duration `mapstructure:"read_timeout"` // 读取超时
WriteTimeout time.Duration `mapstructure:"write_timeout"` // 写入超时
PoolTimeout time.Duration `mapstructure:"pool_timeout"` // 连接池超时
ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
Database int `mapstructure:"database"`
PoolSize int `mapstructure:"pool_size"` // 连接池大小
MinIdleConns int `mapstructure:"min_idle_conns"` // 最小空闲连接数
MaxRetries int `mapstructure:"max_retries"` // 最大重试次数
DialTimeout time.Duration `mapstructure:"dial_timeout"` // 连接超时
ReadTimeout time.Duration `mapstructure:"read_timeout"` // 读取超时
WriteTimeout time.Duration `mapstructure:"write_timeout"` // 写入超时
PoolTimeout time.Duration `mapstructure:"pool_timeout"` // 连接池超时
ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` // 连接最大生命周期(新增)
HealthCheckInterval time.Duration `mapstructure:"health_check_interval"` // 健康检查间隔(新增)
EnableRetryOnError bool `mapstructure:"enable_retry_on_error"` // 错误时启用重试(新增)
}
// RustFSConfig RustFS对象存储配置 (S3兼容)
@@ -192,18 +195,21 @@ func setDefaults() {
viper.SetDefault("database.conn_max_lifetime", "1h")
viper.SetDefault("database.conn_max_idle_time", "10m")
// Redis默认配置
// Redis默认配置(优化后的默认值)
viper.SetDefault("redis.host", "localhost")
viper.SetDefault("redis.port", 6379)
viper.SetDefault("redis.database", 0)
viper.SetDefault("redis.pool_size", 10)
viper.SetDefault("redis.min_idle_conns", 5)
viper.SetDefault("redis.pool_size", 16) // 优化:提高默认连接池大小
viper.SetDefault("redis.min_idle_conns", 8) // 优化:提高最小空闲连接数
viper.SetDefault("redis.max_retries", 3)
viper.SetDefault("redis.dial_timeout", "5s")
viper.SetDefault("redis.read_timeout", "3s")
viper.SetDefault("redis.write_timeout", "3s")
viper.SetDefault("redis.pool_timeout", "4s")
viper.SetDefault("redis.conn_max_idle_time", "30m")
viper.SetDefault("redis.conn_max_idle_time", "10m") // 优化:减少空闲连接超时时间
viper.SetDefault("redis.conn_max_lifetime", "30m") // 新增:连接最大生命周期
viper.SetDefault("redis.health_check_interval", "30s") // 新增:健康检查间隔
viper.SetDefault("redis.enable_retry_on_error", true) // 新增:错误时启用重试
// RustFS默认配置
viper.SetDefault("rustfs.endpoint", "127.0.0.1:9000")
@@ -281,6 +287,9 @@ func setupEnvMappings() {
viper.BindEnv("redis.write_timeout", "REDIS_WRITE_TIMEOUT")
viper.BindEnv("redis.pool_timeout", "REDIS_POOL_TIMEOUT")
viper.BindEnv("redis.conn_max_idle_time", "REDIS_CONN_MAX_IDLE_TIME")
viper.BindEnv("redis.conn_max_lifetime", "REDIS_CONN_MAX_LIFETIME")
viper.BindEnv("redis.health_check_interval", "REDIS_HEALTH_CHECK_INTERVAL")
viper.BindEnv("redis.enable_retry_on_error", "REDIS_ENABLE_RETRY_ON_ERROR")
// RustFS配置
viper.BindEnv("rustfs.endpoint", "RUSTFS_ENDPOINT")
@@ -427,6 +436,22 @@ func overrideFromEnv(config *Config) {
}
}
if connMaxLifetime := os.Getenv("REDIS_CONN_MAX_LIFETIME"); connMaxLifetime != "" {
if val, err := time.ParseDuration(connMaxLifetime); err == nil {
config.Redis.ConnMaxLifetime = val
}
}
if healthCheckInterval := os.Getenv("REDIS_HEALTH_CHECK_INTERVAL"); healthCheckInterval != "" {
if val, err := time.ParseDuration(healthCheckInterval); err == nil {
config.Redis.HealthCheckInterval = val
}
}
if enableRetryOnError := os.Getenv("REDIS_ENABLE_RETRY_ON_ERROR"); enableRetryOnError != "" {
config.Redis.EnableRetryOnError = enableRetryOnError == "true" || enableRetryOnError == "1"
}
// 处理邮件配置
if emailEnabled := os.Getenv("EMAIL_ENABLED"); emailEnabled != "" {
config.Email.Enabled = emailEnabled == "true" || emailEnabled == "True" || emailEnabled == "TRUE" || emailEnabled == "1"

View File

@@ -11,8 +11,8 @@ import (
)
var (
// dbInstance 全局数据库实例
dbInstance *gorm.DB
// dbInstance 全局数据库实例(使用 *DB 封装)
dbInstance *DB
// once 确保只初始化一次
once sync.Once
// initError 初始化错误
@@ -33,7 +33,16 @@ func Init(cfg config.DatabaseConfig, logger *zap.Logger) error {
}
// GetDB 获取数据库实例(线程安全)
// 返回 *gorm.DB 以保持向后兼容
func GetDB() (*gorm.DB, error) {
if dbInstance == nil {
return nil, fmt.Errorf("数据库未初始化,请先调用 database.Init()")
}
return dbInstance.DB, nil
}
// GetDBWrapper 获取数据库封装实例(包含连接池统计功能)
func GetDBWrapper() (*DB, error) {
if dbInstance == nil {
return nil, fmt.Errorf("数据库未初始化,请先调用 database.Init()")
}
@@ -41,6 +50,7 @@ func GetDB() (*gorm.DB, error) {
}
// MustGetDB 获取数据库实例如果未初始化则panic
// 返回 *gorm.DB 以保持向后兼容
func MustGetDB() *gorm.DB {
db, err := GetDB()
if err != nil {
@@ -103,10 +113,5 @@ func Close() error {
return nil
}
sqlDB, err := dbInstance.DB()
if err != nil {
return err
}
return sqlDB.Close()
return dbInstance.Close()
}

View File

@@ -14,8 +14,25 @@ func TestAutoMigrate_WithSQLite(t *testing.T) {
if err != nil {
t.Fatalf("open sqlite err: %v", err)
}
dbInstance = db
defer func() { dbInstance = nil }()
// 创建临时的 *DB 包装器用于测试
// 注意:这里不需要真正的连接池功能,只是测试 AutoMigrate
sqlDB, err := db.DB()
if err != nil {
t.Fatalf("get sql.DB err: %v", err)
}
tempDB := &DB{
DB: db,
sqlDB: sqlDB,
}
// 保存原始实例
originalDB := dbInstance
defer func() { dbInstance = originalDB }()
// 替换为测试实例
dbInstance = tempDB
logger := zaptest.NewLogger(t)
if err := AutoMigrate(logger); err != nil {

View File

@@ -1,9 +1,12 @@
package database
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"sync"
"time"
"carrotskin/pkg/config"
@@ -13,8 +16,31 @@ import (
"gorm.io/gorm/logger"
)
// DBStats 数据库连接池统计信息
type DBStats struct {
MaxOpenConns int // 最大打开连接数
OpenConns int // 当前打开的连接数
InUseConns int // 正在使用的连接数
IdleConns int // 空闲连接数
WaitCount int64 // 等待连接的总次数
WaitDuration time.Duration // 等待连接的总时间
LastPingTime time.Time // 上次探活时间
LastPingSuccess bool // 上次探活是否成功
mu sync.RWMutex // 保护 LastPingTime 和 LastPingSuccess
}
// DB 数据库封装,包含连接池统计
type DB struct {
*gorm.DB
stats *DBStats
sqlDB *sql.DB
healthCh chan struct{} // 健康检查信号通道
closeCh chan struct{} // 关闭信号通道
wg sync.WaitGroup
}
// New 创建新的PostgreSQL数据库连接
func New(cfg config.DatabaseConfig) (*gorm.DB, error) {
func New(cfg config.DatabaseConfig) (*DB, error) {
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s TimeZone=%s",
cfg.Host,
cfg.Port,
@@ -25,11 +51,11 @@ func New(cfg config.DatabaseConfig) (*gorm.DB, error) {
cfg.Timezone,
)
// 配置慢查询监控
// 配置慢查询监控 - 优化从200ms调整为100ms
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond, // 慢查询阈值:200ms
SlowThreshold: 100 * time.Millisecond, // 慢查询阈值:100ms(优化后)
LogLevel: logger.Warn, // 只记录警告和错误
IgnoreRecordNotFoundError: true, // 忽略记录未找到错误
Colorful: false, // 生产环境禁用彩色
@@ -79,12 +105,131 @@ func New(cfg config.DatabaseConfig) (*gorm.DB, error) {
sqlDB.SetConnMaxLifetime(connMaxLifetime)
sqlDB.SetConnMaxIdleTime(connMaxIdleTime)
// 测试连接
if err := sqlDB.Ping(); err != nil {
// 测试连接(带重试机制)
if err := pingWithRetry(sqlDB, 3, 2*time.Second); err != nil {
return nil, fmt.Errorf("数据库连接测试失败: %w", err)
}
return db, nil
// 创建数据库封装
database := &DB{
DB: db,
sqlDB: sqlDB,
stats: &DBStats{},
healthCh: make(chan struct{}, 1),
closeCh: make(chan struct{}),
}
// 初始化统计信息
database.updateStats()
// 启动定期健康检查
database.startHealthCheck(30 * time.Second)
log.Println("[Database] PostgreSQL连接池初始化成功")
log.Printf("[Database] 连接池配置: MaxIdleConns=%d, MaxOpenConns=%d, ConnMaxLifetime=%v, ConnMaxIdleTime=%v",
maxIdleConns, maxOpenConns, connMaxLifetime, connMaxIdleTime)
return database, nil
}
// pingWithRetry 带重试的Ping操作
func pingWithRetry(sqlDB *sql.DB, maxRetries int, retryInterval time.Duration) error {
var err error
for i := 0; i < maxRetries; i++ {
if err = sqlDB.Ping(); err == nil {
return nil
}
if i < maxRetries-1 {
log.Printf("[Database] Ping失败%v 后重试 (%d/%d): %v", retryInterval, i+1, maxRetries, err)
time.Sleep(retryInterval)
}
}
return err
}
// startHealthCheck 启动定期健康检查
func (d *DB) startHealthCheck(interval time.Duration) {
d.wg.Add(1)
go func() {
defer d.wg.Done()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
d.ping()
case <-d.healthCh:
d.ping()
case <-d.closeCh:
return
}
}
}()
}
// ping 执行连接健康检查
func (d *DB) ping() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := d.sqlDB.PingContext(ctx)
d.stats.mu.Lock()
d.stats.LastPingTime = time.Now()
d.stats.LastPingSuccess = err == nil
d.stats.mu.Unlock()
if err != nil {
log.Printf("[Database] 连接健康检查失败: %v", err)
} else {
log.Println("[Database] 连接健康检查成功")
}
}
// GetStats 获取连接池统计信息
func (d *DB) GetStats() DBStats {
d.stats.mu.RLock()
defer d.stats.mu.RUnlock()
// 从底层获取实时统计
stats := d.sqlDB.Stats()
d.stats.MaxOpenConns = stats.MaxOpenConnections
d.stats.OpenConns = stats.OpenConnections
d.stats.InUseConns = stats.InUse
d.stats.IdleConns = stats.Idle
d.stats.WaitCount = stats.WaitCount
d.stats.WaitDuration = stats.WaitDuration
return *d.stats
}
// updateStats 初始化统计信息
func (d *DB) updateStats() {
stats := d.sqlDB.Stats()
d.stats.MaxOpenConns = stats.MaxOpenConnections
d.stats.OpenConns = stats.OpenConnections
d.stats.InUseConns = stats.InUse
d.stats.IdleConns = stats.Idle
}
// LogStats 记录连接池状态日志
func (d *DB) LogStats() {
stats := d.GetStats()
log.Printf("[Database] 连接池状态: Open=%d, Idle=%d, InUse=%d, WaitCount=%d, WaitDuration=%v, LastPing=%v (%v)",
stats.OpenConns, stats.IdleConns, stats.InUseConns, stats.WaitCount, stats.WaitDuration,
stats.LastPingTime.Format("2006-01-02 15:04:05"), stats.LastPingSuccess)
}
// Close 关闭数据库连接
func (d *DB) Close() error {
close(d.closeCh)
d.wg.Wait()
return d.sqlDB.Close()
}
// WithTimeout 创建带有超时控制的上下文
func WithTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
return context.WithTimeout(parent, timeout)
}
// GetDSN 获取数据源名称
@@ -99,9 +244,3 @@ func GetDSN(cfg config.DatabaseConfig) string {
cfg.Timezone,
)
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"sync"
"time"
"carrotskin/pkg/config"
@@ -12,23 +13,39 @@ import (
"go.uber.org/zap"
)
// Client Redis客户端包装
// Client Redis客户端包装(包含连接池统计和健康检查)
type Client struct {
*redis.Client
logger *zap.Logger
*redis.Client // 嵌入原始Redis客户端
logger *zap.Logger // 日志记录器
stats *RedisStats // 连接池统计信息
healthCheckDone chan struct{} // 健康检查完成信号
closeCh chan struct{} // 关闭信号通道
wg sync.WaitGroup // 等待组
}
// New 创建Redis客户端
// RedisStats Redis连接池统计信息
type RedisStats struct {
PoolSize int // 连接池大小
IdleConns int // 空闲连接数
ActiveConns int // 活跃连接数
StaleConns int // 过期连接数
TotalConns int // 总连接数
LastPingTime time.Time // 上次探活时间
LastPingSuccess bool // 上次探活是否成功
mu sync.RWMutex // 保护统计信息
}
// New 创建Redis客户端带健康检查和优化配置
func New(cfg config.RedisConfig, logger *zap.Logger) (*Client, error) {
// 设置默认值
poolSize := cfg.PoolSize
if poolSize <= 0 {
poolSize = 10
poolSize = 16 // 优化:提高默认连接池大小
}
minIdleConns := cfg.MinIdleConns
if minIdleConns <= 0 {
minIdleConns = 5
minIdleConns = 8 // 优化:提高最小空闲连接数
}
maxRetries := cfg.MaxRetries
@@ -58,10 +75,15 @@ func New(cfg config.RedisConfig, logger *zap.Logger) (*Client, error) {
connMaxIdleTime := cfg.ConnMaxIdleTime
if connMaxIdleTime <= 0 {
connMaxIdleTime = 30 * time.Minute
connMaxIdleTime = 10 * time.Minute // 优化:减少空闲连接超时
}
// 创建Redis客户端
connMaxLifetime := cfg.ConnMaxLifetime
if connMaxLifetime <= 0 {
connMaxLifetime = 30 * time.Minute // 新增:连接最大生命周期
}
// 创建Redis客户端带优化配置
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
Password: cfg.Password,
@@ -74,125 +96,254 @@ func New(cfg config.RedisConfig, logger *zap.Logger) (*Client, error) {
WriteTimeout: writeTimeout,
PoolTimeout: poolTimeout,
ConnMaxIdleTime: connMaxIdleTime,
ConnMaxLifetime: connMaxLifetime,
})
// 测试连接
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := rdb.Ping(ctx).Err(); err != nil {
// 测试连接(带重试机制)
if err := pingWithRetry(rdb, 3, 2*time.Second); err != nil {
return nil, fmt.Errorf("Redis连接失败: %w", err)
}
// 创建客户端包装
client := &Client{
Client: rdb,
logger: logger,
stats: &RedisStats{},
healthCheckDone: make(chan struct{}),
closeCh: make(chan struct{}),
}
// 初始化统计信息
client.updateStats()
// 启动定期健康检查
healthCheckInterval := cfg.HealthCheckInterval
if healthCheckInterval <= 0 {
healthCheckInterval = 30 * time.Second
}
client.startHealthCheck(healthCheckInterval)
logger.Info("Redis连接成功",
zap.String("host", cfg.Host),
zap.Int("port", cfg.Port),
zap.Int("database", cfg.Database),
zap.Int("pool_size", poolSize),
zap.Int("min_idle_conns", minIdleConns),
)
return &Client{
Client: rdb,
logger: logger,
}, nil
return client, nil
}
// pingWithRetry 带重试的Ping操作
func pingWithRetry(rdb *redis.Client, maxRetries int, retryInterval time.Duration) error {
var err error
for i := 0; i < maxRetries; i++ {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
err = rdb.Ping(ctx).Err()
cancel()
if err == nil {
return nil
}
if i < maxRetries-1 {
time.Sleep(retryInterval)
}
}
return err
}
// startHealthCheck 启动定期健康检查
func (c *Client) startHealthCheck(interval time.Duration) {
c.wg.Add(1)
go func() {
defer c.wg.Done()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
c.doHealthCheck()
case <-c.closeCh:
return
}
}
}()
}
// doHealthCheck 执行健康检查
func (c *Client) doHealthCheck() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 更新统计信息
c.updateStats()
// 执行Ping检查
err := c.Client.Ping(ctx).Err()
c.stats.mu.Lock()
c.stats.LastPingTime = time.Now()
c.stats.LastPingSuccess = err == nil
c.stats.mu.Unlock()
if err != nil {
c.logger.Warn("Redis健康检查失败", zap.Error(err))
} else {
c.logger.Debug("Redis健康检查成功")
}
}
// updateStats 更新连接池统计信息
func (c *Client) updateStats() {
// 获取底层连接池统计信息
stats := c.Client.PoolStats()
c.stats.mu.Lock()
c.stats.PoolSize = c.Client.Options().PoolSize
c.stats.IdleConns = int(stats.IdleConns)
c.stats.ActiveConns = int(stats.TotalConns) - int(stats.IdleConns)
c.stats.TotalConns = int(stats.TotalConns)
c.stats.StaleConns = int(stats.StaleConns)
c.stats.mu.Unlock()
}
// GetStats 获取连接池统计信息
func (c *Client) GetStats() RedisStats {
c.stats.mu.RLock()
defer c.stats.mu.RUnlock()
return RedisStats{
PoolSize: c.stats.PoolSize,
IdleConns: c.stats.IdleConns,
ActiveConns: c.stats.ActiveConns,
StaleConns: c.stats.StaleConns,
TotalConns: c.stats.TotalConns,
LastPingTime: c.stats.LastPingTime,
LastPingSuccess: c.stats.LastPingSuccess,
}
}
// LogStats 记录连接池状态日志
func (c *Client) LogStats() {
stats := c.GetStats()
c.logger.Info("Redis连接池状态",
zap.Int("pool_size", stats.PoolSize),
zap.Int("idle_conns", stats.IdleConns),
zap.Int("active_conns", stats.ActiveConns),
zap.Int("total_conns", stats.TotalConns),
zap.Int("stale_conns", stats.StaleConns),
zap.Bool("last_ping_success", stats.LastPingSuccess),
)
}
// Ping 验证Redis连接带超时控制
func (c *Client) Ping(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
return c.Client.Ping(ctx).Err()
}
// Close 关闭Redis连接
func (c *Client) Close() error {
// 停止健康检查
close(c.closeCh)
c.wg.Wait()
c.logger.Info("正在关闭Redis连接")
c.LogStats() // 关闭前记录最终状态
return c.Client.Close()
}
// Set 设置键值对(带过期时间)
// ===== 以下是封装的便捷方法,用于返回 (value, error) 格式 =====
// Set 设置键值对(带过期时间)- 封装版本
func (c *Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
return c.Client.Set(ctx, key, value, expiration).Err()
}
// Get 获取键值
// Get 获取键值 - 封装版本
func (c *Client) Get(ctx context.Context, key string) (string, error) {
return c.Client.Get(ctx, key).Result()
}
// Del 删除键
// Del 删除键 - 封装版本
func (c *Client) Del(ctx context.Context, keys ...string) error {
return c.Client.Del(ctx, keys...).Err()
}
// Exists 检查键是否存在
// Exists 检查键是否存在 - 封装版本
func (c *Client) Exists(ctx context.Context, keys ...string) (int64, error) {
return c.Client.Exists(ctx, keys...).Result()
}
// Expire 设置键的过期时间
// Expire 设置键的过期时间 - 封装版本
func (c *Client) Expire(ctx context.Context, key string, expiration time.Duration) error {
return c.Client.Expire(ctx, key, expiration).Err()
}
// TTL 获取键的剩余过期时间
// TTL 获取键的剩余过期时间 - 封装版本
func (c *Client) TTL(ctx context.Context, key string) (time.Duration, error) {
return c.Client.TTL(ctx, key).Result()
}
// Incr 自增
// Incr 自增 - 封装版本
func (c *Client) Incr(ctx context.Context, key string) (int64, error) {
return c.Client.Incr(ctx, key).Result()
}
// Decr 自减
// Decr 自减 - 封装版本
func (c *Client) Decr(ctx context.Context, key string) (int64, error) {
return c.Client.Decr(ctx, key).Result()
}
// HSet 设置哈希字段
// HSet 设置哈希字段 - 封装版本
func (c *Client) HSet(ctx context.Context, key string, values ...interface{}) error {
return c.Client.HSet(ctx, key, values...).Err()
}
// HGet 获取哈希字段
// HGet 获取哈希字段 - 封装版本
func (c *Client) HGet(ctx context.Context, key, field string) (string, error) {
return c.Client.HGet(ctx, key, field).Result()
}
// HGetAll 获取所有哈希字段
// HGetAll 获取所有哈希字段 - 封装版本
func (c *Client) HGetAll(ctx context.Context, key string) (map[string]string, error) {
return c.Client.HGetAll(ctx, key).Result()
}
// HDel 删除哈希字段
// HDel 删除哈希字段 - 封装版本
func (c *Client) HDel(ctx context.Context, key string, fields ...string) error {
return c.Client.HDel(ctx, key, fields...).Err()
}
// SAdd 添加集合成员
// SAdd 添加集合成员 - 封装版本
func (c *Client) SAdd(ctx context.Context, key string, members ...interface{}) error {
return c.Client.SAdd(ctx, key, members...).Err()
}
// SMembers 获取集合所有成员
// SMembers 获取集合所有成员 - 封装版本
func (c *Client) SMembers(ctx context.Context, key string) ([]string, error) {
return c.Client.SMembers(ctx, key).Result()
}
// SRem 删除集合成员
// SRem 删除集合成员 - 封装版本
func (c *Client) SRem(ctx context.Context, key string, members ...interface{}) error {
return c.Client.SRem(ctx, key, members...).Err()
}
// SIsMember 检查是否是集合成员
// SIsMember 检查是否是集合成员 - 封装版本
func (c *Client) SIsMember(ctx context.Context, key string, member interface{}) (bool, error) {
return c.Client.SIsMember(ctx, key, member).Result()
}
// ZAdd 添加有序集合成员
// ZAdd 添加有序集合成员 - 封装版本
func (c *Client) ZAdd(ctx context.Context, key string, members ...redis.Z) error {
return c.Client.ZAdd(ctx, key, members...).Err()
}
// ZRange 获取有序集合范围内的成员
// ZRange 获取有序集合范围内的成员 - 封装版本
func (c *Client) ZRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
return c.Client.ZRange(ctx, key, start, stop).Result()
}
// ZRem 删除有序集合成员
// ZRem 删除有序集合成员 - 封装版本
func (c *Client) ZRem(ctx context.Context, key string, members ...interface{}) error {
return c.Client.ZRem(ctx, key, members...).Err()
}
@@ -207,6 +358,7 @@ func (c *Client) TxPipeline() redis.Pipeliner {
return c.Client.TxPipeline()
}
// Nil 检查错误是否为Nilkey不存在
func (c *Client) Nil(err error) bool {
return errors.Is(err, redis.Nil)
}