2026-03-09 21:28:58 +08:00
|
|
|
package redis
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/alicebob/miniredis/v2"
|
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
|
|
|
|
|
|
"carrot_bbs/internal/config"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Client Redis客户端
|
|
|
|
|
type Client struct {
|
|
|
|
|
rdb *redis.Client
|
|
|
|
|
isMiniRedis bool
|
|
|
|
|
mr *miniredis.Miniredis
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New 创建Redis客户端
|
|
|
|
|
func New(cfg *config.RedisConfig) (*Client, error) {
|
|
|
|
|
switch cfg.Type {
|
|
|
|
|
case "miniredis":
|
|
|
|
|
// 启动内嵌Redis模拟
|
|
|
|
|
mr, err := miniredis.Run()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to start miniredis: %w", err)
|
|
|
|
|
}
|
|
|
|
|
rdb := redis.NewClient(&redis.Options{
|
|
|
|
|
Addr: mr.Addr(),
|
|
|
|
|
Password: "",
|
|
|
|
|
DB: 0,
|
|
|
|
|
})
|
|
|
|
|
return &Client{
|
|
|
|
|
rdb: rdb,
|
|
|
|
|
isMiniRedis: true,
|
|
|
|
|
mr: mr,
|
|
|
|
|
}, nil
|
|
|
|
|
case "redis":
|
|
|
|
|
// 使用真实Redis
|
|
|
|
|
rdb := redis.NewClient(&redis.Options{
|
|
|
|
|
Addr: cfg.Redis.Addr(),
|
|
|
|
|
Password: cfg.Redis.Password,
|
|
|
|
|
DB: cfg.Redis.DB,
|
|
|
|
|
PoolSize: cfg.PoolSize,
|
|
|
|
|
})
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
if err := rdb.Ping(ctx).Err(); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return &Client{rdb: rdb, isMiniRedis: false}, nil
|
|
|
|
|
default:
|
|
|
|
|
// 默认使用miniredis
|
|
|
|
|
mr, err := miniredis.Run()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to start miniredis: %w", err)
|
|
|
|
|
}
|
|
|
|
|
rdb := redis.NewClient(&redis.Options{
|
|
|
|
|
Addr: mr.Addr(),
|
|
|
|
|
})
|
|
|
|
|
return &Client{
|
|
|
|
|
rdb: rdb,
|
|
|
|
|
isMiniRedis: true,
|
|
|
|
|
mr: mr,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get 获取值
|
|
|
|
|
func (c *Client) Get(ctx context.Context, key string) (string, error) {
|
|
|
|
|
return c.rdb.Get(ctx, key).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set 设置值
|
|
|
|
|
func (c *Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
|
|
|
|
return c.rdb.Set(ctx, key, value, expiration).Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Del 删除键
|
|
|
|
|
func (c *Client) Del(ctx context.Context, keys ...string) error {
|
|
|
|
|
return c.rdb.Del(ctx, keys...).Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Exists 检查键是否存在
|
|
|
|
|
func (c *Client) Exists(ctx context.Context, keys ...string) (int64, error) {
|
|
|
|
|
return c.rdb.Exists(ctx, keys...).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Incr 递增
|
|
|
|
|
func (c *Client) Incr(ctx context.Context, key string) (int64, error) {
|
|
|
|
|
return c.rdb.Incr(ctx, key).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expire 设置过期时间
|
|
|
|
|
func (c *Client) Expire(ctx context.Context, key string, expiration time.Duration) (bool, error) {
|
|
|
|
|
return c.rdb.Expire(ctx, key, expiration).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetClient 获取原生客户端
|
|
|
|
|
func (c *Client) GetClient() *redis.Client {
|
|
|
|
|
return c.rdb
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close 关闭连接
|
|
|
|
|
func (c *Client) Close() error {
|
|
|
|
|
if err := c.rdb.Close(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if c.mr != nil {
|
|
|
|
|
c.mr.Close()
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsMiniRedis 返回是否是miniredis
|
|
|
|
|
func (c *Client) IsMiniRedis() bool {
|
|
|
|
|
return c.isMiniRedis
|
|
|
|
|
}
|
2026-03-12 08:38:14 +08:00
|
|
|
|
|
|
|
|
// ==================== Hash 操作 ====================
|
|
|
|
|
|
|
|
|
|
// HSet 设置 Hash 字段
|
|
|
|
|
func (c *Client) HSet(ctx context.Context, key string, field string, value interface{}) error {
|
|
|
|
|
return c.rdb.HSet(ctx, key, field, value).Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HMSet 批量设置 Hash 字段
|
|
|
|
|
func (c *Client) HMSet(ctx context.Context, key string, values map[string]interface{}) error {
|
|
|
|
|
return c.rdb.HMSet(ctx, key, values).Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HGet 获取 Hash 字段值
|
|
|
|
|
func (c *Client) HGet(ctx context.Context, key string, field string) (string, error) {
|
|
|
|
|
return c.rdb.HGet(ctx, key, field).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HMGet 批量获取 Hash 字段值
|
|
|
|
|
func (c *Client) HMGet(ctx context.Context, key string, fields ...string) ([]interface{}, error) {
|
|
|
|
|
return c.rdb.HMGet(ctx, key, fields...).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HGetAll 获取 Hash 所有字段
|
|
|
|
|
func (c *Client) HGetAll(ctx context.Context, key string) (map[string]string, error) {
|
|
|
|
|
return c.rdb.HGetAll(ctx, key).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HDel 删除 Hash 字段
|
|
|
|
|
func (c *Client) HDel(ctx context.Context, key string, fields ...string) error {
|
|
|
|
|
return c.rdb.HDel(ctx, key, fields...).Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HExists 检查 Hash 字段是否存在
|
|
|
|
|
func (c *Client) HExists(ctx context.Context, key string, field string) (bool, error) {
|
|
|
|
|
return c.rdb.HExists(ctx, key, field).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HLen 获取 Hash 字段数量
|
|
|
|
|
func (c *Client) HLen(ctx context.Context, key string) (int64, error) {
|
|
|
|
|
return c.rdb.HLen(ctx, key).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== Sorted Set 操作 ====================
|
|
|
|
|
|
|
|
|
|
// ZAdd 添加 Sorted Set 成员
|
|
|
|
|
func (c *Client) ZAdd(ctx context.Context, key string, score float64, member string) error {
|
|
|
|
|
return c.rdb.ZAdd(ctx, key, redis.Z{Score: score, Member: member}).Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZAddArgs 批量添加 Sorted Set 成员
|
|
|
|
|
func (c *Client) ZAddArgs(ctx context.Context, key string, members ...redis.Z) error {
|
|
|
|
|
return c.rdb.ZAdd(ctx, key, members...).Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZRangeByScore 按分数范围获取成员(升序)
|
|
|
|
|
func (c *Client) ZRangeByScore(ctx context.Context, key string, min, max string, offset, count int64) ([]string, error) {
|
|
|
|
|
return c.rdb.ZRangeByScore(ctx, key, &redis.ZRangeBy{
|
|
|
|
|
Min: min,
|
|
|
|
|
Max: max,
|
|
|
|
|
Offset: offset,
|
|
|
|
|
Count: count,
|
|
|
|
|
}).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZRevRangeByScore 按分数范围获取成员(降序)
|
|
|
|
|
func (c *Client) ZRevRangeByScore(ctx context.Context, key string, max, min string, offset, count int64) ([]string, error) {
|
|
|
|
|
return c.rdb.ZRevRangeByScore(ctx, key, &redis.ZRangeBy{
|
|
|
|
|
Min: min,
|
|
|
|
|
Max: max,
|
|
|
|
|
Offset: offset,
|
|
|
|
|
Count: count,
|
|
|
|
|
}).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZRange 获取指定范围的成员(升序)
|
|
|
|
|
func (c *Client) ZRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
|
|
|
|
|
return c.rdb.ZRange(ctx, key, start, stop).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZRevRange 获取指定范围的成员(降序)
|
|
|
|
|
func (c *Client) ZRevRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
|
|
|
|
|
return c.rdb.ZRevRange(ctx, key, start, stop).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZRem 删除 Sorted Set 成员
|
|
|
|
|
func (c *Client) ZRem(ctx context.Context, key string, members ...interface{}) error {
|
|
|
|
|
return c.rdb.ZRem(ctx, key, members...).Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZScore 获取成员分数
|
|
|
|
|
func (c *Client) ZScore(ctx context.Context, key string, member string) (float64, error) {
|
|
|
|
|
return c.rdb.ZScore(ctx, key, member).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZCard 获取 Sorted Set 成员数量
|
|
|
|
|
func (c *Client) ZCard(ctx context.Context, key string) (int64, error) {
|
|
|
|
|
return c.rdb.ZCard(ctx, key).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ZCount 统计分数范围内的成员数量
|
|
|
|
|
func (c *Client) ZCount(ctx context.Context, key string, min, max string) (int64, error) {
|
|
|
|
|
return c.rdb.ZCount(ctx, key, min, max).Result()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== Pipeline 操作 ====================
|
|
|
|
|
|
|
|
|
|
// Pipeliner Pipeline 接口(使用 redis 库原生接口)
|
|
|
|
|
type Pipeliner = redis.Pipeliner
|
|
|
|
|
|
|
|
|
|
// Pipeline 创建 Pipeline
|
|
|
|
|
func (c *Client) Pipeline() Pipeliner {
|
|
|
|
|
return c.rdb.Pipeline()
|
|
|
|
|
}
|