package cache import ( "context" "errors" "fmt" "time" "gitea.bvbej.com/bvbej/base-golang/pkg/time_parse" "gitea.bvbej.com/bvbej/base-golang/pkg/trace" "github.com/redis/go-redis/v9" ) type Option func(*option) type Trace = trace.T type option struct { Trace *trace.Trace Redis *trace.Redis } type RedisConfig struct { Addr string `yaml:"addr"` Pass string `yaml:"pass"` DB int `yaml:"db"` MaxRetries int `yaml:"maxRetries"` // 最大重试次数 PoolSize int `yaml:"poolSize"` // Redis连接池大小 MinIdleConn int `yaml:"minIdleConn"` // 最小空闲连接数 } func newOption() *option { return &option{} } var _ Repo = (*cacheRepo)(nil) type Repo interface { i() Client() *redis.Client 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, error) Decr(key string, options ...Option) (int64, error) 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) HIncrBy(key, field string, incr int64, options ...Option) (int64, error) HIncrByFloat(key, field string, incr float64, options ...Option) (float64, error) LPush(key, value string, options ...Option) error LLen(key string, options ...Option) (int64, error) BRPop(key string, timeout time.Duration, options ...Option) (string, error) Close() error } type cacheRepo struct { client *redis.Client ctx context.Context } func New(cfg RedisConfig) (Repo, error) { client := redis.NewClient(&redis.Options{ Addr: cfg.Addr, Password: cfg.Pass, DB: cfg.DB, MaxRetries: cfg.MaxRetries, PoolSize: cfg.PoolSize, MinIdleConns: cfg.MinIdleConn, }) ctx := context.TODO() if err := client.Ping(ctx).Err(); err != nil { return nil, errors.Join(err, errors.New("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) Client() *redis.Client { return c.client } 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.Join(err, fmt.Errorf("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.Join(err, fmt.Errorf("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.Join(err, fmt.Errorf("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, error) { 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, err := c.client.Incr(c.ctx, key).Result() if err != nil { return 0, errors.Join(err, fmt.Errorf("redis incr key: %s err", key)) } return value, nil } func (c *cacheRepo) Decr(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 = "decr" 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.Decr(c.ctx, key).Result() if err != nil { return 0, errors.Join(err, fmt.Errorf("redis decr key: %s err", key)) } return value, nil } 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.Join(err, fmt.Errorf("redis hget key: %s field: %s err", 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.Join(err, fmt.Errorf("redis hset 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.Join(err, fmt.Errorf("redis hdel key: %s 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.Join(err, fmt.Errorf("redis hget all key: %s err", key)) } return value, nil } func (c *cacheRepo) HIncrBy(key, field string, incr int64, options ...Option) (int64, error) { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "hash incr int64" opt.Redis.Key = key opt.Redis.Value = fmt.Sprintf("field:%s incr:%d", field, incr) opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } value, err := c.client.HIncrBy(c.ctx, key, field, incr).Result() if err != nil { return 0, errors.Join(err, fmt.Errorf("redis hash incr int64 key: %s err", key)) } return value, nil } func (c *cacheRepo) HIncrByFloat(key, field string, incr float64, options ...Option) (float64, error) { ts := time.Now() opt := newOption() defer func() { if opt.Trace != nil { opt.Redis.Timestamp = time_parse.CSTLayoutString() opt.Redis.Handle = "hash incr float64" opt.Redis.Key = key opt.Redis.Value = fmt.Sprintf("field:%s incr:%d", field, incr) opt.Redis.CostSeconds = time.Since(ts).Seconds() opt.Trace.AppendRedis(opt.Redis) } }() for _, f := range options { f(opt) } value, err := c.client.HIncrByFloat(c.ctx, key, field, incr).Result() if err != nil { return 0, errors.Join(err, fmt.Errorf("redis hash incr float64 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.Join(err, fmt.Errorf("redis list push key: %s value: %s err", 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.Join(err, fmt.Errorf("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.Join(err, fmt.Errorf("redis list len key: %s err", key)) } return value[1], nil } func (c *cacheRepo) Close() error { return c.client.Close() }