Files
SiteProxy/cache/cache.go
2025-12-15 02:43:23 +08:00

237 lines
5.5 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.
// cache/cache.go
package cache
import (
"crypto/sha256"
"encoding/hex"
"strconv"
"sync"
"time"
)
type CacheEntry struct {
Data []byte
Headers map[string]string
CreatedAt time.Time
ExpiresAt time.Time
Size int64
}
type MemoryCache struct {
entries sync.Map
maxSize int64
currentSize int64
ttl time.Duration
mu sync.Mutex
}
func NewMemoryCache(maxSize int64, ttl time.Duration) *MemoryCache {
mc := &MemoryCache{
maxSize: maxSize,
ttl: ttl,
}
// 启动清理协程
if ttl > 0 {
go mc.cleanup()
}
return mc
}
// Age 返回缓存条目的年龄(秒)
func (e *CacheEntry) Age() string {
age := time.Since(e.CreatedAt)
return strconv.FormatInt(int64(age.Seconds()), 10)
}
// Get 获取缓存条目
func (mc *MemoryCache) Get(key string) *CacheEntry {
val, ok := mc.entries.Load(key)
if !ok {
return nil
}
entry := val.(*CacheEntry)
// 检查是否过期
if time.Now().After(entry.ExpiresAt) {
mc.Delete(key)
return nil
}
return entry
}
// Set 设置缓存条目
func (mc *MemoryCache) Set(key string, data []byte, headers map[string]string) bool {
// 如果禁用缓存maxSize = 0直接返回
if mc.maxSize == 0 {
return false
}
size := int64(len(data))
// 如果单个条目超过最大缓存大小,不缓存
if size > mc.maxSize {
return false
}
mc.mu.Lock()
defer mc.mu.Unlock()
// 检查是否超过最大缓存大小
if mc.currentSize+size > mc.maxSize {
// 尝试清理过期条目
mc.evictExpired()
// 如果还是不够,使用 LRU 清理
if mc.currentSize+size > mc.maxSize {
mc.evictOldest(size)
}
}
now := time.Now()
entry := &CacheEntry{
Data: data,
Headers: headers,
CreatedAt: now,
ExpiresAt: now.Add(mc.ttl),
Size: size,
}
// 如果 key 已存在,先删除旧的
if oldVal, exists := mc.entries.Load(key); exists {
oldEntry := oldVal.(*CacheEntry)
mc.currentSize -= oldEntry.Size
}
mc.entries.Store(key, entry)
mc.currentSize += size
return true
}
// Delete 删除缓存条目
func (mc *MemoryCache) Delete(key string) {
val, ok := mc.entries.LoadAndDelete(key)
if ok {
entry := val.(*CacheEntry)
mc.mu.Lock()
mc.currentSize -= entry.Size
mc.mu.Unlock()
}
}
// Clear 清空所有缓存
func (mc *MemoryCache) Clear() {
mc.mu.Lock()
defer mc.mu.Unlock()
mc.entries.Range(func(key, value interface{}) bool {
mc.entries.Delete(key)
return true
})
mc.currentSize = 0
}
// GenerateKey 生成缓存键
func (mc *MemoryCache) GenerateKey(url string) string {
hash := sha256.Sum256([]byte(url))
return hex.EncodeToString(hash[:])
}
// evictExpired 清理过期条目
func (mc *MemoryCache) evictExpired() {
now := time.Now()
mc.entries.Range(func(key, value interface{}) bool {
entry := value.(*CacheEntry)
if now.After(entry.ExpiresAt) {
mc.entries.Delete(key)
mc.currentSize -= entry.Size
}
return true
})
}
// evictOldest 清理最旧的条目
func (mc *MemoryCache) evictOldest(needed int64) {
type entryWithKey struct {
key string
entry *CacheEntry
}
var entries []entryWithKey
mc.entries.Range(func(key, value interface{}) bool {
entries = append(entries, entryWithKey{
key: key.(string),
entry: value.(*CacheEntry),
})
return true
})
// 按创建时间排序(最旧的在前)
for i := 0; i < len(entries)-1; i++ {
for j := i + 1; j < len(entries); j++ {
if entries[i].entry.CreatedAt.After(entries[j].entry.CreatedAt) {
entries[i], entries[j] = entries[j], entries[i]
}
}
}
// 删除最旧的条目直到有足够空间
freed := int64(0)
for _, e := range entries {
if freed >= needed {
break
}
mc.entries.Delete(e.key)
mc.currentSize -= e.entry.Size
freed += e.entry.Size
}
}
// cleanup 定期清理过期条目
func (mc *MemoryCache) cleanup() {
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
for range ticker.C {
mc.mu.Lock()
mc.evictExpired()
mc.mu.Unlock()
}
}
// Stats 返回缓存统计信息
func (mc *MemoryCache) Stats() (entries int, size int64) {
count := 0
mc.entries.Range(func(key, value interface{}) bool {
count++
return true
})
return count, mc.currentSize
}
// GetStats 返回详细的缓存统计信息
func (mc *MemoryCache) GetStats() map[string]interface{} {
entries, size := mc.Stats()
utilizationPercent := float64(0)
if mc.maxSize > 0 {
utilizationPercent = float64(size) / float64(mc.maxSize) * 100
}
return map[string]interface{}{
"entries": entries,
"size_bytes": size,
"size_mb": float64(size) / 1024 / 1024,
"max_size_bytes": mc.maxSize,
"max_size_mb": float64(mc.maxSize) / 1024 / 1024,
"utilization_pct": utilizationPercent,
"ttl_seconds": int64(mc.ttl.Seconds()),
"enabled": mc.maxSize > 0,
}
}