diff --git a/Helper/NotificationHelper.cs b/Helper/NotificationHelper.cs
index 320e219..b592e2e 100644
--- a/Helper/NotificationHelper.cs
+++ b/Helper/NotificationHelper.cs
@@ -1,6 +1,9 @@
+
+using System;
+using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
+using System.Threading;
using CommunityToolkit.Mvvm.Messaging;
-using NLog;
using PMSWPF.Enums;
using PMSWPF.Message;
@@ -8,113 +11,147 @@ namespace PMSWPF.Helper;
///
/// 通知帮助类,用于显示各种类型的通知消息,并集成日志记录功能。
+/// 新增了通知节流功能,以防止在短时间内向用户发送大量重复的通知。
///
-public class NotificationHelper
+public static class NotificationHelper
{
///
- /// 获取当前类的 NLog 日志实例。
+ /// 内部类,用于存储节流通知的状态信息。
///
- private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
+ private class ThrottledNotificationInfo
+ {
+ public int Count;
+ public Timer Timer;
+ public NotificationType NotificationType;
+ }
+
+ private static readonly ConcurrentDictionary ThrottledNotifications = new ConcurrentDictionary();
+ private const int ThrottleTimeSeconds = 30;
///
- /// 显示一个通用通知消息,并根据通知类型记录日志。
+ /// 内部核心通知发送方法,包含了节流逻辑。
///
- /// 通知消息内容。
- /// 通知类型(如信息、错误、成功等),默认为信息。
- /// 是否为全局通知(目前未使用,保留参数)。
- /// 自动捕获:调用此方法的源文件完整路径。
- /// 自动捕获:调用此方法的行号。
- public static void ShowMessage(string msg, NotificationType notificationType = NotificationType.Info,
- bool isGlobal = false, [CallerFilePath] string callerFilePath = "",
- [CallerLineNumber] int callerLineNumber = 0)
+ private static void SendNotificationInternal(string msg, NotificationType notificationType, bool throttle, Exception exception, string callerFilePath, string callerMember, int callerLineNumber)
{
- // 根据通知类型记录日志
+ // 根据通知类型决定日志级别,并使用 NlogHelper 记录日志(利用其自身的节流逻辑)
if (notificationType == NotificationType.Error)
{
- Logger.Error($"{msg} (File: {callerFilePath}, Line: {callerLineNumber})");
+ NlogHelper.Error(msg, exception, throttle, callerFilePath, callerMember, callerLineNumber);
}
else
{
- Logger.Info($"{msg} (File: {callerFilePath}, Line: {callerLineNumber})");
+ NlogHelper.Info(msg, throttle, callerFilePath, callerMember, callerLineNumber);
}
- // 通过消息总线发送通知消息,以便UI层可以订阅并显示
- WeakReferenceMessenger.Default.Send(
- new NotificationMessage(msg, notificationType));
+ // 如果不启用通知节流,则直接发送通知并返回。
+ 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 static void ShowMessage(string msg, NotificationType notificationType = NotificationType.Info, bool throttle = false,
+ [CallerFilePath] string callerFilePath = "",
+ [CallerLineNumber] int callerLineNumber = 0)
+ {
+ SendNotificationInternal(msg, notificationType, throttle, null, callerFilePath, "", callerLineNumber);
+ }
+
+ ///
+ /// 显示一个错误通知消息,并记录错误日志。支持节流。
///
/// 错误消息内容。
/// 可选:要记录的异常对象。
+ /// 是否启用通知和日志节流。
/// 自动捕获:调用此方法的源文件完整路径。
/// 自动捕获:调用此方法的成员或属性名称。
/// 自动捕获:调用此方法的行号。
- public static void ShowError(string msg, Exception exception = null,
+ public static void ShowError(string msg, Exception exception = null, bool throttle = false,
[CallerFilePath] string callerFilePath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = 0)
{
- // 使用 using 语句确保 MappedDiagnosticsLogicalContext 在作用域结束时被清理。
- // 这对于异步方法尤其重要,因为上下文会随着异步操作的流转而传递。
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerFilePath", callerFilePath))
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerLineNumber", callerLineNumber))
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerMember", callerMember))
- {
- // 记录错误日志,包含异常信息(如果提供)
- Logger.Error(exception, msg);
- // 通过消息总线发送错误通知
- WeakReferenceMessenger.Default.Send(
- new NotificationMessage(msg, NotificationType.Error));
- }
- }
-
- ///
- /// 显示一个成功通知消息,并记录信息日志。
- ///
- /// 成功消息内容。
- /// 自动捕获:调用此方法的源文件完整路径。
- /// 自动捕获:调用此方法的成员或属性名称。
- /// 自动捕获:调用此方法的行号。
- public static void ShowSuccess(string msg,
- [CallerFilePath] string callerFilePath = "",
- [CallerMemberName] string callerMember = "",
- [CallerLineNumber] int callerLineNumber = 0)
- {
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerFilePath", callerFilePath))
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerLineNumber", callerLineNumber))
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerMember", callerMember))
- {
- // 记录信息日志
- Logger.Info(msg);
- // 通过消息总线发送成功通知
- WeakReferenceMessenger.Default.Send(
- new NotificationMessage(msg, NotificationType.Success));
- }
+ SendNotificationInternal(msg, NotificationType.Error, throttle, exception, callerFilePath, callerMember, callerLineNumber);
}
///
- /// 显示一个信息通知消息,并记录信息日志。
+ /// 显示一个成功通知消息,并记录信息日志。支持节流。
///
- /// 信息消息内容。
+ /// 成功消息内容。
+ /// 是否启用通知和日志节流。
/// 自动捕获:调用此方法的源文件完整路径。
/// 自动捕获:调用此方法的成员或属性名称。
/// 自动捕获:调用此方法的行号。
- public static void ShowInfo(string msg,
+ public static void ShowSuccess(string msg, bool throttle = false,
[CallerFilePath] string callerFilePath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = 0)
{
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerFilePath", callerFilePath))
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerLineNumber", callerLineNumber))
- using (MappedDiagnosticsLogicalContext.SetScoped("CallerMember", callerMember))
- {
- // 记录信息日志
- Logger.Info(msg);
- // 通过消息总线发送信息通知
- WeakReferenceMessenger.Default.Send(
- new NotificationMessage(msg, NotificationType.Info));
- }
+ SendNotificationInternal(msg, NotificationType.Success, throttle, null, callerFilePath, callerMember, callerLineNumber);
}
-}
\ No newline at end of file
+
+ ///
+ /// 显示一个信息通知消息,并记录信息日志。支持节流。
+ ///
+ /// 信息消息内容。
+ /// 是否启用通知和日志节流。
+ /// 自动捕获:调用此方法的源文件完整路径。
+ /// 自动捕获:调用此方法的成员或属性名称。
+ /// 自动捕获:调用此方法的行号。
+ public static void ShowInfo(string msg, bool throttle = false,
+ [CallerFilePath] string callerFilePath = "",
+ [CallerMemberName] string callerMember = "",
+ [CallerLineNumber] int callerLineNumber = 0)
+ {
+ SendNotificationInternal(msg, NotificationType.Info, throttle, null, callerFilePath, callerMember, callerLineNumber);
+ }
+}