base-golang/pkg/cache/redis.go
2024-07-31 16:49:14 +08:00

483 lines
11 KiB
Go

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()
}