fsnotify仅负责通知文件变更,上传需自行集成http/sftp/sdk;监听后立即上传易因编辑器原子写入、缓存未刷盘导致读空或截断,须结合rename事件、文件大小与修改时间稳定校验,并加context超时防护。

直接同步到云端不是 fsnotify 能干的事——它只负责“通知你文件变了”,上传动作得你自己接 HTTP、SFTP 或云厂商 SDK。别指望 fsnotify 自带上传逻辑,否则监听一触发就卡住或并发爆炸。
为什么 fsnotify 监听后立刻上传会失败
常见现象是:改一个文件,watcher.Events 里收到 Write 事件,但 os.Open 打开失败,或读出来是空/截断内容。这不是网络问题,是典型的竞态:
-
vim、VS Code保存时先写临时文件(如.file.txt.swp),再rename覆盖原文件,Write事件在 rename 前就发出来了 - Linux 内核的
IN_CLOSE_WRITE事件虽比IN_MODIFY更可靠,但fsnotify默认不区分底层 inotify 事件类型,event.Op只暴露了聚合后的fsnotify.Write,无法精准对应IN_CLOSE_WRITE - 文件系统缓存未刷盘,
os.Stat()返回的Size还没更新,直接io.Copy就传一半
如何让上传前确认文件已就绪
不能靠“收到事件就传”,得加一层状态校验。核心是两个动作:等待稳定 + 验证完整性。
- 对
event.Name对应的完整路径调用os.Stat(),检查Size和ModTime()是否 100ms 内无变化(避免高频小写) - 若文件大于 1MB,建议计算
sha256.Sum256并和云端已有哈希比对;小文件可跳过哈希,仅比对ModTime和Size - 遇到
event.Op&fsnotify.Rename != 0(含mv、编辑器保存),优先处理该事件——它比Create或Write更接近“写入完成”信号 - 忽略
event.Op&fsnotify.Chmod != 0,除非你明确要同步权限位(云端对象存储一般不支持 POSIX 权限)
上传通道选 HTTP PUT 还是长连接 RPC
取决于你的云端目标:
- 对接 S3 兼容服务(如 MinIO、腾讯云 COS):用
aws-sdk-go-v2的s3.PutObject,它内部复用http.Client,自动处理分块、重试、签名,别自己拼PUT请求 - 对接自建 HTTP 接口:小文件(http.Post +
multipart/form-data;大文件(>1MB)必须分片上传,且服务端要支持POST /upload/chunk+POST /upload/commit两阶段 - 内网高速同步(如 NAS 到另一台 Linux 服务器):用
net/rpc+gob编码,rpc.Register(&SyncServer{})暴露UploadFile方法,比 HTTP 少 3~5ms RTT 开销
所有上传路径都必须加 context.WithTimeout,比如 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second),否则网络卡死时整个 goroutine 会挂住。
怎么避免同步循环和重复上传
云端写回本地时,fsnotify 同样会捕获事件——这是最隐蔽的坑。解决思路不是过滤事件类型,而是打标隔离:
- 上传前在文件同目录写一个隐藏标记文件(如
.synced-<file_id></file_id>),云端下载后也写这个标记;本地监听时跳过所有以.synced-开头的Create事件 - 给每个上传任务生成唯一
file_id(可用filepath.Base(path) + "-" + fmt.Sprintf("%d", stat.ModTime().UnixNano())),传给云端并要求其回传该 ID;本地收到事件时,若event.Name匹配已知file_id,直接丢弃 - 不要监听整个用户主目录或
/home,限定死源目录(如/data/sync),并用filepath.Abs()校验路径合法性,防止软链接逃逸到其他挂载点
真正麻烦的永远不是“怎么上传”,而是“怎么确认该不该传”和“传完别人又改怎么办”。fsnotify 只给你一个毛坯事件,剩下的全是状态机和边界条件。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











