483 lines
11 KiB
Go
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()
|
|
}
|