144 lines
2.8 KiB
Go
144 lines
2.8 KiB
Go
package cache
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// CacheEntry represents a cached value with expiration
|
|
type CacheEntry struct {
|
|
Value interface{}
|
|
ExpiresAt time.Time
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
// IsExpired checks if the cache entry has expired
|
|
func (e *CacheEntry) IsExpired() bool {
|
|
return time.Now().After(e.ExpiresAt)
|
|
}
|
|
|
|
// Cache provides an in-memory cache with TTL support
|
|
type Cache struct {
|
|
entries map[string]*CacheEntry
|
|
mu sync.RWMutex
|
|
ttl time.Duration
|
|
}
|
|
|
|
// NewCache creates a new cache with a default TTL
|
|
func NewCache(defaultTTL time.Duration) *Cache {
|
|
c := &Cache{
|
|
entries: make(map[string]*CacheEntry),
|
|
ttl: defaultTTL,
|
|
}
|
|
|
|
// Start background cleanup goroutine
|
|
go c.cleanup()
|
|
|
|
return c
|
|
}
|
|
|
|
// Get retrieves a value from the cache
|
|
func (c *Cache) Get(key string) (interface{}, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
entry, exists := c.entries[key]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
|
|
if entry.IsExpired() {
|
|
// Don't delete here, let cleanup handle it
|
|
return nil, false
|
|
}
|
|
|
|
return entry.Value, true
|
|
}
|
|
|
|
// Set stores a value in the cache with the default TTL
|
|
func (c *Cache) Set(key string, value interface{}) {
|
|
c.SetWithTTL(key, value, c.ttl)
|
|
}
|
|
|
|
// SetWithTTL stores a value in the cache with a custom TTL
|
|
func (c *Cache) SetWithTTL(key string, value interface{}, ttl time.Duration) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
c.entries[key] = &CacheEntry{
|
|
Value: value,
|
|
ExpiresAt: time.Now().Add(ttl),
|
|
CreatedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Delete removes a value from the cache
|
|
func (c *Cache) Delete(key string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
delete(c.entries, key)
|
|
}
|
|
|
|
// Clear removes all entries from the cache
|
|
func (c *Cache) Clear() {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.entries = make(map[string]*CacheEntry)
|
|
}
|
|
|
|
// cleanup periodically removes expired entries
|
|
func (c *Cache) cleanup() {
|
|
ticker := time.NewTicker(1 * time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
c.mu.Lock()
|
|
for key, entry := range c.entries {
|
|
if entry.IsExpired() {
|
|
delete(c.entries, key)
|
|
}
|
|
}
|
|
c.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// Stats returns cache statistics
|
|
func (c *Cache) Stats() map[string]interface{} {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
total := len(c.entries)
|
|
expired := 0
|
|
for _, entry := range c.entries {
|
|
if entry.IsExpired() {
|
|
expired++
|
|
}
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"total_entries": total,
|
|
"active_entries": total - expired,
|
|
"expired_entries": expired,
|
|
"default_ttl_seconds": int(c.ttl.Seconds()),
|
|
}
|
|
}
|
|
|
|
// GenerateKey generates a cache key from a string
|
|
func GenerateKey(prefix string, parts ...string) string {
|
|
key := prefix
|
|
for _, part := range parts {
|
|
key += ":" + part
|
|
}
|
|
|
|
// Hash long keys to keep them manageable
|
|
if len(key) > 200 {
|
|
hash := sha256.Sum256([]byte(key))
|
|
return prefix + ":" + hex.EncodeToString(hash[:])
|
|
}
|
|
|
|
return key
|
|
}
|
|
|