必须封装成可启停结构体,启动时机卡在HTTP server成功listen之后;清理器含ticker、done通道、dir和maxAge;Start/Stop控制生命周期;用filepath.WalkDir遍历;统一用ModTime()配合time.Now()快照判断过期;删除前加日志、捕获error并继续执行;不用sync.Map而用map+RWMutex维护元数据。

如何用 time.Ticker 启动文件清理定时任务
直接在 main() 里起 goroutine 调 time.NewTicker 是最常见错误——服务还没监听就开扫,或测试时 goroutine 泄漏导致 panic。必须封装成可启停结构体,启动时机卡在 HTTP server 成功 listen 之后。
清理器结构体至少含:ticker *time.Ticker、done chan struct{}、dir string 和 maxAge time.Duration。Start() 方法里初始化 ticker 并启动 goroutine;Stop() 方法调 ticker.Stop() 并 close done。
- 别用
time.Millisecond级别间隔,生产环境建议最低time.Minute,日志类文件可用1 * time.Hour - 每次 tick 触发后,先检查
dir是否存在且可读,避免 panic - 用
filepath.WalkDir替代filepath.Glob,前者支持递归、错误可控、不加载全部路径到内存
判断文件是否过期的可靠方式
文件系统没有 TTL,os.WriteFile 写出的文件不会自动失效。所谓“过期”,全靠应用层比对时间戳。最稳妥做法是统一用 ModTime(),并配合一次 time.Now() 快照,避免同一轮扫描中因系统时间跳变导致误删。
错误写法:if time.Since(info.ModTime()) > maxAge —— 每次都调 time.Now(),纳秒级误差会累积;正确写法:开头取 now := time.Now(),再判断 now.Sub(info.ModTime()) > maxAge 或 info.ModTime().Add(maxAge).Before(now)。
立即学习“go语言免费学习笔记(深入)”;
- 不要依赖
Atime(访问时间),多数文件系统默认禁用或不精确 - 若需更高精度控制(比如按写入内容里的 JSON 字段判断),得额外解析文件,但会显著拖慢扫描速度
- 对临时文件目录,可提前用
os.Chtimes(path, atime, mtime)把mtime设为期望过期时刻,后续仅比对ModTime即可
安全删除文件时容易忽略的细节
调 os.Remove 前不加防护,会导致权限拒绝、目录非空、文件正被占用等错误静默失败,残留垃圾文件越积越多。
必须做三件事:记录日志、捕获 error、继续执行。别用 os.RemoveAll 直接删整个目录——它可能误删未过期文件;也别在遍历中直接删,否则 filepath.WalkDir 可能 panic 或跳过子项。
- 删除前加日志:
log.Printf("removing expired file: %s, modtime: %v", path, info.ModTime()) - 删完检查 error,但别 return 或 break,要保证本轮所有候选文件都尝试处理
- 若目标是日志轮转文件(如
app.log.2026-06-01),建议用正则匹配命名规则,而非只认扩展名,避免误删.log.bak这类备份文件
为什么不用 sync.Map 或第三方缓存库管文件元数据
sync.Map 不支持安全遍历+删除组合操作,而文件清理本质是「查一堆路径 → 判过期 → 删」,必须可控迭代。用它只会逼你把 key 全 Load 出来再 range,失去并发优势,还增加 GC 压力。
同理,go-cache、cache2go 这类纯内存库和文件清理无关——它们删的是 map 里的 struct,不是磁盘上的 .log 文件。想靠它们“自动删文件”,结果只会是 os.Stat: no such file。
- 真要维护文件元数据索引,用普通
map[string]time.Time+sync.RWMutex更合适:读多(检查是否过期)走 RLock,写少(新增/更新时间戳)走 Lock - 高频写场景(如每秒生成多个临时文件),考虑用 SQLite 存索引,避免内存膨胀;但小规模应用,纯内存 map 足够
- 清理逻辑永远不要假设“文件存在就一定可删”——
os.Remove失败很常见,必须容忍并记录
真正难的不是写个 for range 删除文件,而是让清理行为可中断、可观测、不干扰主业务。哪怕只删一个日志目录,也要把它当成一个带生命周期的小服务来设计。


















