using MessagePack; using SHH.Contracts; using SHH.ProcessLaunchers; using System.Collections.ObjectModel; using System.Windows; namespace SHH.CameraDashboard { /// /// Interaction logic for App.xaml /// public partial class App : Application { #region 定义全局单例 /// /// 进程管理器 /// public static ProcessManager ProcManager { get; private set; } = new ProcessManager(new ProcessDashboardLogger()); #endregion #region OnExit /// /// 退出时执行 /// /// protected override void OnExit(ExitEventArgs e) { // 1. 显式停止通讯总线 CommandBusClient.Instance.Stop(); // 2. 如果你有其他的单例服务需要清理(比如视频解码库),也放在这里 base.OnExit(e); // 3. 终极保底:如果程序在清理逻辑执行后 3 秒还没消失,强制杀掉进程 // 防止某些第三方 DLL(如海康 SDK)的线程卡死 Task.Delay(3000).ContinueWith(_ => { System.Diagnostics.Process.GetCurrentProcess().Kill(); }); } #endregion protected override async void OnStartup(StartupEventArgs e) { base.OnStartup(e); // 启动视频流接收 (Port 6002) StreamReceiverService.Instance.Start(6002); // 启动指令服务 (Port 6001) CommandBusClient.Instance.Start(6001); CommandBusClient.Instance.OnServerRegistered += SetupAutomaticConfiguration; //CommandServer.Instance.Start(6001); //CommandServer.Instance.OnClientRegistered += SetupAutomaticConfiguration; // 现在我们来配置启动 // 1. 【核心代码】程序启动时,异步读取配置文件 var savedNodes = await LocalStorageService.LoadAsync>(AppPaths.ServiceNodesConfig); if (savedNodes != null) { foreach (var node in savedNodes) AppGlobal.ServiceNodes.Add(node); } // ========================================================= // 3. 构建启动参数 & 注册进程 // ========================================================= // A. 获取当前 Dashboard 的 PID (用于父进程守护) int myPid = System.Environment.ProcessId; // B. 构建参数字符串 // --pid: 让 Service 知道谁是父进程 // --uris: 告诉 Service 反向连接的目标 (注意顺序:视频端口, 指令端口) // --mode: 1 (主动连接模式) // --ports: Service 自身的 WebAPI 端口 (5005) string serviceArgs = $"" + $"--pid {myPid} " + $"--appid \"CameraApp_01\" " + $"--uris \"127.0.0.1,6003,video,调试PC;\" " + $"--uris \"127.0.0.1,6001,command,调试PC;\" " + $"--uris \"127.0.0.1,6004,command,调试PC;\" " + $"--uris \"127.0.0.1,6002,video,大屏展示;\" " + $"--mode 1 " + $"--ports \"5000,100\""; // C. 注册进程配置 (复用 ProcManager) ProcManager.Register(new ProcessConfig { Id = "CameraService", // 内部标识 DisplayName = "视频接入服务", // UI显示名称 // 请确保路径正确,建议用相对路径 AppDomain.CurrentDomain.BaseDirectory + "SHH.CameraService.exe" ExePath = @"E:\Codes2026\Ayay\SHH.CameraService\bin\Debug\net8.0\SHH.CameraService.exe", Arguments = serviceArgs, // ★★★ 核心:注入参数 ★★★ StartupOrder = 1, // 优先级 RestartDelayMs = 2000, // 崩溃后2秒重启 Visible = false // 不显示黑框 }); // ========================================================= // 4. 发射!启动所有注册的进程 // ========================================================= _ = ProcManager.StartAllAsync(); // 3. 启动主窗口 // 注意:如果 LoadAsync 耗时较长,这里可能会导致启动画面停留, // 实际项目中可以搞一个 Splash Screen (启动屏) 来做这件事。 var mainWin = new MainWindow(); mainWin.Show(); } private void SetupAutomaticConfiguration(RegisterPayload client) { Console.WriteLine($"[自动化] 新服务上线: {client.InstanceId}"); //Task.Run(async () => //{ // await Task.Delay(500); // // 1. 构建业务配置对象 // var cameraConfig = new CameraConfigDto // { // Id = 17798, // Name = "206摄像头", // Location = "404办公室", // IpAddress = "172.16.41.88", // Username = "admin", // Password = "abcd1234", // Port = 8000, // ChannelIndex = 1, // StreamType = 0, // Brand = DeviceBrand.HikVision.GetHashCode(), // 对应 DeviceBrand 枚举 // RenderHandle = 0, // 初始化为0 // MainboardIp = "", // 留空 // MainboardPort = 0, // RtspPath = "" // }; // // ★ 新增:一并带上订阅要求 ★ // cameraConfig.AutoSubscriptions = new List // { // // 第一条:显示帧,要求 8 帧 // new CameraConfigSubscribeDto { // AppId = "UI_Display", // Type = 0, // TargetFps = 8, // Memo = "显示帧" // }, // // 第二条:分析帧,要求 1 帧 // new CameraConfigSubscribeDto { // AppId = "AI_Analysis", // Type = 0, // Memo = "分析帧", // TargetFps = 1 // } // }; // // 2. 构造指令包 // var command = new CommandPayload // { // Protocol = ProtocolHeaders.Command, // CmdCode = ProtocolHeaders.SyncCamera, // TargetId = client.InstanceId, // RequestId = Guid.NewGuid().ToString("N"), // // ★ 修正 1: 使用 JsonParams 属性名,并将对象序列化为 JSON 字符串 ★ // // 因为你的 DTO 定义 JsonParams 是 string 类型 // JsonParams = JsonHelper.Serialize(cameraConfig), // // ★ 修正 2: Timestamp 直接赋值 DateTime 对象 ★ // // 因为你的 DTO 定义 Timestamp 是 DateTime 类型 // Timestamp = DateTime.Now, // RequireAck = true // }; // // 3. 发送 // await CommandBusClient.Instance.SendInternalAsync(client.InstanceId, command); //}); } /// /// 全局统一退出入口 /// public static void ShutdownApp() { // 1. 这里可以处理统一的资源清理逻辑 (如停止摄像头推流、关闭数据库连接) // 2. 保存用户配置 // 3. 彻底退出 StreamReceiverService.Instance.Dispose(); CommandServer.Instance.Dispose(); // 停止所有子进程 ProcManager.StopAll(); Current.Shutdown(); } } }