package cache import ( "context" "fwh-toolbox-server/cache/redisx" "go.uber.org/zap" ) // GetOrSetStale: 如果缓存存在但 TTL 小于 threshold,则返回旧值并后台刷新 func GetOrSetStale[T any]( ctx context.Context, key string, loader LoaderFunc[T], opts ...Option, ) (*T, error) { o := defaultOptions() for _, op := range opts { op(o) } full := fullKey(o.prefix, key) // 1) 尝试读取缓存并检查 TTL var out T if err := redisx.GetJSON(ctx, full, &out); err == nil { // cache exists => check remaining TTL ttl, err := redisx.TTL(ctx, full) if err != nil { // 无法获取 TTL 则直接返回缓存值 return &out, nil } if ttl <= 0 || ttl > o.staleThreshold { // TTL 足够 -> 直接返回 return &out, nil } // TTL <= threshold -> 视为 stale:返回旧值并后台刷新 go func() { // Use GetOrSet with lock disabled? We want one worker to refresh: use lockEnabled true _, err := GetOrSet(ctx, key, loader, append(opts, WithAsync(false))...) if err != nil { zap.L().Error("cachex: background refresh failed", zap.String("full", full), zap.String("err", err.Error())) } }() return &out, nil } // 未命中则走普通 GetOrSet(带锁) return GetOrSet(ctx, key, loader, opts...) }