package main import ( "log" "os" "path/filepath" "sync" "time" "github.com/robfig/cron/v3" ) // ----------------------------------------------------------------------------- // TTL 清理器 // ----------------------------------------------------------------------------- var cleanerOnce sync.Once func startCleaner() { cleanerOnce.Do(func() { if err := os.MkdirAll(*uploadDir, 0o755); err != nil { log.Fatalf("create upload dir failed: %v", err) } c := cron.New() _, err := c.AddFunc("@every "+scanEvery.String(), func() { now := time.Now() removed := 0 var removedBytes int64 _ = filepath.Walk(*uploadDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } if now.Sub(info.ModTime()) > *fileTTL { if rmErr := os.Remove(path); rmErr != nil { log.Printf("[cleaner] remove failed: %s err=%v", path, rmErr) } else { log.Printf("[cleaner] removed: %s (age=%v, size=%s)", path, now.Sub(info.ModTime()), formatSize(info.Size())) removed++ removedBytes += info.Size() } } return nil }) quotaSub(removedBytes) // 漂移修正: 每小时一次 rescan 校正任何来源不明的字节数偏差 rescanQuota() log.Printf("[cleaner] scan done, removed=%d (%s), quota used now=%s", removed, formatSize(removedBytes), formatSize(quotaUsedSnapshot())) }) if err != nil { log.Fatalf("add cron job failed: %v", err) } c.Start() log.Printf("[cleaner] started, schedule=@every %s dir=%s ttl=%s", scanEvery, *uploadDir, *fileTTL) }) }