github.com/binbinly/pkg@v0.0.11-0.20240321014439-f4fbf666eb0f/cache/redis.go (about)

     1  package cache
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  	"time"
     7  
     8  	"github.com/binbinly/pkg/logger"
     9  	"github.com/pkg/errors"
    10  	"github.com/redis/go-redis/v9"
    11  )
    12  
    13  type redisCache struct {
    14  	client *redis.Client
    15  	opts   Options
    16  }
    17  
    18  // NewRedisCache new redis cache
    19  func NewRedisCache(client *redis.Client, opts ...Option) Cache {
    20  	o := NewOptions(opts...)
    21  	return &redisCache{
    22  		client: client,
    23  		opts:   o,
    24  	}
    25  }
    26  
    27  // Set cache
    28  func (c *redisCache) Set(ctx context.Context, key string, val any, expiration time.Duration) error {
    29  	buf, err := c.opts.codec.Marshal(val)
    30  	if err != nil {
    31  		return errors.Wrapf(err, "[cache] marshal data err, value is %+v", val)
    32  	}
    33  
    34  	if expiration == 0 {
    35  		expiration = c.opts.expire
    36  	}
    37  
    38  	if err = c.client.Set(ctx, c.buildKey(key), buf, expiration).Err(); err != nil {
    39  		return errors.Wrapf(err, "[cache] redis set error")
    40  	}
    41  	return nil
    42  }
    43  
    44  // Get cache
    45  func (c *redisCache) Get(ctx context.Context, key string, val any) error {
    46  	cacheKey := c.buildKey(key)
    47  	data, err := c.client.Get(ctx, cacheKey).Bytes()
    48  	if err != nil && err != redis.Nil {
    49  		return errors.Wrapf(err, "[cache] get data error from redis, key is %+v", cacheKey)
    50  	}
    51  
    52  	// 防止data为空时,Unmarshal报错
    53  	if string(data) == "" {
    54  		return nil
    55  	}
    56  	if string(data) == NotFoundPlaceholder {
    57  		return ErrPlaceholder
    58  	}
    59  
    60  	if err = c.opts.codec.Unmarshal(data, val); err != nil {
    61  		return errors.Wrapf(err, "[cache] unmarshal data error, key=%s, cacheKey=%s type=%v, data=%+v ",
    62  			key, cacheKey, reflect.TypeOf(val), string(data))
    63  	}
    64  	return nil
    65  }
    66  
    67  // MultiSet 批量设置缓存
    68  func (c *redisCache) MultiSet(ctx context.Context, m map[string]any, expiration time.Duration) error {
    69  	if len(m) == 0 {
    70  		return nil
    71  	}
    72  	if expiration == 0 {
    73  		expiration = c.opts.expire
    74  	}
    75  	// key-value是成对的,所以这里的容量是map的2倍
    76  	paris := make([]any, 0, 2*len(m))
    77  	for key, value := range m {
    78  		buf, err := c.opts.codec.Marshal(value)
    79  		if err != nil {
    80  			continue
    81  		}
    82  		paris = append(paris, []byte(c.buildKey(key)))
    83  		paris = append(paris, buf)
    84  	}
    85  
    86  	if err := c.client.MSet(ctx, paris...).Err(); err != nil {
    87  		return errors.Wrapf(err, "[cache] redis multi set error")
    88  	}
    89  	// 设置过期时间
    90  	pipe := c.client.Pipeline()
    91  	for i := 0; i < len(paris); i = i + 2 {
    92  		switch paris[i].(type) {
    93  		case []byte:
    94  			pipe.Expire(ctx, string(paris[i].([]byte)), expiration)
    95  		}
    96  	}
    97  	if _, err := pipe.Exec(ctx); err != nil {
    98  		return errors.Wrapf(err, "[cache] redis multi set expire error")
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // MultiGet 批量获取缓存
   105  func (c *redisCache) MultiGet(ctx context.Context, keys []string, value any, obj func() any) error {
   106  	if len(keys) == 0 {
   107  		return nil
   108  	}
   109  	cacheKeys := make([]string, len(keys))
   110  	for index, key := range keys {
   111  		cacheKeys[index] = c.buildKey(key)
   112  	}
   113  	values, err := c.client.MGet(ctx, cacheKeys...).Result()
   114  	if err != nil {
   115  		return errors.Wrapf(err, "[cache] redis MGet error, keys is %+v", keys)
   116  	}
   117  
   118  	// 通过反射注入到map
   119  	valueMap := reflect.ValueOf(value)
   120  	for i, val := range values {
   121  		if val == nil {
   122  			continue
   123  		}
   124  		object := obj()
   125  		if val.(string) == NotFoundPlaceholder {
   126  			valueMap.SetMapIndex(reflect.ValueOf(keys[i]), reflect.ValueOf(object))
   127  			continue
   128  		}
   129  
   130  		if err = c.opts.codec.Unmarshal([]byte(val.(string)), &object); err != nil {
   131  			logger.Warnf("[cache] unmarshal data error: %+v, key=%s, type=%v val=%v", err,
   132  				keys[i], reflect.TypeOf(val), val)
   133  			continue
   134  		}
   135  		valueMap.SetMapIndex(reflect.ValueOf(keys[i]), reflect.ValueOf(object))
   136  	}
   137  	return nil
   138  }
   139  
   140  // Del cache
   141  func (c *redisCache) Del(ctx context.Context, keys ...string) error {
   142  	if len(keys) == 0 {
   143  		return nil
   144  	}
   145  
   146  	// 批量构建cacheKey
   147  	cacheKeys := make([]string, len(keys))
   148  	for index, key := range keys {
   149  		cacheKeys[index] = c.buildKey(key)
   150  	}
   151  
   152  	if err := c.client.Del(ctx, cacheKeys...).Err(); err != nil {
   153  		return errors.Wrapf(err, "[cache] redis delete error, keys is %+v", keys)
   154  	}
   155  	return nil
   156  }
   157  
   158  // SetCacheWithNotFound 设置空值
   159  func (c *redisCache) SetCacheWithNotFound(ctx context.Context, key string) error {
   160  	return c.client.Set(ctx, c.buildKey(key), NotFoundPlaceholder, DefaultNotFoundExpireTime).Err()
   161  }
   162  
   163  func (c *redisCache) buildKey(key string) string {
   164  	cacheKey, _ := BuildCacheKey(c.opts.prefix, key)
   165  	return cacheKey
   166  }