migrate from github
This commit is contained in:
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"golang.org/x/sync/singleflight"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Loader struct {
|
||||
redis *redis.Client
|
||||
sf singleflight.Group
|
||||
}
|
||||
|
||||
func (l *Loader) Redis() *redis.Client {
|
||||
return l.redis
|
||||
}
|
||||
|
||||
func NewLoader(redis *redis.Client) *Loader {
|
||||
return &Loader{
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
type CacheLoadOperation[T any] struct {
|
||||
loader *Loader
|
||||
keyParserMap map[string]Parser[T]
|
||||
defaultKey *string
|
||||
baseTTL time.Duration
|
||||
dbLoader func() (*T, error)
|
||||
valueType bool
|
||||
}
|
||||
|
||||
func NewCacheLoadOperation[T any](loader *Loader) *CacheLoadOperation[T] {
|
||||
return &CacheLoadOperation[T]{
|
||||
loader: loader,
|
||||
baseTTL: time.Hour,
|
||||
valueType: false,
|
||||
keyParserMap: make(map[string]Parser[T]),
|
||||
}
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) WithDefaultKeyAndParser(key string, parser Parser[T]) *CacheLoadOperation[T] {
|
||||
var defaultParser Parser[T]
|
||||
op.defaultKey = &key
|
||||
if parser == nil {
|
||||
defaultParser = DefaultParser[T]
|
||||
} else {
|
||||
defaultParser = parser
|
||||
}
|
||||
|
||||
op.keyParserMap[key] = defaultParser
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) WithKeyAndParser(key string, parser Parser[T]) *CacheLoadOperation[T] {
|
||||
op.keyParserMap[key] = parser
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) WithTTL(ttl time.Duration) *CacheLoadOperation[T] {
|
||||
op.baseTTL = ttl
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) WithDBLoader(dbLoader func() (*T, error)) *CacheLoadOperation[T] {
|
||||
op.dbLoader = dbLoader
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) Exec(ctx context.Context) (*T, error) {
|
||||
if op.defaultKey == nil {
|
||||
return nil, status.GenErrWithCustomMsg(status.StatusInternalParamsError, "default key is needed")
|
||||
}
|
||||
if op.dbLoader == nil {
|
||||
return nil, status.GenErrWithCustomMsg(status.StatusInternalParamsError, "database loader function is required")
|
||||
}
|
||||
|
||||
// try to load from any of the cache keys
|
||||
for key, parser := range op.keyParserMap {
|
||||
val, err := op.loader.redis.Get(ctx, key).Result()
|
||||
if err == nil {
|
||||
if val == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if data, err := parser([]byte(val)); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all caches missed, use singleflight to avoid concurrent db queries
|
||||
sfKey := *op.defaultKey
|
||||
v, err, _ := op.loader.sf.Do(sfKey, func() (interface{}, error) {
|
||||
// double check all caches
|
||||
for key, parser := range op.keyParserMap {
|
||||
|
||||
val, err := op.loader.redis.Get(ctx, key).Result()
|
||||
if err == nil {
|
||||
if val == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if data, err := parser([]byte(val)); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// query database
|
||||
data, err := op.dbLoader()
|
||||
if err != nil {
|
||||
// cache empty value for all keys if record not found
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
op.loader.redis.Set(ctx, *op.defaultKey, "null", 5*time.Minute)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bytes, _ := sonic.Marshal(data)
|
||||
ttl := op.baseTTL + time.Duration(rand.Intn(600))*time.Second
|
||||
|
||||
op.loader.redis.Set(ctx, *op.defaultKey, bytes, ttl)
|
||||
|
||||
return data, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return v.(*T), nil
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
)
|
||||
|
||||
type Parser[T any] func(data []byte) (*T, error)
|
||||
|
||||
func DefaultParser[T any](data []byte) (*T, error) {
|
||||
var value T
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
var ParseInt64 = DefaultParser[int64]
|
||||
|
||||
func ParseIDFromDocument(data []byte) (*int64, error) {
|
||||
var doc model.Document
|
||||
if err := json.Unmarshal(data, &doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &doc.ID, nil
|
||||
}
|
||||
Reference in New Issue
Block a user