package cache import ( "crypto/md5" "encoding/hex" "sync" "time" "github.com/subconverter-go/internal/conversion" "github.com/subconverter-go/internal/logging" ) // CacheEntry 缓存条目 type CacheEntry struct { Response *conversion.ConversionResponse `json:"response"` Expiration time.Time `json:"expiration"` AccessCount int `json:"accessCount"` LastAccess time.Time `json:"lastAccess"` } // CacheManager 缓存管理器 // 提供转换结果的缓存功能,提高性能和减少重复计算 type CacheManager struct { logger *logging.Logger entries map[string]*CacheEntry mutex sync.RWMutex ttl time.Duration maxSize int enabled bool stats *CacheStats cleanupChan chan struct{} } // CacheStats 缓存统计信息 type CacheStats struct { Hits int64 `json:"hits"` Misses int64 `json:"misses"` Evictions int64 `json:"evictions"` Size int64 `json:"size"` TotalSize int64 `json:"totalSize"` HitRate float64 `json:"hitRate"` } // CacheConfig 缓存配置 type CacheConfig struct { Enabled bool `json:"enabled" yaml:"enabled"` TTL time.Duration `json:"ttl" yaml:"ttl"` MaxSize int `json:"maxSize" yaml:"maxSize"` Cleanup time.Duration `json:"cleanup" yaml:"cleanup"` } // NewCacheManager 创建新的缓存管理器 func NewCacheManager(config *CacheConfig, logger *logging.Logger) *CacheManager { if config == nil { // 默认配置 config = &CacheConfig{ Enabled: true, TTL: 5 * time.Minute, MaxSize: 1000, Cleanup: 1 * time.Minute, } } cm := &CacheManager{ logger: logger, entries: make(map[string]*CacheEntry), ttl: config.TTL, maxSize: config.MaxSize, enabled: config.Enabled, stats: &CacheStats{}, cleanupChan: make(chan struct{}), } if cm.enabled { go cm.cleanupTask(config.Cleanup) logger.Infof("Cache manager initialized with TTL=%v, maxSize=%d", cm.ttl, cm.maxSize) } else { logger.Info("Cache manager disabled") } return cm } // Get 获取缓存的转换结果 func (cm *CacheManager) Get(key string) (*conversion.ConversionResponse, bool) { if !cm.enabled { return nil, false } cm.mutex.RLock() defer cm.mutex.RUnlock() entry, exists := cm.entries[key] if !exists { cm.stats.Misses++ return nil, false } // 检查是否过期 if time.Now().After(entry.Expiration) { cm.stats.Misses++ go cm.removeExpiredEntry(key) return nil, false } // 更新访问统计 entry.AccessCount++ entry.LastAccess = time.Now() cm.stats.Hits++ cm.logger.Debugf("Cache hit for key: %s", key) return entry.Response, true } // Set 设置缓存条目 func (cm *CacheManager) Set(key string, response *conversion.ConversionResponse) error { if !cm.enabled { return nil } cm.mutex.Lock() defer cm.mutex.Unlock() // 检查缓存大小限制 if len(cm.entries) >= cm.maxSize { cm.evictEntries() } entry := &CacheEntry{ Response: response, Expiration: time.Now().Add(cm.ttl), AccessCount: 1, LastAccess: time.Now(), } cm.entries[key] = entry cm.stats.Size++ cm.stats.TotalSize++ cm.logger.Debugf("Cache entry set for key: %s, expires at: %v", key, entry.Expiration) return nil } // GenerateKey 生成缓存键 func (cm *CacheManager) GenerateKey(request *conversion.ConversionRequest) string { if request == nil { return "" } signature := request.CacheSignature() // 使用MD5生成固定长度的键 hash := md5.Sum([]byte(signature)) return hex.EncodeToString(hash[:]) } // Remove 移除指定的缓存条目 func (cm *CacheManager) Remove(key string) { if !cm.enabled { return } cm.mutex.Lock() defer cm.mutex.Unlock() if _, exists := cm.entries[key]; exists { delete(cm.entries, key) cm.stats.Size-- cm.logger.Debugf("Cache entry removed for key: %s", key) } } // Clear 清空所有缓存 func (cm *CacheManager) Clear() { if !cm.enabled { return } cm.mutex.Lock() defer cm.mutex.Unlock() cm.entries = make(map[string]*CacheEntry) cm.stats.Size = 0 cm.logger.Info("Cache cleared") } // GetStats 获取缓存统计信息 func (cm *CacheManager) GetStats() *CacheStats { if !cm.enabled { return &CacheStats{} } cm.mutex.RLock() defer cm.mutex.RUnlock() // 计算命中率 total := cm.stats.Hits + cm.stats.Misses if total > 0 { cm.stats.HitRate = float64(cm.stats.Hits) / float64(total) } // 返回统计信息的副本 statsCopy := *cm.stats return &statsCopy } // Close 关闭缓存管理器 func (cm *CacheManager) Close() { if !cm.enabled { return } // 停止清理任务 close(cm.cleanupChan) // 清空缓存 cm.Clear() cm.logger.Info("Cache manager closed") } // removeExpiredEntry 移除过期条目(异步) func (cm *CacheManager) removeExpiredEntry(key string) { cm.mutex.Lock() defer cm.mutex.Unlock() if entry, exists := cm.entries[key]; exists && time.Now().After(entry.Expiration) { delete(cm.entries, key) cm.stats.Size++ cm.stats.Evictions++ cm.logger.Debugf("Expired cache entry removed for key: %s", key) } } // evictEntries 执行缓存淘汰策略 func (cm *CacheManager) evictEntries() { // LRU (Least Recently Used) 淘汰策略 var oldestKey string var oldestTime time.Time for key, entry := range cm.entries { if oldestKey == "" || entry.LastAccess.Before(oldestTime) { oldestKey = key oldestTime = entry.LastAccess } } if oldestKey != "" { delete(cm.entries, oldestKey) cm.stats.Size-- cm.stats.Evictions++ cm.logger.Debugf("Cache entry evicted (LRU) for key: %s", oldestKey) } } // cleanupTask 定期清理过期条目 func (cm *CacheManager) cleanupTask(interval time.Duration) { // 如果间隔为0,不启动清理任务 if interval <= 0 { return } ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ticker.C: cm.cleanupExpired() case <-cm.cleanupChan: return } } } // cleanupExpired 清理所有过期条目 func (cm *CacheManager) cleanupExpired() { cm.mutex.Lock() defer cm.mutex.Unlock() now := time.Now() expiredKeys := make([]string, 0) for key, entry := range cm.entries { if now.After(entry.Expiration) { expiredKeys = append(expiredKeys, key) } } for _, key := range expiredKeys { delete(cm.entries, key) cm.stats.Evictions++ } cm.stats.Size = int64(len(cm.entries)) if len(expiredKeys) > 0 { cm.logger.Debugf("Cleaned up %d expired cache entries", len(expiredKeys)) } } // IsEnabled 检查缓存是否启用 func (cm *CacheManager) IsEnabled() bool { return cm.enabled } // GetSize 获取当前缓存大小 func (cm *CacheManager) GetSize() int { if !cm.enabled { return 0 } cm.mutex.RLock() defer cm.mutex.RUnlock() return len(cm.entries) } // GetTTTL 获取缓存TTL func (cm *CacheManager) GetTTL() time.Duration { return cm.ttl } // SetTTTL 设置缓存TTL func (cm *CacheManager) SetTTL(ttl time.Duration) { cm.mutex.Lock() defer cm.mutex.Unlock() cm.ttl = ttl cm.logger.Infof("Cache TTL updated to: %v", ttl) } // GetMaxSize 获取最大缓存大小 func (cm *CacheManager) GetMaxSize() int { return cm.maxSize } // SetMaxSize 设置最大缓存大小 func (cm *CacheManager) SetMaxSize(maxSize int) { cm.mutex.Lock() defer cm.mutex.Unlock() cm.maxSize = maxSize cm.logger.Infof("Cache max size updated to: %d", maxSize) } // GetEntry 获取缓存条目详细信息(用于调试) func (cm *CacheManager) GetEntry(key string) (*CacheEntry, bool) { if !cm.enabled { return nil, false } cm.mutex.RLock() defer cm.mutex.RUnlock() entry, exists := cm.entries[key] if !exists { return nil, false } // 返回副本 entryCopy := *entry return &entryCopy, true } // GetAllKeys 获取所有缓存键 func (cm *CacheManager) GetAllKeys() []string { if !cm.enabled { return nil } cm.mutex.RLock() defer cm.mutex.RUnlock() keys := make([]string, 0, len(cm.entries)) for key := range cm.entries { keys = append(keys, key) } return keys }