| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- using Avalonia;
- using Avalonia.Controls;
- using Avalonia.Controls.ApplicationLifetimes;
- using Avalonia.Markup.Xaml;
- using Avalonia.Markup.Xaml.Styling;
- using Microsoft.Extensions.DependencyInjection;
- using System.Threading;
- using YZWater.Avalonia.Controls;
- using YZWater.Avalonia.Views;
- using YZWater.Core.Services;
- using YZWater.Core.ViewModels;
- namespace YZWater.Avalonia;
- public partial class App : Application
- {
- private ResourceInclude? _darkTheme;
- private ResourceInclude? _lightTheme;
- private DataLoggingService? _dataLogger;
- private HubServer? _hubServer;
- private HubClient? _hubClient;
- private Action? _userLoggedOutHandler;
- private int _cleanupStarted;
- public override void Initialize()
- {
- LogService.Initialize();
- AvaloniaXamlLoader.Load(this);
- _darkTheme = new ResourceInclude(new Uri("avares://YZWater.Avalonia/Themes/IndustrialTheme.axaml"))
- { Source = new Uri("avares://YZWater.Avalonia/Themes/IndustrialTheme.axaml") };
- _lightTheme = new ResourceInclude(new Uri("avares://YZWater.Avalonia/Themes/IndustrialThemeLight.axaml"))
- { Source = new Uri("avares://YZWater.Avalonia/Themes/IndustrialThemeLight.axaml") };
- ThemeService.Instance.ThemeChanged += ApplyTheme;
- // 全局异常处理
- AppDomain.CurrentDomain.UnhandledException += (s, e) =>
- {
- LogCrash("AppDomain.UnhandledException", e.ExceptionObject as Exception);
- };
- TaskScheduler.UnobservedTaskException += (s, e) =>
- {
- LogCrash("TaskScheduler.UnobservedTaskException", e.Exception);
- e.SetObserved();
- };
- global::Avalonia.Threading.Dispatcher.UIThread.UnhandledException += (s, e) =>
- {
- LogCrash("UIThread.UnhandledException", e.Exception);
- e.Handled = true;
- };
- }
- private static void LogCrash(string source, Exception? ex)
- {
- try
- {
- Serilog.Log.Fatal(ex, "未处理异常 [{Source}]", source);
- File.AppendAllText("crash.log", $"[{DateTime.Now}] {source}:\n{ex}\n\n");
- }
- catch { /* 日志写入失败时不能再抛异常 */ }
- }
- public override void OnFrameworkInitializationCompleted()
- {
- DatabaseService.Initialize();
- // 初始化 ServiceLocator(渐进式 DI 迁移)
- var services = new ServiceCollection();
- services.AddSingleton<IConfigService>(ConfigService.Instance);
- ServiceLocator.Configure(services.BuildServiceProvider());
- // PlcService.Initialize() 延迟到 StartPlcServices() 中调用(登录成功后)
- // 启动单实例监听(新启动时会激活本窗口)
- SingleInstanceHelper.StartListening();
- ThemeService.Instance.LoadFromConfig();
- LanguageService.Instance.LoadFromConfig();
- PlcConfig.LoadFromConfig();
- ThemeHelper.SetTheme(ThemeService.Instance.IsDarkTheme);
- // 加载项目档案(如果配置了 ActiveProject)
- ProjectService.EnsureProjectsDir();
- var config = ConfigService.GetConfig();
- if (!string.IsNullOrWhiteSpace(config?.ActiveProject))
- {
- ProjectService.LoadProject(config.ActiveProject);
- }
- if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
- {
- desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
- // 注册全局退出清理(无论以何种方式退出都会执行)
- desktop.ShutdownRequested += (_, _) =>
- {
- Cleanup();
- };
- desktop.Exit += (_, _) => Cleanup();
- ShowLoginWindow(desktop);
- }
- base.OnFrameworkInitializationCompleted();
- }
- private void ShowLoginWindow(IClassicDesktopStyleApplicationLifetime desktop)
- {
- var loginVm = new LoginViewModel();
- var loginView = new LoginView { DataContext = loginVm };
- loginVm.LoginSucceeded += () =>
- {
- StartServices();
- ShowMainWindow(desktop);
- loginView.Close();
- };
- loginView.Closed += (_, _) =>
- {
- if (!AuthService.IsLoggedIn)
- {
- desktop.Shutdown();
- }
- };
- desktop.MainWindow = loginView;
- loginView.Show();
- }
- private async void StartServices()
- {
- var config = ConfigService.GetConfig();
- var mode = config?.RunMode ?? "Direct";
- // 启动数据库备份服务(启动时备份 + 定时备份)
- try
- {
- await BackupService.StartAsync();
- Serilog.Log.Information("BackupService 已启动");
- }
- catch (Exception ex)
- {
- Serilog.Log.Error(ex, "启动 BackupService 失败");
- }
- // 启动数据保留清理服务(启动时清理 + 定时清理)
- try
- {
- DataRetentionService.Start();
- Serilog.Log.Information("DataRetentionService 已启动");
- }
- catch (Exception ex)
- {
- Serilog.Log.Error(ex, "启动 DataRetentionService 失败");
- }
- switch (mode)
- {
- case "Server":
- StartServerMode(config!);
- break;
- case "Client":
- StartClientMode(config!);
- break;
- default:
- StartDirectMode();
- break;
- }
- }
- /// <summary>
- /// 启动 PLC 相关服务(直连和服务端共用)
- /// </summary>
- private void StartPlcServices()
- {
- var config = ConfigService.GetConfig();
- if (config?.RunMode == "Mock")
- {
- MockPlcService.Enable();
- Serilog.Log.Information("启动 Mock 模式(模拟数据)");
- }
- else
- {
- PlcService.Initialize();
- }
- PlcPollingService.Instance.Start();
- AlarmService.Instance.Start();
- _dataLogger = new DataLoggingService(PlcPollingService.Instance);
- _dataLogger.Start();
- // 启动看门狗服务
- StartWatchdog();
- // 启动液位自动控制
- AutoControlService.Instance.Start();
- // 初始化新集成服务(与现有服务并行运行)
- InitializeNewServicesAsync().ConfigureAwait(false);
- }
- private void StartWatchdog()
- {
- try
- {
- var watchdog = WatchdogService.Instance;
- // 注册 PLC 轮询服务的健康检查
- watchdog.RegisterHealthCheck("PlcPolling", PlcPollingService.Instance);
- // 订阅看门狗事件
- watchdog.ServiceUnhealthy += (serviceName, reason) =>
- {
- Serilog.Log.Warning("看门狗: 服务 {ServiceName} 不健康 - {Reason}", serviceName, reason);
- // 可以发送告警通知
- };
- watchdog.ServiceRestarted += (serviceName) =>
- {
- Serilog.Log.Information("看门狗: 服务 {ServiceName} 已重启", serviceName);
- // 可以发送告警通知
- };
- watchdog.ServiceRecovered += (serviceName, reason) =>
- {
- Serilog.Log.Information("看门狗: 服务 {ServiceName} 已恢复 - {Reason}", serviceName, reason);
- };
- watchdog.Start();
- Serilog.Log.Information("看门狗服务已启动");
- }
- catch (Exception ex)
- {
- Serilog.Log.Error(ex, "启动看门狗服务失败");
- }
- }
- /// <summary>
- /// 初始化新集成的服务(Stateless/Quartz/Elsa)
- /// </summary>
- private async Task InitializeNewServicesAsync()
- {
- try
- {
- // 1. 初始化设备状态机(从项目档案或默认配置读取设备列表)
- DeviceStateMachineManager.Instance.InitializeDefaultDevices();
- DeviceStateMachineManager.Instance.SubscribeToPlcData();
- Serilog.Log.Information("设备状态机已初始化并订阅 PLC 数据");
- // 2+3. 并行启动 Quartz 调度器和 Elsa 工作流引擎(互不依赖)
- await Task.WhenAll(
- QuartzSchedulerService.Instance.StartAsync(),
- WorkflowService.Instance.InitializeAsync()
- );
- Serilog.Log.Information("Quartz 调度器和 Elsa 工作流引擎已启动");
- }
- catch (Exception ex)
- {
- Serilog.Log.Error(ex, "初始化新服务失败");
- }
- }
- private void StartDirectMode()
- {
- StartPlcServices();
- Serilog.Log.Information("启动直连模式");
- }
- private void StartServerMode(Core.Models.SystemConfig config)
- {
- StartPlcServices();
- _hubServer = new HubServer { Port = config.HubPort, Token = config.HubToken };
- _hubServer.Start();
- _hubServer.CommandReceived += HandleHubCommandAsync;
- // 注册到连接状态集中管理
- ConnectionStatusService.Instance.RegisterHubServer(_hubServer);
- PlcPollingService.Instance.DataUpdated += OnDataUpdatedForBroadcast;
- AlarmService.Instance.AlarmRaised += OnAlarmRaisedForBroadcast;
- Serilog.Log.Information("启动服务端模式,端口: {Port}", config.HubPort);
- }
- // 存储为命名方法以便取消订阅
- private async void OnDataUpdatedForBroadcast(Core.Models.PlcDataModel data)
- {
- try
- {
- var server = _hubServer; // 捕获局部变量,防止并发 Dispose
- if (server != null)
- await server.BroadcastAsync(Core.Models.HubMessage.Data(data));
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "广播数据失败");
- }
- }
- private async void OnAlarmRaisedForBroadcast(Core.Models.AlarmRecord alarm)
- {
- try
- {
- var server = _hubServer; // 捕获局部变量,防止并发 Dispose
- if (server != null)
- await server.BroadcastAsync(Core.Models.HubMessage.Alarm(alarm));
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "广播报警失败");
- }
- }
- private void StartClientMode(Core.Models.SystemConfig config)
- {
- _hubClient = new HubClient { ServerUrl = config.HubServerUrl, Token = config.HubToken };
- // 注册到连接状态集中管理
- ConnectionStatusService.Instance.RegisterHubClient(_hubClient);
- // 客户端也启动本地数据日志,断连后仍有历史数据
- _dataLogger = new DataLoggingService(PlcPollingService.Instance);
- _dataLogger.Start();
- _hubClient.DataReceived += data =>
- {
- var localData = PlcPollingService.Instance.Data;
- CopyPlcData(data, localData);
- PlcPollingService.Instance.RaiseDataUpdated(localData);
- };
- _hubClient.AlarmReceived += alarm =>
- {
- _ = AlarmService.Instance.ProcessRemoteAlarmAsync(alarm);
- };
- Task.Run(async () =>
- {
- try
- {
- await _hubClient.ConnectAsync();
- }
- catch (Exception ex)
- {
- Serilog.Log.Error(ex, "连接中控服务端失败");
- }
- });
- Serilog.Log.Information("启动客户端模式,服务端: {Url}", config.HubServerUrl);
- }
- private async Task<Core.Models.CommandResultPayload> HandleHubCommandAsync(Core.Models.CommandPayload command)
- {
- try
- {
- switch (command.Action)
- {
- case "start_pump":
- case "stop_pump":
- if (!int.TryParse(command.Target?.Replace("P", ""), out var pumpIdx) || pumpIdx < 1 || pumpIdx > 5)
- {
- return new Core.Models.CommandResultPayload { Success = false, Message = "无效的泵编号(需 1-5)" };
- }
- var writeValue = command.Action == "start_pump";
- var addr = PlcConfig.GetPumpRunAddress(pumpIdx - 1);
- var ok = await PlcService.WriteBoolAsync(addr, writeValue);
- var verb = writeValue ? "启动" : "停止";
- AuditService.Log(command.Operator ?? "远程", "Control",
- $"远程{verb}泵{pumpIdx}", $"P{pumpIdx}", ok ? "Success" : "Failed");
- return new Core.Models.CommandResultPayload
- {
- Success = ok,
- Message = ok ? $"泵{pumpIdx}已{verb}" : "写入失败"
- };
- default:
- return new Core.Models.CommandResultPayload { Success = false, Message = $"未知命令: {command.Action}" };
- }
- }
- catch (Exception ex)
- {
- Serilog.Log.Error(ex, "执行命令异常: {Action}", command.Action);
- return new Core.Models.CommandResultPayload { Success = false, Message = ex.Message };
- }
- }
- /// <summary>
- /// 复制 PLC 数据到本地模型(JSON 序列化,自动覆盖所有属性)
- /// </summary>
- private static void CopyPlcData(Core.Models.PlcDataModel source, Core.Models.PlcDataModel target)
- {
- var json = System.Text.Json.JsonSerializer.Serialize(source);
- var copy = System.Text.Json.JsonSerializer.Deserialize<Core.Models.PlcDataModel>(json);
- if (copy == null) return;
- // 使用反射批量赋值,确保新增属性自动覆盖
- var props = typeof(Core.Models.PlcDataModel).GetProperties(
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
- .Where(p => p.CanRead && p.CanWrite);
- foreach (var prop in props)
- {
- prop.SetValue(target, prop.GetValue(copy));
- }
- }
- private void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop)
- {
- try
- {
- var mainWindow = new MainWindow();
- desktop.MainWindow = mainWindow;
- // 先取消旧的事件订阅(防止累积)
- if (_userLoggedOutHandler != null)
- {
- AuthService.UserLoggedOut -= _userLoggedOutHandler;
- }
- _userLoggedOutHandler = () =>
- {
- StopServices();
- ShowLoginWindow(desktop);
- mainWindow.Close();
- };
- AuthService.UserLoggedOut += _userLoggedOutHandler;
- mainWindow.Show();
- }
- catch (Exception ex)
- {
- LogCrash("ShowMainWindow", ex);
- desktop.Shutdown();
- }
- }
- private void StopServices()
- {
- Serilog.Log.Information("=== StopServices 开始 ===");
- var sw = System.Diagnostics.Stopwatch.StartNew();
- // 取消事件订阅(防止已释放的 hub 收到回调)
- PlcPollingService.Instance.DataUpdated -= OnDataUpdatedForBroadcast;
- AlarmService.Instance.AlarmRaised -= OnAlarmRaisedForBroadcast;
- Serilog.Log.Information("[{Elapsed}ms] 事件订阅已取消", sw.ElapsedMilliseconds);
- // 0. 停止备份服务
- try
- {
- BackupService.Stop();
- Serilog.Log.Information("[{Elapsed}ms] BackupService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] BackupService 停止失败", sw.ElapsedMilliseconds);
- }
- // 0.1 停止数据保留清理服务
- try
- {
- DataRetentionService.Stop();
- Serilog.Log.Information("[{Elapsed}ms] DataRetentionService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] DataRetentionService 停止失败", sw.ElapsedMilliseconds);
- }
- // 1. 先停止看门狗(防止它尝试重启其他正在停止的服务)
- try
- {
- WatchdogService.Instance.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] WatchdogService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] WatchdogService 停止失败", sw.ElapsedMilliseconds);
- }
- // 2. 先停止 PLC 轮询(停止 PLC 连接尝试,避免 Quartz Job 卡住)
- try
- {
- PlcPollingService.Instance.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] PlcPollingService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] PlcPollingService 停止失败", sw.ElapsedMilliseconds);
- }
- // 3. 停止报警服务
- try
- {
- AlarmService.Instance.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] AlarmService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] AlarmService 停止失败", sw.ElapsedMilliseconds);
- }
- // 4. 停止 WebSocket 通信
- try
- {
- _hubClient?.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] HubClient 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] HubClient 停止失败", sw.ElapsedMilliseconds);
- }
- _hubClient = null;
- // 取消 Hub 连接状态注册
- try
- {
- ConnectionStatusService.Instance.UnregisterHubs();
- Serilog.Log.Information("[{Elapsed}ms] ConnectionStatusService Hub 注册已取消", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] ConnectionStatusService 取消注册失败", sw.ElapsedMilliseconds);
- }
- try
- {
- _hubServer?.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] HubServer 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] HubServer 停止失败", sw.ElapsedMilliseconds);
- }
- _hubServer = null;
- // 5. 停止数据日志
- try
- {
- _dataLogger?.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] DataLoggingService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] DataLoggingService 停止失败", sw.ElapsedMilliseconds);
- }
- _dataLogger = null;
- // 6. 停止 Quartz 调度器(此时 PlcPollingJob 不会再有 PLC 连接操作)
- try
- {
- Serilog.Log.Information("[{Elapsed}ms] 正在停止 QuartzSchedulerService...", sw.ElapsedMilliseconds);
- QuartzSchedulerService.Instance.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] QuartzSchedulerService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] QuartzSchedulerService 停止失败", sw.ElapsedMilliseconds);
- }
- // 7. 停止其他服务
- try
- {
- AutoControlService.Instance.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] AutoControlService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] AutoControlService 停止失败", sw.ElapsedMilliseconds);
- }
- try
- {
- WorkflowService.Instance.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] WorkflowService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] WorkflowService 停止失败", sw.ElapsedMilliseconds);
- }
- try
- {
- DeviceStateMachineManager.Instance.UnsubscribeFromPlcData();
- DeviceStateMachineManager.Instance.Clear();
- Serilog.Log.Information("[{Elapsed}ms] DeviceStateMachineManager 已清理", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] DeviceStateMachineManager 清理失败", sw.ElapsedMilliseconds);
- }
- try
- {
- NotificationService.Instance.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] NotificationService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] NotificationService 停止失败", sw.ElapsedMilliseconds);
- }
- // 8. 重置单例(为登出/重登录准备)
- AutoControlService.ResetInstance();
- QuartzSchedulerService.ResetInstance();
- WorkflowService.ResetInstance();
- WatchdogService.ResetInstance();
- AlarmService.ResetInstance();
- PlcPollingService.ResetInstance();
- // 9. 停止 PLC 底层连接
- try
- {
- PlcService.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] PlcService 已停止", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] PlcService 停止失败", sw.ElapsedMilliseconds);
- }
- sw.Stop();
- Serilog.Log.Information("=== StopServices 完成 (耗时 {Elapsed}ms) ===", sw.ElapsedMilliseconds);
- }
- private void Cleanup()
- {
- Serilog.Log.Information("========== Cleanup 开始 ==========");
- var sw = System.Diagnostics.Stopwatch.StartNew();
- if (Interlocked.Exchange(ref _cleanupStarted, 1) != 0)
- {
- Serilog.Log.Information("Cleanup 已在进行中,跳过重复调用");
- return;
- }
- StopServices();
- Serilog.Log.Information("[{Elapsed}ms] StopServices 已完成", sw.ElapsedMilliseconds);
- if (_userLoggedOutHandler != null)
- {
- AuthService.UserLoggedOut -= _userLoggedOutHandler;
- _userLoggedOutHandler = null;
- }
- ThemeService.Instance.ThemeChanged -= ApplyTheme;
- try
- {
- SingleInstanceHelper.Release();
- Serilog.Log.Information("[{Elapsed}ms] SingleInstanceHelper 已释放", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] SingleInstanceHelper 释放失败", sw.ElapsedMilliseconds);
- }
- try
- {
- DatabaseService.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] DatabaseService 已释放", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] DatabaseService 释放失败", sw.ElapsedMilliseconds);
- }
- // 停止 AuditService 后台写入任务(flush 剩余数据)
- try
- {
- AuditService.Shutdown();
- Serilog.Log.Information("[{Elapsed}ms] AuditService 已释放", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] AuditService 释放失败", sw.ElapsedMilliseconds);
- }
- // 释放 LogBufferService
- try
- {
- LogBufferService.Instance.Dispose();
- Serilog.Log.Information("[{Elapsed}ms] LogBufferService 已释放", sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- Serilog.Log.Warning(ex, "[{Elapsed}ms] LogBufferService 释放失败", sw.ElapsedMilliseconds);
- }
- sw.Stop();
- Serilog.Log.Information("========== Cleanup 完成 (总耗时 {Elapsed}ms) ==========", sw.ElapsedMilliseconds);
- // 最后关闭日志(确保前面的日志都写入)
- LogService.Shutdown();
- }
- private void ApplyTheme()
- {
- var resources = Resources.MergedDictionaries;
- if (resources.Count == 0) return;
- var targetTheme = ThemeService.Instance.IsDarkTheme ? _darkTheme : _lightTheme;
- if (targetTheme == null) return;
- resources.Clear();
- resources.Add(targetTheme);
- ThemeHelper.SetTheme(ThemeService.Instance.IsDarkTheme);
- }
- }
|