应优先用os.readdir而非filepath.walk,因前者返回有序fs.direntry、支持显式递归控制与过滤,且避免重复os.stat;日期提取须用entry.info().modtime()而非解析路径名,确保时间来源真实可信。

直接结论:别用目录名解析日期,用 entry.Info().ModTime();别裸用 filepath.Walk,优先选 os.ReadDir 控制递归和过滤。
为什么不能硬解析路径名取日期
很多人把“按日期归档”理解成从目录名里截取 2024-03-15 这类字符串,但实际需求是“把今天修改过的目录移到 archive/2024/03/15/”。路径名不规范(比如叫 logs_20240315_v2 或 prod-2024.03.15)就会崩,而且完全脱离文件真实状态。
- 正确做法是调用
entry.Info().ModTime()获取最后修改时间,再格式化为年/月/日 - 注意时区:默认用本地时区,若需 UTC 归档,先调用
t.In(time.UTC) -
os.ReadDir返回的fs.DirEntry已含Info(),复用即可,避免重复调用os.Stat
os.ReadDir vs filepath.Walk:该选哪个
filepath.Walk 不保证遍历顺序,对符号链接处理不透明,容易跳过子目录或重复处理——归档场景下极不可靠。而 os.ReadDir(Go 1.16+)返回有序 fs.DirEntry 列表,能显式控制递归、跳过非目录项、过滤隐藏项。
- 要跳过
.git、.DS_Store等隐藏项:检查entry.Name()[0] == '.' - 只归档叶子目录(即无子目录的目录):对每个
entry调用os.ReadDir并判断子项数量是否为 0 - Go 版本低于 1.16?降级用
os.Open+Readdir,但得额外调用entry.Type()区分文件/目录
跨设备移动目录失败怎么办
os.Rename 在 Linux 下本质是 rename(2) 系统调用,仅支持同文件系统内操作。一旦源路径和目标路径挂载点不同(如 /tmp 和 /home),就会报错 invalid cross-device link。
- 检测是否跨设备:用
os.Stat获取两个路径的sys.Stat_t.Dev字段比对 - 跨设备时改用
io.Copy+os.RemoveAll组合 - 注意原子性:可先写临时目录,再
os.Rename切换,失败时清理临时目录
归档逻辑必须幂等,尤其在定时任务里
用 time.Ticker 做简单归档可以,但生产环境别裸用——进程崩溃就丢任务,重启可能重复执行。哪怕用 robfig/cron/v3,也得防多实例并发冲突。
- 归档前加分布式锁(如 Redis
SET lock:archive NX EX 300) - SQL 归档别直接
DELETE FROM logs WHERE created_at ,改用主键分批删:<code>DELETE FROM logs WHERE id - 大字段表考虑先
UPDATE SET archived = true, data = NULL标记,再异步清空
归档不是“搬完就完”,关键在时间戳来源是否可信、移动是否跨设备、任务是否可重入——这三个点漏掉任何一个,上线后都得半夜爬起来修。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











