package cache import ( "context" "time" "git.bvbej.com/bvbej/base-golang/pkg/errors" "git.bvbej.com/bvbej/base-golang/pkg/time_parse" "git.bvbej.com/bvbej/base-golang/pkg/trace" "github.com/go-redis/redis/v8" ) type Option func(*option) type Trace = trace.T type option struct { Trace *trace.Trace Redis *trace.Redis } func newOption() *option { return &option{} } var _ Repo = (*cacheRepo)(nil) type Repo interface { i() Set(key, value string, ttl time.Duration, options ...Option) error Get(key string, options ...Option) (string, error) TTL(key string) (time.Duration, error) Expire(key string, ttl time.Duration) bool ExpireAt(key string, ttl time.Time) bool Del(key string, options ...Option) bool Exists(keys ...string) bool Incr(key string, options ...Option) int64 HGet(key, field string, options ...Option) (string, error) HSet(key, field, value string, options ...Option) error HDel(key, field string, options ...Option) error HGetAll(key string, options ...Option) (map[string]string, error) Close() error } type cacheRepo struct { client *redis.Client ctx context.Context } func New(addr, pass string, db, maxRetries, poolSize, minIdleConn int) (Repo, error) { client := redis.NewClient(&redis.Options{ Addr: addr, Password: pass, DB: db, MaxRetries: maxRetries, PoolSize: poolSize, MinIdleConns: minIdleConn, }) ctx := context.TODO() if err := client.Ping(ctx).Err(); err != nil { return nil, errors.Wrap(err, "ping redis err") } return &cacheRepo{ client: client, ctx: ctx, }, nil } func WithTrace(t Trace) Option { return func(opt *option) { if t != nil { opt.Trace = t.(*trace.Trace) opt.Redis = new(trace.Redis) } } } func (c *cacheRepo) i() {} func (c *cacheRepo) Set(key, value string, ttl time.Duration, options ...Option) error { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "set" opt.Redis.Key = key opt.Redis.Value = value opt.Redis.TTL = ttl.Minutes() opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } if err := c.client.Set(c.ctx, key, value, ttl).Err(); err != nil { return errors.Wrapf(err, "redis set key: %s err", key) } return nil } func (c *cacheRepo) Get(key string, options ...Option) (string, error) { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "get" opt.Redis.Key = key opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } value, err := c.client.Get(c.ctx, key).Result() if err != nil { return "", errors.Wrapf(err, "redis get key: %s err", key) } return value, nil } func (c *cacheRepo) TTL(key string) (time.Duration, error) { ttl, err := c.client.TTL(c.ctx, key).Result() if err != nil { return -1, errors.Wrapf(err, "redis get key: %s err", key) } return ttl, nil } func (c *cacheRepo) Expire(key string, ttl time.Duration) bool { ok, _ := c.client.Expire(c.ctx, key, ttl).Result() return ok } func (c *cacheRepo) ExpireAt(key string, ttl time.Time) bool { ok, _ := c.client.ExpireAt(c.ctx, key, ttl).Result() return ok } func (c *cacheRepo) Exists(keys ...string) bool { if len(keys) == 0 { return true } value, _ := c.client.Exists(c.ctx, keys...).Result() return value > 0 } func (c *cacheRepo) Del(key string, options ...Option) bool { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "del" opt.Redis.Key = key opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } if key == "" { return true } value, _ := c.client.Del(c.ctx, key).Result() return value > 0 } func (c *cacheRepo) Incr(key string, options ...Option) int64 { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "incr" opt.Redis.Key = key opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } value, _ := c.client.Incr(c.ctx, key).Result() return value } func (c *cacheRepo) HGet(key, field string, options ...Option) (string, error) { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "hash get" opt.Redis.Key = key opt.Redis.Value = field opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } value, err := c.client.HGet(c.ctx, key, field).Result() if err != nil { return "", errors.Wrapf(err, "redis hget key: %s field: %s", key, field) } return value, nil } func (c *cacheRepo) HSet(key, field, value string, options ...Option) error { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "hash set" opt.Redis.Key = key opt.Redis.Value = field + "/" + value opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } if err := c.client.HSet(c.ctx, key, field, value).Err(); err != nil { return errors.Wrapf(err, "redis set key: %s field: %s err", key, field) } return nil } func (c *cacheRepo) HDel(key, field string, options ...Option) error { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "hash del" opt.Redis.Key = key opt.Redis.Value = field opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } if err := c.client.HDel(c.ctx, key, field).Err(); err != nil { return errors.Wrapf(err, "redis del field: %s err", key, field) } return nil } func (c *cacheRepo) HGetAll(key string, options ...Option) (map[string]string, error) { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "hash get all" opt.Redis.Key = key opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } value, err := c.client.HGetAll(c.ctx, key).Result() if err != nil { return nil, errors.Wrapf(err, "redis get all key: %s err", key) } return value, nil } func (c *cacheRepo) LPush(key, value string, options ...Option) error { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "list push" opt.Redis.Key = key opt.Redis.Value = value opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } _, err := c.client.LPush(c.ctx, key, value).Result() if err != nil { return errors.Wrapf(err, "redis list push key: %s value: %s", key, value) } return nil } func (c *cacheRepo) LLen(key string, options ...Option) (int64, error) { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "list len" opt.Redis.Key = key opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } value, err := c.client.LLen(c.ctx, key).Result() if err != nil { return 0, errors.Wrapf(err, "redis list len key: %s err", key) } return value, nil } func (c *cacheRepo) BRPop(key string, timeout time.Duration, options ...Option) (string, error) { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "list brpop" opt.Redis.Key = key opt.Redis.TTL = timeout.Seconds() opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } value, err := c.client.BRPop(c.ctx, timeout, key).Result() if err != nil { return "", errors.Wrapf(err, "redis list len key: %s err", key) } return value[0], nil } func (c *cacheRepo) Close() error { return c.client.Close() }