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
381 lines
7.9 KiB
Go
381 lines
7.9 KiB
Go
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
|
||
}
|