同步代理的核心职责是持续监控本地目录变化、比对云端状态、按需上传下载,并处理冲突与断点续传;关键在于区分变更事件与最终状态,基于文件元数据(大小+etag/md5)判断同步时机,维护本地状态缓存,网络操作须带重试与取消令牌。

同步代理的核心职责是什么
一个本地文件同步代理不是简单地复制粘贴文件,它要持续观察本地目录变化、比对云端状态、按需上传下载,并处理冲突和断点续传。关键在于:它必须区分「变更事件」和「最终状态」——FileSystemWatcher 触发的 Changed 事件非常频繁且可能重复,不能直接拿它当同步指令;真正该同步的是文件内容哈希或最后修改时间戳确认过的终态。
- 同步粒度应基于文件元数据(大小 +
ETag或MD5)而非仅靠时间戳,Windows 文件系统时间精度低,NFS 或虚拟机场景下容易误判 - 必须维护本地状态缓存(如 SQLite 数据库存储每个文件的
local_hash、remote_version、is_deleted),否则每次启动都要全量扫描比对 - 所有网络操作必须带重试(指数退避)和取消令牌(
CancellationToken),避免卡死整个代理进程
如何用 FileSystemWatcher 安全捕获真实变更
FileSystemWatcher 是入口,但默认配置极易漏事件或触发多次。它本身不保证事件顺序,也不合并连续写入(例如 Word 保存会触发 3–4 次 Changed)。
- 设置
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,禁用FileName和DirectoryName,减少重命名/移动带来的干扰 - 必须启用
IncludeSubdirectories = true,但为避免递归监听导致句柄耗尽,建议只监听用户指定的根路径,子目录由程序逻辑遍历发现 - 对每个
Changed事件,立刻启动一个带延时的去抖任务(例如Task.Delay(1000, token)),等静默期结束后再读取文件大小和哈希——这是避免“写一半就同步”的唯一可靠方式 - 删除事件(
Deleted)需单独处理并记录到本地状态库,不能依赖后续扫描推断
上传/下载如何避免阻塞和重复
同步代理常因大文件上传卡住 UI 或拖慢扫描。所有 I/O 必须异步且可中断,同时防止同一文件被多次排队。
- 使用
HttpClient.PutAsync()或分块POST上传,配合IProgress<long></long>更新进度,上传前先校验本地哈希是否已存在于云端(查/api/v1/file/info?hash=xxx) - 下载使用
Range请求支持断点续传,把临时文件写到.sync_tmp_abc123,完整校验哈希后再原子重命名为目标名 - 维护一个内存中
ConcurrentDictionary<string task></string>(key 为文件相对路径),提交新任务前先TryRemove旧任务并await其完成,防止并发上传同一文件
冲突检测与用户干预点在哪
自动覆盖不是好策略。Dropbox 式行为是:当本地和云端都修改过同一文件,保留两者,生成类似 report.docx (Your Conflicted Copy 2024-05-22).docx 的副本。
- 冲突判定依据是:本地有修改(
local_hash != remote_hash)且云端也有更新(remote_version > local_version) - 不要尝试自动合并二进制文件,文本文件也仅在明确标记为
text/plain且启用 diff 工具时才走合并逻辑 - 把冲突文件路径写入一个
conflicts.json清单,供 GUI 层弹窗提示,或 CLI 输出警告行 —— 这个清单本身也要同步到云端,让多端知道“此处有未决冲突”
本地状态库结构稍复杂,但少它一天都跑不稳;哈希计算别图省事用 File.ReadAllBytes(),大文件必须流式计算;还有,别忘了 Windows 上长路径(>260 字符)需要在 app.manifest 里启用 longPathAware。










