206 lines
4.2 KiB
Go
206 lines
4.2 KiB
Go
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()
|
|
}
|