using System; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Threading; using CommunityToolkit.Mvvm.Messaging; using DMS.Core.Enums; using DMS.Message; using DMS.WPF.Interfaces; using Microsoft.Extensions.Logging; namespace DMS.WPF.Services; /// /// 通知服务类,用于显示各种类型的通知消息,并集成日志记录功能。 /// 新增了通知节流功能,以防止在短时间内向用户发送大量重复的通知。 /// public class NotificationService : INotificationService { private readonly ILogger _logger; /// /// 内部类,用于存储节流通知的状态信息。 /// private class ThrottledNotificationInfo { public int Count; public Timer Timer; public NotificationType NotificationType; } private readonly ConcurrentDictionary _throttledNotifications = new ConcurrentDictionary(); private const int ThrottleTimeSeconds = 30; public NotificationService(ILogger logger) { _logger = logger; } /// /// 内部核心通知发送方法,包含了节流逻辑。 /// private void SendNotificationInternal(string msg, NotificationType notificationType, bool throttle, Exception exception, string callerFilePath, string callerMember, int callerLineNumber) { // 根据通知类型决定日志级别,并使用 ILogger 记录日志 if (notificationType == NotificationType.Error) { _logger.LogError(exception, $"[{callerFilePath}:{callerMember}:{callerLineNumber}] {msg}"); } else { switch (notificationType) { case NotificationType.Info: _logger.LogInformation($"[{callerFilePath}:{callerMember}:{callerLineNumber}] {msg}"); break; case NotificationType.Success: _logger.LogInformation($"[{callerFilePath}:{callerMember}:{callerLineNumber}] {msg}"); break; case NotificationType.Warning: _logger.LogWarning($"[{callerFilePath}:{callerMember}:{callerLineNumber}] {msg}"); break; } } // 如果不启用通知节流,则直接发送通知并返回。 if (!throttle) { WeakReferenceMessenger.Default.Send(new NotificationMessage(msg, notificationType)); return; } var key = $"{callerFilePath}:{callerLineNumber}:{msg}"; _throttledNotifications.AddOrUpdate( key, // --- 添加逻辑:当通知第一次被节流时执行 --- _ => { // 1. 首次出现,立即发送一次通知。 WeakReferenceMessenger.Default.Send(new NotificationMessage(msg, notificationType)); // 2. 创建新的节流信息对象。 var newThrottledNotification = new ThrottledNotificationInfo { Count = 1, NotificationType = notificationType }; // 3. 创建并启动计时器。 newThrottledNotification.Timer = new Timer(s => { if (_throttledNotifications.TryRemove(key, out var finishedNotification)) { finishedNotification.Timer.Dispose(); if (finishedNotification.Count > 1) { var summaryMsg = $"消息 '{msg}' 在过去 {ThrottleTimeSeconds} 秒内出现了 {finishedNotification.Count} 次。"; WeakReferenceMessenger.Default.Send(new NotificationMessage(summaryMsg, finishedNotification.NotificationType)); } } }, null, ThrottleTimeSeconds * 1000, Timeout.Infinite); return newThrottledNotification; }, // --- 更新逻辑:当通知在节流窗口内再次出现时执行 --- (_, existingNotification) => { Interlocked.Increment(ref existingNotification.Count); return existingNotification; }); } /// /// 显示一个通用通知消息,并根据通知类型记录日志。支持节流。 /// /// 通知消息内容。 /// 通知类型(如信息、错误、成功等)。 /// 是否启用通知节流。 /// 自动捕获:调用此方法的源文件完整路径。 /// 自动捕获:调用此方法的行号。 public void ShowMessage(string msg, NotificationType notificationType = NotificationType.Info, bool throttle = true, [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { SendNotificationInternal(msg, notificationType, throttle, null, callerFilePath, "", callerLineNumber); } /// /// 显示一个错误通知消息,并记录错误日志。支持节流。 /// /// 错误消息内容。 /// 可选:要记录的异常对象。 /// 是否启用通知和日志节流。 /// 自动捕获:调用此方法的源文件完整路径。 /// 自动捕获:调用此方法的成员或属性名称。 /// 自动捕获:调用此方法的行号。 public void ShowError(string msg, Exception exception = null, bool throttle = true, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = 0) { SendNotificationInternal(msg, NotificationType.Error, throttle, exception, callerFilePath, callerMember, callerLineNumber); } /// /// 显示一个成功通知消息,并记录信息日志。支持节流。 /// /// 成功消息内容。 /// 是否启用通知和日志节流。 /// 自动捕获:调用此方法的源文件完整路径。 /// 自动捕获:调用此方法的成员或属性名称。 /// 自动捕获:调用此方法的行号。 public void ShowSuccess(string msg, bool throttle = true, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = 0) { SendNotificationInternal(msg, NotificationType.Success, throttle, null, callerFilePath, callerMember, callerLineNumber); } /// /// 显示一个信息通知消息,并记录信息日志。支持节流。 /// /// 信息消息内容。 /// 是否启用通知和日志节流。 /// 自动捕获:调用此方法的源文件完整路径。 /// 自动捕获:调用此方法的成员或属性名称。 /// 自动捕获:调用此方法的行号。 public void ShowInfo(string msg, bool throttle = true, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = 0) { SendNotificationInternal(msg, NotificationType.Info, throttle, null, callerFilePath, callerMember, callerLineNumber); } /// /// 显示一个警告通知消息,并记录警告日志。支持节流。 /// /// 警告消息内容。 /// 是否启用通知和日志节流。 /// 自动捕获:调用此方法的源文件完整路径。 /// 自动捕获:调用此方法的成员或属性名称。 /// 自动捕获:调用此方法的行号。 public void ShowWarn(string msg, bool throttle = true, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = 0) { SendNotificationInternal(msg, NotificationType.Warning, throttle, null, callerFilePath, callerMember, callerLineNumber); } }