220 lines
4.6 KiB
Go
220 lines
4.6 KiB
Go
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()
|
||
}
|