otpm/cache/cache.go
“xHuPo” bcd986e3f7 beta
2025-05-23 18:57:11 +08:00

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()
}