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(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; } } /// /// 启动 PLC 相关服务(直连和服务端共用) /// 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, "启动看门狗服务失败"); } } /// /// 初始化新集成的服务(Stateless/Quartz/Elsa) /// 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 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 }; } } /// /// 复制 PLC 数据到本地模型(JSON 序列化,自动覆盖所有属性) /// 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(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); } }