C# CQRS与文件事件 C#如何将文件上传、修改、删除作为事件源

1次阅读

文件系统变化需通过filesystemwatcher捕获后包装为不可变、语义清晰的领域事件(如fileuploadedevent),经事件总线分发;须避免阻塞监听线程、添加去重与correlationid关联命令与事件,并采用双源校验保障可靠性。

C# CQRS与文件事件 C#如何将文件上传、修改、删除作为事件源

文件系统变化怎么变成 CQRS 里的事件

windows 上的 FileSystemWatcher 是最直接的入口,但它不是事件总线,也不能直接塞进 CQRS 的 IEvent 流程里。你得在它触发后,把原始通知包装成领域事件,再交给事件总线(比如 MediatR 或自建的 IEventPublisher)分发。

常见错误是直接在 Changed 回调里做业务逻辑或保存数据库——这会卡住文件监听线程,导致漏事件、重复触发,甚至 FileSystemWatcher 自动停止。

  • 只在回调里提取关键信息:路径、变更类型(Created/Changed/Deleted)、时间戳、是否是目录
  • Task.Runawait 转到后台处理,避免阻塞 FileSystemWatcher 的内部线程池
  • 对同一文件的连续修改(比如保存 word 文档)会触发多次 Changed,加个简单去重缓存(如 ConcurrentDictionary<String datetime></string>),500ms 内相同路径只发一次 FileModifiedEvent

CQRS 事件类该怎么设计才不踩坑

别把 FileSystemEventArgs 直接当领域事件用。CQRS 要求事件是不可变、语义清晰、面向业务的——FileRenamedEventFileSystemChangedEvent 更好理解,也更利于后续重放或审计。

容易忽略的是版本和序列号:文件操作没有天然顺序,但 CQRS 事件流必须有序。建议在事件基类里加 SequenceNumber(由事件存储生成)和 OccurredAtUtc(用 DateTime.UtcNow,别用 Now)。

  • 事件类必须是 public、无参构造、所有属性 get/set,否则序列化(如 json.NET 或 MessagePack)可能失败
  • 路径字段统一用 string,不要存 FileInfoFileStream——它们不能跨进程/序列化
  • 删除事件要包含原文件大小、哈希(如果之前上传时计算过),否则审计时无法确认删的是哪个版本

上传/修改/删除动作如何与文件事件对齐

用户点击“上传”不是文件事件的起点,而是命令(UploadFileCommand)。真正的事件源有两个:命令执行成功后显式发布 FileUploadedEvent,以及 FileSystemWatcher 捕获到磁盘写入完成后的 Created 事件。二者要能关联上——靠同一个 CorrelationId 字段。

典型场景:Web API 接收上传 → 保存到 uploads/ 目录 → 发布 FileUploadedEvent(含 CorrelationId)→ FileSystemWatcher 监听到该路径创建 → 发布带相同 CorrelationIdFileSyncedEvent。这样就能追踪“用户上传”到“磁盘落盘”的完整链路。

  • 不要依赖 FileSystemWatcherCreated 等于“上传完成”——大文件写入可能分块,Created 只表示文件句柄打开,内容未必写完
  • 修改场景同理:先发 FileUpdatedCommand,服务端改完再写磁盘,最后由监听器补发 FileUpdatedEvent,而非监听 Changed 就发
  • 删除操作必须走命令(DeleteFileCommand),禁止前端直删磁盘;否则事件流断裂,审计日志缺失操作人、原因等上下文

FileSystemWatcher 在生产环境为什么经常失效

它不是为高可靠事件总线设计的:缓冲区默认只有 8KB,超量就丢事件;监视网络路径(SMB)基本不可靠;权限不足时静默失败,不抛异常;进程重启后监听丢失——这些都导致事件空洞。

真正能落地的方式是“双源校验”:以定期扫描(如每分钟查 Directory.GetFiles + FileInfo.LastWriteTimeUtc)作为兜底,和 FileSystemWatcher 输出做比对。差异项补发事件,并记录告警。

  • 务必设置 NotifyFilter,只监听需要的类型(如 NotifyFilters.FileName | NotifyFilters.LastWrite),减少内核通知压力
  • 启用 IncludeSubdirectories = true 时,子目录新增会触发两次事件(父目录 Created + 子目录 Created),需在去重逻辑里一并处理
  • linux/macos 不支持 FileSystemWatcher(.NET 6+ 有部分改进但仍有缺陷),跨平台项目必须用 System.IO.Pipelines + inotify 或第三方库如 Libuv 替代

事情说清了就结束。文件事件做 CQRS 源头最难的不是监听,而是让“磁盘发生了什么”和“业务认为发生了什么”始终对得上——中间差的那层映射逻辑,没人能帮你写。

text=ZqhQzanResources