package cache import ( "context" "encoding/json" "errors" "strings" "time" "github.com/redis/go-redis/v9" "server/internal/config" ) type Caches struct { rdb *redis.Client keyPrefix string } func NewCaches(rdb *redis.Client, cfg *config.Config) *Caches { return &Caches{ rdb: rdb, keyPrefix: cfg.Redis.KeyPrefix, } } func (c *Caches) key(key string) string { if c.keyPrefix == "" { return key } return c.keyPrefix + key } func (c *Caches) Set(ctx context.Context, key string, value any, expiration time.Duration) error { return c.rdb.Set(ctx, c.key(key), value, expiration).Err() } func (c *Caches) Get(ctx context.Context, key string) (string, bool, error) { val, err := c.rdb.Get(ctx, c.key(key)).Result() if errors.Is(err, redis.Nil) { return "", false, nil } if err != nil { return "", false, err } return val, true, nil } func (c *Caches) SetJSON(ctx context.Context, key string, value any, expiration time.Duration) error { b, err := json.Marshal(value) if err != nil { return err } return c.rdb.Set(ctx, c.key(key), b, expiration).Err() } func GetJSON[T any](ctx context.Context, c *Caches, key string) (*T, bool, error) { val, err := c.rdb.Get(ctx, c.key(key)).Bytes() if errors.Is(err, redis.Nil) { return nil, false, nil } if err != nil { return nil, false, err } var result T if err = json.Unmarshal(val, &result); err != nil { return nil, false, err } return &result, true, nil } func GetOrSetJSON[T any](ctx context.Context, c *Caches, key string, expiration time.Duration, fetch func() (T, error)) (T, error) { var zero T cacheKey := c.key(key) val, err := c.rdb.Get(ctx, cacheKey).Bytes() if err == nil { var result T if err = json.Unmarshal(val, &result); err == nil { return result, nil } } if !errors.Is(err, redis.Nil) { return zero, err } result, err := fetch() if err != nil { return zero, err } b, err := json.Marshal(result) if err != nil { return zero, err } _ = c.rdb.Set(ctx, cacheKey, b, expiration).Err() return result, nil } func (c *Caches) Del(ctx context.Context, keys ...string) error { for i := range keys { keys[i] = c.key(keys[i]) } return c.rdb.Del(ctx, keys...).Err() } func (c *Caches) Exists(ctx context.Context, key string) (bool, error) { n, err := c.rdb.Exists(ctx, c.key(key)).Result() if err != nil { return false, err } return n > 0, nil } // SAdd 向 Set 中添加成员 func (c *Caches) SAdd(ctx context.Context, key string, members ...any) error { return c.rdb.SAdd(ctx, c.key(key), members...).Err() } // SMembers 获取 Set 中所有成员 func (c *Caches) SMembers(ctx context.Context, key string) ([]string, error) { return c.rdb.SMembers(ctx, c.key(key)).Result() } // DelSetMembers 删除 Set 中记录的所有成员对应的缓存 key,并删除 Set 本身 func (c *Caches) DelSetMembers(ctx context.Context, key string) error { members, err := c.SMembers(ctx, key) if err != nil { return err } keys := append(members, key) return c.Del(ctx, keys...) } // SRem 从 Set 中移除成员 func (c *Caches) SRem(ctx context.Context, key string, members ...any) error { return c.rdb.SRem(ctx, c.key(key), members...).Err() } // SIsMember 判断 member 是否在 Set 中 func (c *Caches) SIsMember(ctx context.Context, key string, member any) (bool, error) { return c.rdb.SIsMember(ctx, c.key(key), member).Result() } // MGet 批量获取多个 key 的值 func (c *Caches) MGet(ctx context.Context, keys ...string) (map[string]string, error) { if len(keys) == 0 { return map[string]string{}, nil } redisKeys := make([]string, len(keys)) for i, key := range keys { redisKeys[i] = c.key(key) } values, err := c.rdb.MGet(ctx, redisKeys...).Result() if err != nil { return nil, err } result := make(map[string]string, len(keys)) for i, value := range values { if value == nil { continue } str, ok := value.(string) if !ok { continue } result[keys[i]] = str } return result, nil } // DelByPrefix 批量删除指定前缀的缓存键 func (c *Caches) DelByPrefix(ctx context.Context, prefix string) error { prefix = c.key(prefix) if !strings.HasSuffix(prefix, "*") { prefix += "*" } var cursor uint64 var keys []string var err error for { keys, cursor, err = c.rdb.Scan(ctx, cursor, prefix, 100).Result() if err != nil { return err } if len(keys) > 0 { if err = c.rdb.Del(ctx, keys...).Err(); err != nil { return err } } if cursor == 0 { break } } return nil } func (c *Caches) Expire(ctx context.Context, key string, expiration time.Duration) error { return c.rdb.Expire(ctx, c.keyPrefix+key, expiration).Err() }