如何通过FileSystemWatcher与Incoming Webhook实现Teams通知推送
文件夹变更Teams通知问题修复与优化
核心问题排查
你的代码存在三个关键问题导致Teams通知失败:
- 推送时机错误:仅在程序启动时发送了一次POST请求,并未在文件创建/变更/删除等事件触发时执行推送逻辑
- JSON格式错误:Teams Webhook要求JSON使用双引号,你用了单引号;且
$Created是无效变量,未关联实际事件信息 - 异步调用不规范:事件处理方法是同步方法,直接调用异步HTTP请求会引发线程管理问题
修正后的完整代码
using System; using System.IO; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; class FolderMonitor { // 复用HttpClient,避免频繁创建连接 private static readonly HttpClient _httpClient = new HttpClient(); // 替换为你的Teams Incoming Webhook URL private const string TeamsWebhookUrl = "你的Teams Webhook地址"; static async Task Main(string[] args) { if (args.Length == 0) { Console.WriteLine("请传入要监控的文件夹路径"); return; } var monitorPath = args[0]; using var watcher = new FileSystemWatcher(monitorPath); watcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size; // 绑定异步事件处理方法 watcher.Changed += OnChangedAsync; watcher.Created += OnCreatedAsync; watcher.Deleted += OnDeletedAsync; watcher.Renamed += OnRenamedAsync; watcher.Error += OnError; watcher.Filter = ""; watcher.IncludeSubdirectories = false; watcher.EnableRaisingEvents = true; Console.WriteLine("监控已启动,按回车键退出。"); Console.ReadLine(); } private static async void OnChangedAsync(object sender, FileSystemEventArgs e) { if (e.ChangeType != WatcherChangeTypes.Changed) return; var message = $"文件已修改:{e.FullPath}"; Console.WriteLine(message); await SendTeamsNotification(message); } private static async void OnCreatedAsync(object sender, FileSystemEventArgs e) { var message = $"文件已创建:{e.FullPath}"; Console.WriteLine(message); await SendTeamsNotification(message); } private static async void OnDeletedAsync(object sender, FileSystemEventArgs e) { var message = $"文件已删除:{e.FullPath}"; Console.WriteLine(message); await SendTeamsNotification(message); } private static async void OnRenamedAsync(object sender, RenamedEventArgs e) { var message = $"文件已重命名:\n旧路径:{e.OldFullPath}\n新路径:{e.FullPath}"; Console.WriteLine(message); await SendTeamsNotification(message); } private static void OnError(object sender, ErrorEventArgs e) => PrintException(e.GetException()); private static async Task SendTeamsNotification(string text) { try { var payload = new { text = text }; var json = JsonSerializer.Serialize(payload); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(TeamsWebhookUrl, content); response.EnsureSuccessStatusCode(); // 抛出HTTP错误状态码的异常 } catch (Exception ex) { Console.WriteLine($"推送Teams通知失败:{ex.Message}"); PrintException(ex.InnerException); } } private static void PrintException(Exception? ex) { if (ex != null) { Console.WriteLine($"异常信息:{ex.Message}"); Console.WriteLine("调用栈:"); Console.WriteLine(ex.StackTrace); Console.WriteLine(); PrintException(ex.InnerException); } } }
关键优化点说明
- 复用HttpClient:静态HttpClient实例避免了每次请求创建新连接的开销,符合.NET最佳实践
- 异步事件处理:将事件处理方法改为
async void(仅适用于事件处理场景),确保HTTP请求异步执行不阻塞监控逻辑 - 正确的JSON序列化:使用
System.Text.Json序列化对象,保证JSON格式符合Teams Webhook要求 - 异常处理:在推送方法中添加异常捕获,记录失败信息,避免监控逻辑崩溃
- 参数校验:启动时检查输入参数,避免空路径导致的错误
额外优化建议
- 处理重复事件:FileSystemWatcher可能会对单个文件变更触发多次
Changed事件,可通过记录文件最后修改时间来过滤重复通知 - 配置化管理:将Webhook URL、监控路径等配置移到配置文件或环境变量,避免硬编码
- 批量通知:如果短时间内有大量文件变更,可合并通知内容,避免Teams被刷屏
- 日志集成:使用日志框架(如Serilog、NLog)替代Console.WriteLine,方便后期排查问题
- 线程安全:如果需要在事件处理中操作共享资源,需添加线程同步机制(如
lock)
内容的提问来源于stack exchange,提问作者Marck
相关产品推荐
相关产品推荐

