package cache import ( "crypto/md5" "encoding/hex" "io/ioutil" "os" "path/filepath" "sync" "time" "pansou/util/json" ) // 磁盘缓存项元数据 type diskCacheMetadata struct { Key string `json:"key"` Expiry time.Time `json:"expiry"` LastUsed time.Time `json:"last_used"` Size int `json:"size"` } // DiskCache 磁盘缓存 type DiskCache struct { path string maxSizeMB int metadata map[string]*diskCacheMetadata mutex sync.RWMutex currSize int64 } // NewDiskCache 创建新的磁盘缓存 func NewDiskCache(path string, maxSizeMB int) (*DiskCache, error) { // 确保缓存目录存在 if err := os.MkdirAll(path, 0755); err != nil { return nil, err } cache := &DiskCache{ path: path, maxSizeMB: maxSizeMB, metadata: make(map[string]*diskCacheMetadata), } // 加载现有缓存元数据 cache.loadMetadata() // 启动周期性清理 go cache.startCleanupTask() return cache, nil } // 加载元数据 func (c *DiskCache) loadMetadata() { c.mutex.Lock() defer c.mutex.Unlock() // 遍历缓存目录 files, err := ioutil.ReadDir(c.path) if err != nil { return } for _, file := range files { if file.IsDir() { continue } // 跳过元数据文件 if file.Name() == "metadata.json" { continue } // 读取元数据 metadataFile := filepath.Join(c.path, file.Name()+".meta") data, err := ioutil.ReadFile(metadataFile) if err != nil { continue } var meta diskCacheMetadata if err := json.Unmarshal(data, &meta); err != nil { continue } // 更新总大小 c.currSize += int64(meta.Size) // 存储元数据 c.metadata[meta.Key] = &meta } } // 保存元数据 func (c *DiskCache) saveMetadata(key string, meta *diskCacheMetadata) error { metadataFile := filepath.Join(c.path, c.getFilename(key)+".meta") data, err := json.Marshal(meta) if err != nil { return err } return ioutil.WriteFile(metadataFile, data, 0644) } // 获取文件名 func (c *DiskCache) getFilename(key string) string { hash := md5.Sum([]byte(key)) return hex.EncodeToString(hash[:]) } // Set 设置缓存 func (c *DiskCache) Set(key string, data []byte, ttl time.Duration) error { c.mutex.Lock() defer c.mutex.Unlock() // 如果已存在,先减去旧项的大小 if meta, exists := c.metadata[key]; exists { c.currSize -= int64(meta.Size) // 删除旧文件 filename := c.getFilename(key) os.Remove(filepath.Join(c.path, filename)) os.Remove(filepath.Join(c.path, filename+".meta")) } // 检查空间 maxSize := int64(c.maxSizeMB) * 1024 * 1024 if c.currSize+int64(len(data)) > maxSize { // 清理空间 c.evictLRU(int64(len(data))) } // 获取文件名 filename := c.getFilename(key) filePath := filepath.Join(c.path, filename) // 写入文件 if err := ioutil.WriteFile(filePath, data, 0644); err != nil { return err } // 创建元数据 now := time.Now() meta := &diskCacheMetadata{ Key: key, Expiry: now.Add(ttl), LastUsed: now, Size: len(data), } // 保存元数据 if err := c.saveMetadata(key, meta); err != nil { // 如果元数据保存失败,删除数据文件 os.Remove(filePath) return err } // 更新内存中的元数据 c.metadata[key] = meta c.currSize += int64(len(data)) return nil } // Get 获取缓存 func (c *DiskCache) Get(key string) ([]byte, bool, error) { c.mutex.RLock() meta, exists := c.metadata[key] c.mutex.RUnlock() if !exists { return nil, false, nil } // 检查是否过期 if time.Now().After(meta.Expiry) { c.Delete(key) return nil, false, nil } // 获取文件路径 filePath := filepath.Join(c.path, c.getFilename(key)) // 读取文件 data, err := ioutil.ReadFile(filePath) if err != nil { // 如果文件不存在,删除元数据 if os.IsNotExist(err) { c.Delete(key) } return nil, false, err } // 更新最后使用时间 c.mutex.Lock() meta.LastUsed = time.Now() c.saveMetadata(key, meta) c.mutex.Unlock() return data, true, nil } // Delete 删除缓存 func (c *DiskCache) Delete(key string) error { c.mutex.Lock() defer c.mutex.Unlock() meta, exists := c.metadata[key] if !exists { return nil } // 删除文件 filename := c.getFilename(key) os.Remove(filepath.Join(c.path, filename)) os.Remove(filepath.Join(c.path, filename+".meta")) // 更新元数据 c.currSize -= int64(meta.Size) delete(c.metadata, key) return nil } // Has 检查缓存是否存在 func (c *DiskCache) Has(key string) bool { c.mutex.RLock() defer c.mutex.RUnlock() meta, exists := c.metadata[key] if !exists { return false } // 检查是否过期 if time.Now().After(meta.Expiry) { // 异步删除过期项 go c.Delete(key) return false } return true } // 清理过期项 func (c *DiskCache) cleanExpired() { c.mutex.Lock() defer c.mutex.Unlock() now := time.Now() for key, meta := range c.metadata { if now.After(meta.Expiry) { // 删除文件 filename := c.getFilename(key) err := os.Remove(filepath.Join(c.path, filename)) if err == nil || os.IsNotExist(err) { os.Remove(filepath.Join(c.path, filename+".meta")) c.currSize -= int64(meta.Size) delete(c.metadata, key) } } } } // 驱逐策略 - LRU func (c *DiskCache) evictLRU(requiredSpace int64) { // 按最后使用时间排序 type cacheItem struct { key string lastUsed time.Time size int } items := make([]cacheItem, 0, len(c.metadata)) for k, v := range c.metadata { items = append(items, cacheItem{ key: k, lastUsed: v.LastUsed, size: v.Size, }) } // 按最后使用时间排序 // 使用冒泡排序保持简单 for i := 0; i < len(items); i++ { for j := 0; j < len(items)-i-1; j++ { if items[j].lastUsed.After(items[j+1].lastUsed) { items[j], items[j+1] = items[j+1], items[j] } } } // 从最久未使用开始删除,直到有足够空间 maxSize := int64(c.maxSizeMB) * 1024 * 1024 for _, item := range items { if c.currSize+requiredSpace <= maxSize { break } // 删除文件 filename := c.getFilename(item.key) err := os.Remove(filepath.Join(c.path, filename)) if err == nil || os.IsNotExist(err) { os.Remove(filepath.Join(c.path, filename+".meta")) c.currSize -= int64(item.size) delete(c.metadata, item.key) } } } // 启动定期清理任务 func (c *DiskCache) startCleanupTask() { ticker := time.NewTicker(10 * time.Minute) for range ticker.C { c.cleanExpired() } } // Clear 清空缓存 func (c *DiskCache) Clear() error { c.mutex.Lock() defer c.mutex.Unlock() // 删除所有缓存文件 files, err := ioutil.ReadDir(c.path) if err != nil { return err } for _, file := range files { if file.IsDir() { continue } os.Remove(filepath.Join(c.path, file.Name())) } // 重置元数据 c.metadata = make(map[string]*diskCacheMetadata) c.currSize = 0 return nil }