first commit
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

This commit is contained in:
Rogee
2025-09-28 10:05:07 +08:00
commit 7fcabe0225
481 changed files with 125127 additions and 0 deletions

380
internal/cache/manager.go vendored Normal file
View File

@@ -0,0 +1,380 @@
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
}