必须监听父目录而非单个文件,因编辑器原子写入会销毁原inode;需用filepath.base()过滤事件、位运算判断fsnotify.write/rename、os.stat()确认文件有效性,并妥善管理子进程生命周期以防端口冲突。

直接用 fsnotify 监听目录、过滤 .go 文件、触发 exec.Command("go", "run", "main.go") 是可行的,但不处理编辑器原子写入、进程残留和事件漏收,十次有八次会卡住或端口冲突。
为什么 fsnotify.Add("main.go") 总是不触发保存事件
VS Code、vim、Sublime 等编辑器默认启用原子写入:先写临时文件(如 main.go~ 或 .main.go.swp),再 rename 覆盖原文件。你监听的 main.go inode 已失效,Write 事件根本不会发生。
- 必须监听父目录(如
"."或"cmd/myserver"),再用filepath.Base(event.Name)判断是否为目标文件名 - 要同时响应
fsnotify.Write和fsnotify.Rename事件——后者才是编辑器保存的真实信号 - 判断操作类型必须用位运算:
if e.Op&fsnotify.Write != 0或if e.Op&fsnotify.Rename != 0,不能写== - 收到事件后立刻
os.Stat(e.Name),确认文件存在、非零长度、且后缀不是~、.swp、.tmp
重启时端口被占、进程没死干净怎么办
新进程启动时若旧进程还在 listen,net.Listen 直接报 address already in use,这不是 Go 错误,是操作系统级资源冲突。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 旧进程不能靠
os.Exit(0)——那是退出当前进程,而你的监控程序和服务器是两个独立进程 - 必须拿到旧
*os.Process,调proc.Signal(os.Interrupt)或proc.Kill(),再等proc.Wait()(建议加 2 秒超时) - 不要在事件 goroutine 里直接
exec.Command().Run()——会阻塞监听,导致后续事件堆积丢失 - 重启逻辑建议用
time.AfterFunc做简单防抖(如 300ms 内只执行一次),避免连续保存触发多次
要不要自己写,还是直接上 air
自己实现能控制细节(比如跳过 vendor、按包名过滤、集成构建步骤),但 air 已覆盖绝大多数开发场景,配置即开箱即用。
-
air默认监听.go、.html、.tmpl,支持自定义air.toml中的build.cmd和build.bin - 若项目用了 cgo 或需预编译,
air的build.flags = ["-tags=dev"]比手写更稳 - 真需要自研只有两种情况:要嵌入到已有 CLI 工具中,或需在重启前后执行特定 hook(如数据库迁移检查)
- 复杂点不在监听本身,而在子进程生命周期管理——你得决定是让服务器自己响应
SIGHUP平滑重启,还是由监控进程完全接管。后者简单,前者更健壮,但信号传递和状态同步容易出错
最易被忽略的是符号链接和深层嵌套目录:fsnotify 默认跳过软链;macOS 上超过 100 层嵌套可能触发 kqueue 限制,需捕获 fsnotify.ErrEventOverflow。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










