// Package cache 提供轻量的 JSON 缓存抽象,用于缓存热点只读数据。 // // 所有方法在后端不可用/出错时都"优雅降级"(视为未命中 / 静默跳过), // 因此调用方始终能回退到数据库,缓存层不会成为故障点。 package cache import ( "context" "encoding/json" "sync" "time" "github.com/redis/go-redis/v9" ) // Cache 是一个简单的 JSON 键值缓存。 type Cache interface { // GetJSON 命中则把值反序列化到 dest 并返回 true;未命中/出错返回 false。 GetJSON(ctx context.Context, key string, dest any) bool // SetJSON 写入(带 TTL);出错静默忽略。 SetJSON(ctx context.Context, key string, val any, ttl time.Duration) // Delete 删除若干键;出错静默忽略。 Delete(ctx context.Context, keys ...string) } // ---------------- Redis 实现 ---------------- type redisCache struct { rdb *redis.Client } // NewRedis 返回基于 Redis 的缓存实现。rdb 为 nil 时所有操作均为安全空操作。 func NewRedis(rdb *redis.Client) Cache { return &redisCache{rdb: rdb} } func (c *redisCache) GetJSON(ctx context.Context, key string, dest any) bool { if c.rdb == nil { return false } b, err := c.rdb.Get(ctx, key).Bytes() if err != nil || len(b) == 0 { return false } return json.Unmarshal(b, dest) == nil } func (c *redisCache) SetJSON(ctx context.Context, key string, val any, ttl time.Duration) { if c.rdb == nil { return } b, err := json.Marshal(val) if err != nil { return } _ = c.rdb.Set(ctx, key, b, ttl).Err() } func (c *redisCache) Delete(ctx context.Context, keys ...string) { if c.rdb == nil || len(keys) == 0 { return } _ = c.rdb.Del(ctx, keys...).Err() } // ---------------- 内存实现(测试 / 开发 / 降级) ---------------- type memItem struct { data []byte exp time.Time } type memCache struct { mu sync.RWMutex items map[string]memItem } // NewMemory 返回进程内内存缓存实现。 func NewMemory() Cache { return &memCache{items: make(map[string]memItem)} } func (c *memCache) GetJSON(ctx context.Context, key string, dest any) bool { c.mu.RLock() it, ok := c.items[key] c.mu.RUnlock() if !ok { return false } if !it.exp.IsZero() && time.Now().After(it.exp) { c.mu.Lock() delete(c.items, key) c.mu.Unlock() return false } return json.Unmarshal(it.data, dest) == nil } func (c *memCache) SetJSON(ctx context.Context, key string, val any, ttl time.Duration) { b, err := json.Marshal(val) if err != nil { return } var exp time.Time if ttl > 0 { exp = time.Now().Add(ttl) } c.mu.Lock() c.items[key] = memItem{data: b, exp: exp} c.mu.Unlock() } func (c *memCache) Delete(ctx context.Context, keys ...string) { c.mu.Lock() for _, k := range keys { delete(c.items, k) } c.mu.Unlock() }