Files
subconverter-go/internal/cache/manager.go
Rogee 7fcabe0225
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
first commit
2025-09-28 10:05:07 +08:00

381 lines
7.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}