package cache import ( "context" "sync" "time" ) // memoryItem 内存缓存条目。 type memoryItem struct { value string expiresAt time.Time // 零值表示不过期 } // MemoryCache 进程内存缓存实现(单机、无持久化)。 type MemoryCache struct { mu sync.RWMutex items map[string]memoryItem done chan struct{} } // NewMemory 构造内存缓存,并启动后台过期清理。 func NewMemory() *MemoryCache { m := &MemoryCache{ items: make(map[string]memoryItem), done: make(chan struct{}), } go m.gcLoop() return m } // gcLoop 每分钟清理一次过期键,避免长期运行内存膨胀。 func (m *MemoryCache) gcLoop() { ticker := time.NewTicker(time.Minute) defer ticker.Stop() for { select { case <-m.done: return case now := <-ticker.C: m.mu.Lock() for k, item := range m.items { if !item.expiresAt.IsZero() && now.After(item.expiresAt) { delete(m.items, k) } } m.mu.Unlock() } } } // Get 读取键值。 func (m *MemoryCache) Get(_ context.Context, key string) (string, error) { m.mu.RLock() item, ok := m.items[key] m.mu.RUnlock() if !ok { return "", ErrNotFound } if !item.expiresAt.IsZero() && time.Now().After(item.expiresAt) { return "", ErrNotFound } return item.value, nil } // Set 写入键值。 func (m *MemoryCache) Set(_ context.Context, key, value string, ttl time.Duration) error { item := memoryItem{value: value} if ttl > 0 { item.expiresAt = time.Now().Add(ttl) } m.mu.Lock() m.items[key] = item m.mu.Unlock() return nil } // Delete 删除键。 func (m *MemoryCache) Delete(_ context.Context, keys ...string) error { m.mu.Lock() for _, k := range keys { delete(m.items, k) } m.mu.Unlock() return nil } // Exists 判断键是否存在。 func (m *MemoryCache) Exists(_ context.Context, key string) (bool, error) { _, err := m.Get(context.Background(), key) return err == nil, nil } // Incr 原子自增;首次创建时记录窗口起点(以过期时间体现)。 func (m *MemoryCache) Incr(_ context.Context, key string, ttl time.Duration) (int64, error) { m.mu.Lock() defer m.mu.Unlock() now := time.Now() item, ok := m.items[key] if ok && !item.expiresAt.IsZero() && now.After(item.expiresAt) { // 窗口已过期,重新计数 ok = false } var n int64 if !ok { n = 1 newItem := memoryItem{value: "1"} if ttl > 0 { newItem.expiresAt = now.Add(ttl) } m.items[key] = newItem return n, nil } // 解析现有值 for _, c := range item.value { if c < '0' || c > '9' { n = 0 break } n = n*10 + int64(c-'0') } n++ newItem := memoryItem{value: itoa(n), expiresAt: item.expiresAt} m.items[key] = newItem return n, nil } // Close 停止清理协程。 func (m *MemoryCache) Close() error { select { case <-m.done: default: close(m.done) } return nil } // itoa 简单整数转字符串,避免在锁内依赖 strconv 的额外开销(数值都很小)。 func itoa(n int64) string { if n == 0 { return "0" } var buf [20]byte i := len(buf) neg := n < 0 if neg { n = -n } for n > 0 { i-- buf[i] = byte('0' + n%10) n /= 10 } if neg { i-- buf[i] = '-' } return string(buf[i:]) }