package dbcache import ( "context" "time" "git.nobla.cn/golang/aeus/pkg/cache" "git.nobla.cn/golang/aeus/pkg/errors" "golang.org/x/sync/singleflight" "gorm.io/gorm" ) var singleInstance singleflight.Group type ( CacheOptions struct { db *gorm.DB cache cache.Cache dependency CacheDependency cacheDuration time.Duration } CacheOption func(o *CacheOptions) cacheEntry[T any] struct { Value T CompareValue string CreatedAt int64 } ) func WithDB(db *gorm.DB) CacheOption { return func(o *CacheOptions) { o.db = db } } func WithCache(c cache.Cache) CacheOption { return func(o *CacheOptions) { o.cache = c } } func WithCacheDuration(d time.Duration) CacheOption { return func(o *CacheOptions) { o.cacheDuration = d } } func WithDependency(d CacheDependency) CacheOption { return func(o *CacheOptions) { o.dependency = d } } // TryCache 尝试从缓存中获取数据 func TryCache[T any](ctx context.Context, key string, f func(tx *gorm.DB) (T, error), cbs ...CacheOption) (result T, err error) { var ( none T value any val any hasDependValue bool dependValue string ) opts := &CacheOptions{ cache: cache.Default(), cacheDuration: time.Minute * 10, } for _, cb := range cbs { cb(opts) } if opts.db == nil { return none, errors.Format(errors.Unavailable, "db instance unavailable") } //从缓存加载数据 if value, err = opts.cache.Get(ctx, key); err == nil { if entry, ok := value.(*cacheEntry[T]); ok { if time.Now().Unix()-entry.CreatedAt <= 1 { return entry.Value, nil } if opts.dependency == nil { return entry.Value, nil } if dependValue, err = opts.dependency.GetValue(ctx, opts.db); err == nil { hasDependValue = true if entry.CompareValue == dependValue { return entry.Value, nil } else { opts.cache.Delete(ctx, key) } } } } //从数据库加载数据 tx := opts.db.WithContext(ctx) if val, err, _ = singleInstance.Do(key, func() (any, error) { if result, err = f(tx); err == nil { if !hasDependValue && opts.dependency != nil { dependValue, err = opts.dependency.GetValue(ctx, tx) } opts.cache.Put(ctx, key, &cacheEntry[T]{ CompareValue: dependValue, Value: result, CreatedAt: time.Now().Unix(), }, opts.cacheDuration) } return result, err }); err != nil { return none, err } else { return val.(T), err } }