✨ feat(*): 添加管理员解锁与 SFTP 探活能力
* 新增管理员解锁弹窗、手动操作权限控制与倒计时状态 * 支持系统设置页测试 SFTP 连接,并在启动时执行探活 * 补充设计时服务、全局异常兜底与相关单元测试
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxiOmron.PcbCheck.Options;
|
||||
using AxiOmron.PcbCheck.Services.Implementations;
|
||||
using AxiOmron.PcbCheck.Services.Interfaces;
|
||||
using AxiOmron.PcbCheck.ViewModels;
|
||||
using AxiOmron.PcbCheck.Views.Pages;
|
||||
using AxiOmron.PcbCheck.Views.Windows;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -19,6 +21,7 @@ namespace AxiOmron.PcbCheck;
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
private static readonly NLog.Logger FallbackLogger = NLog.LogManager.GetCurrentClassLogger();
|
||||
private IHost? _host;
|
||||
|
||||
/// <summary>
|
||||
@@ -33,6 +36,7 @@ public partial class App : Application
|
||||
protected override async void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
RegisterGlobalExceptionHandlers();
|
||||
try
|
||||
{
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
@@ -53,6 +57,7 @@ public partial class App : Application
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogGlobalException("应用启动失败", ex);
|
||||
MessageBox.Show($"应用启动失败: {ex.Message}", "Axi Omron PCB Check", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Shutdown(-1);
|
||||
}
|
||||
@@ -64,6 +69,7 @@ public partial class App : Application
|
||||
/// <param name="e">退出事件参数。</param>
|
||||
protected override async void OnExit(ExitEventArgs e)
|
||||
{
|
||||
UnregisterGlobalExceptionHandlers();
|
||||
if (_host is not null)
|
||||
{
|
||||
try
|
||||
@@ -118,13 +124,99 @@ public partial class App : Application
|
||||
services.AddSingleton<WorkflowHostedService>();
|
||||
services.AddSingleton<IWorkflowControlService>(sp => sp.GetRequiredService<WorkflowHostedService>());
|
||||
services.AddHostedService(sp => sp.GetRequiredService<WorkflowHostedService>());
|
||||
services.AddSingleton<IAdminUnlockDialogService, AdminUnlockDialogService>();
|
||||
|
||||
services.AddSingleton<MainWindowViewModel>();
|
||||
services.AddSingleton<SystemSettingViewModel>();
|
||||
services.AddSingleton<DashboardPage>();
|
||||
services.AddSingleton<SystemSettingsPage>();
|
||||
services.AddTransient<AdminUnlockDialog>();
|
||||
services.AddSingleton<MainWindow>();
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册全局异常处理器,用于兜底记录 UI 线程、未观察任务和域级异常。
|
||||
/// </summary>
|
||||
private void RegisterGlobalExceptionHandlers()
|
||||
{
|
||||
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException += OnCurrentDomainUnhandledException;
|
||||
TaskScheduler.UnobservedTaskException += OnTaskSchedulerUnobservedTaskException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销全局异常处理器。
|
||||
/// </summary>
|
||||
private void UnregisterGlobalExceptionHandlers()
|
||||
{
|
||||
DispatcherUnhandledException -= OnDispatcherUnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException -= OnCurrentDomainUnhandledException;
|
||||
TaskScheduler.UnobservedTaskException -= OnTaskSchedulerUnobservedTaskException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 UI 线程未捕获异常,记录日志并阻止应用直接闪退。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件源。</param>
|
||||
/// <param name="e">异常事件参数。</param>
|
||||
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
LogGlobalException("UI 线程未处理异常", e.Exception);
|
||||
MessageBox.Show($"程序捕获到未处理异常,已写入日志:{e.Exception.Message}", "Axi Omron PCB Check", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理未观察任务异常,记录日志并标记为已观察。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件源。</param>
|
||||
/// <param name="e">异常事件参数。</param>
|
||||
private void OnTaskSchedulerUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
LogGlobalException("未观察任务异常", e.Exception);
|
||||
e.SetObserved();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理应用程序域未捕获异常,尽量在进程退出前补充日志。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件源。</param>
|
||||
/// <param name="e">异常事件参数。</param>
|
||||
private void OnCurrentDomainUnhandledException(object? sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var exception = e.ExceptionObject as Exception ?? new Exception(e.ExceptionObject?.ToString() ?? "未知未处理异常");
|
||||
LogGlobalException($"应用程序域未处理异常(IsTerminating={e.IsTerminating})", exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一记录全局异常日志,优先复用宿主日志器,失败时退回 NLog 直接写入。
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息。</param>
|
||||
/// <param name="exception">异常对象。</param>
|
||||
private static void LogGlobalException(string message, Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Services is not null)
|
||||
{
|
||||
var logger = Services.GetService<ILogger<App>>();
|
||||
logger?.LogError(exception, message);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略宿主日志失败,继续使用回退日志。
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FallbackLogger.Error(exception, message);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 全局兜底日志不再向外抛出。
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user