package cache import ( "context" "encoding/json" "fmt" "sync" "time" ) // Item represents a cache item type Item struct { Value []byte Expiration int64 } // Expired returns true if the item has expired func (item Item) Expired() bool { if item.Expiration == 0 { return false } return time.Now().UnixNano() > item.Expiration } // Cache represents an in-memory cache type Cache struct { items map[string]Item mu sync.RWMutex defaultExpiration time.Duration cleanupInterval time.Duration stopCleanup chan bool } // New creates a new cache with the given default expiration and cleanup interval func New(defaultExpiration, cleanupInterval time.Duration) *Cache { cache := &Cache{ items: make(map[string]Item), defaultExpiration: defaultExpiration, cleanupInterval: cleanupInterval, stopCleanup: make(chan bool), } // Start cleanup goroutine if cleanup interval > 0 if cleanupInterval > 0 { go cache.startCleanup() } return cache } // startCleanup starts the cleanup process func (c *Cache) startCleanup() { ticker := time.NewTicker(c.cleanupInterval) defer ticker.Stop() for { select { case <-ticker.C: c.DeleteExpired() case <-c.stopCleanup: return } } } // Set adds an item to the cache with the given key and expiration func (c *Cache) Set(key string, value interface{}, expiration time.Duration) error { // Convert value to bytes var valueBytes []byte var err error switch v := value.(type) { case []byte: valueBytes = v case string: valueBytes = []byte(v) default: valueBytes, err = json.Marshal(value) if err != nil { return fmt.Errorf("failed to marshal value: %w", err) } } // Calculate expiration var exp int64 if expiration == 0 { if c.defaultExpiration > 0 { exp = time.Now().Add(c.defaultExpiration).UnixNano() } } else if expiration > 0 { exp = time.Now().Add(expiration).UnixNano() } c.mu.Lock() c.items[key] = Item{ Value: valueBytes, Expiration: exp, } c.mu.Unlock() return nil } // Get gets an item from the cache func (c *Cache) Get(key string, value interface{}) (bool, error) { c.mu.RLock() item, found := c.items[key] c.mu.RUnlock() if !found { return false, nil } // Check if item has expired if item.Expired() { c.mu.Lock() delete(c.items, key) c.mu.Unlock() return false, nil } // Unmarshal value switch v := value.(type) { case *[]byte: *v = item.Value case *string: *v = string(item.Value) default: if err := json.Unmarshal(item.Value, value); err != nil { return true, fmt.Errorf("failed to unmarshal value: %w", err) } } return true, nil } // Delete deletes an item from the cache func (c *Cache) Delete(key string) { c.mu.Lock() delete(c.items, key) c.mu.Unlock() } // DeleteExpired deletes all expired items from the cache func (c *Cache) DeleteExpired() { now := time.Now().UnixNano() c.mu.Lock() for k, v := range c.items { if v.Expiration > 0 && now > v.Expiration { delete(c.items, k) } } c.mu.Unlock() } // Clear deletes all items from the cache func (c *Cache) Clear() { c.mu.Lock() c.items = make(map[string]Item) c.mu.Unlock() } // Close stops the cleanup goroutine func (c *Cache) Close() { if c.cleanupInterval > 0 { c.stopCleanup <- true } } // CacheService provides caching functionality type CacheService struct { cache *Cache } // NewCacheService creates a new CacheService func NewCacheService(defaultExpiration, cleanupInterval time.Duration) *CacheService { return &CacheService{ cache: New(defaultExpiration, cleanupInterval), } } // Set adds an item to the cache func (s *CacheService) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error { return s.cache.Set(key, value, expiration) } // Get gets an item from the cache func (s *CacheService) Get(ctx context.Context, key string, value interface{}) (bool, error) { return s.cache.Get(key, value) } // Delete deletes an item from the cache func (s *CacheService) Delete(ctx context.Context, key string) { s.cache.Delete(key) } // Clear deletes all items from the cache func (s *CacheService) Clear(ctx context.Context) { s.cache.Clear() } // Close closes the cache func (s *CacheService) Close() { s.cache.Close() }