应使用 Directory.GetFiles 配 SearchOption.TopDirectoryOnly 显式限定范围,对每个 FileInfo 调用 Refresh() 再读 LastWriteTime 以避免缓存偏差,并用 UTC 时间统一计算归档窗口。

如何用 Directory.GetFiles 安全筛选带时间条件的旧文件
直接遍历再判断 LastWriteTime 是最常见做法,但容易漏掉符号链接、权限不足目录或正在被占用的文件。别用 Directory.EnumerateFiles 配 Where 过滤——它会在整个树遍历完才开始过滤,内存和耗时都不可控。
- 始终用
SearchOption.TopDirectoryOnly显式限定范围,避免意外递归子目录 - 捕获
UnauthorizedAccessException和DirectoryNotFoundException,跳过而非中断 - 对每个
FileInfo调用Refresh()再读LastWriteTime,防止缓存导致时间偏差(尤其在 NTFS 卷挂载点或网络路径) - 示例:查 90 天前的
.log文件:var cutoff = DateTime.Now.AddDays(-90); var files = Directory.GetFiles(@"C:logs", "*.log", SearchOption.TopDirectoryOnly) .Where(p => { try { var fi = new FileInfo(p); fi.Refresh(); return fi.LastWriteTime <= cutoff;} catch {return false;} }).ToArray();
删除前必须绕过 IOException:文件正被其他进程锁定怎么办
直接 File.Delete 抛 IOException“The process cannot access the file……”是高频失败点,不是权限问题,而是日志写入器、IDE、甚至 Windows 资源管理器预览窗格在 hold 句柄。
- 先尝试
File.Move到临时隔离目录(如@".archive_quarantine"),成功即代表可操作;失败再重试 2 次,间隔 100ms - 若仍失败,改用
File.Replace将原文件替换成空文件(需提前创建),这能绕过部分锁机制 - 永远不要用
Process.Kill或Handle操作强行释放——会破坏应用一致性 - 注意:NTFS 压缩或加密属性可能让
Delete静默失败,删后检查File.Exists确认
归档逻辑里 ZipArchive 的三个隐性陷阱
用 System.IO.Compression.ZipArchive 打包旧文件看似简单,但压缩流生命周期、路径编码、时间戳精度三处极易出错。
- 必须用
using (var fs = File.Create(zipPath)) using (var archive = new ZipArchive(fs, ZipArchiveMode.Create))—— 顺序反了(archive 在外层)会导致 zip 文件损坏 - 添加文件时,
archive.CreateEntry的entryName必须是相对路径(如"logs/app_20230101.log"),不能含盘符或..,否则解压失败且无明确错误 -
ZipArchiveEntry.LastWriteTime默认取当前时间,要保留原始时间得手动赋值:entry.LastWriteTime = new FileInfo(filePath).LastWriteTime - 大文件(>100MB)建议分块写入,避免
OutOfMemoryException;小文件则合并进单个ZipArchive实例,别为每个文件开新 archive
Windows 服务场景下 FileSystemWatcher 不适合做归档触发器
想监听文件变化自动归档?FileSystemWatcher 在服务中极不稳定:丢失事件、重复触发、无法跨卷监控,且不感知“写入完成”语义(只管 Create/Change,不管 WriteComplete)。
- 归档策略本质是时间驱动,不是事件驱动——老老实实用
Timer定期扫描,精度可控、行为可测 - 若真要用监听,至少加一层落盘确认:把待归档文件名写入
.pending标记文件,定时任务读标记再执行,避免监听误判 - 服务账户默认无权访问用户文档目录,所有路径必须显式配置,且测试时用
sc config YourService obj= "NT AUTHORITYLocalService"验证权限边界 - 别依赖
Watcher.Changed的NotifyFilters.LastWrite判断“写完”,它只表示有写操作发生,不保证数据刷盘
真正麻烦的是跨时区部署和夏令时切换——DateTime.Now 在服务器集群里可能不一致,归档窗口计算必须统一用 UTC 时间,且策略配置里明确写死时区偏移,别信系统时钟。