2026-01-09 12:30:36 +08:00
|
|
|
|
using MessagePack;
|
2026-01-07 10:59:03 +08:00
|
|
|
|
using SHH.Contracts;
|
2026-01-05 14:54:06 +08:00
|
|
|
|
using SHH.ProcessLaunchers;
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
2025-12-30 10:53:02 +08:00
|
|
|
|
using System.Windows;
|
|
|
|
|
|
|
|
|
|
|
|
namespace SHH.CameraDashboard
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Interaction logic for App.xaml
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public partial class App : Application
|
|
|
|
|
|
{
|
2026-01-05 14:54:06 +08:00
|
|
|
|
#region 定义全局单例
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 进程管理器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static ProcessManager ProcManager { get; private set; }
|
|
|
|
|
|
= new ProcessManager(new ProcessDashboardLogger());
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-01-12 18:27:58 +08:00
|
|
|
|
#region OnExit
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 退出时执行
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
|
|
|
|
|
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
|
2026-01-05 14:54:06 +08:00
|
|
|
|
|
2026-01-01 22:40:32 +08:00
|
|
|
|
protected override async void OnStartup(StartupEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
base.OnStartup(e);
|
|
|
|
|
|
|
2026-01-05 14:54:06 +08:00
|
|
|
|
// 启动视频流接收 (Port 6002)
|
|
|
|
|
|
StreamReceiverService.Instance.Start(6002);
|
|
|
|
|
|
|
|
|
|
|
|
// 启动指令服务 (Port 6001)
|
|
|
|
|
|
|
2026-01-09 12:30:36 +08:00
|
|
|
|
CommandBusClient.Instance.Start(6001);
|
|
|
|
|
|
CommandBusClient.Instance.OnServerRegistered += SetupAutomaticConfiguration;
|
|
|
|
|
|
|
|
|
|
|
|
//CommandServer.Instance.Start(6001);
|
|
|
|
|
|
|
|
|
|
|
|
//CommandServer.Instance.OnClientRegistered += SetupAutomaticConfiguration;
|
2026-01-05 14:54:06 +08:00
|
|
|
|
|
|
|
|
|
|
// 现在我们来配置启动
|
|
|
|
|
|
|
2026-01-01 22:40:32 +08:00
|
|
|
|
// 1. 【核心代码】程序启动时,异步读取配置文件
|
|
|
|
|
|
var savedNodes = await LocalStorageService.LoadAsync<ObservableCollection<ServiceNodeModel>>(AppPaths.ServiceNodesConfig);
|
|
|
|
|
|
if (savedNodes != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var node in savedNodes)
|
|
|
|
|
|
AppGlobal.ServiceNodes.Add(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-05 14:54:06 +08:00
|
|
|
|
// =========================================================
|
|
|
|
|
|
// 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\" " +
|
2026-01-09 12:30:36 +08:00
|
|
|
|
$"--uris \"127.0.0.1,6003,video,调试PC;\" " +
|
2026-01-07 10:59:03 +08:00
|
|
|
|
$"--uris \"127.0.0.1,6001,command,调试PC;\" " +
|
2026-01-09 12:30:36 +08:00
|
|
|
|
$"--uris \"127.0.0.1,6004,command,调试PC;\" " +
|
|
|
|
|
|
$"--uris \"127.0.0.1,6002,video,大屏展示;\" " +
|
2026-01-05 14:54:06 +08:00
|
|
|
|
$"--mode 1 " +
|
|
|
|
|
|
$"--ports \"5000,100\"";
|
|
|
|
|
|
|
|
|
|
|
|
// C. 注册进程配置 (复用 ProcManager)
|
|
|
|
|
|
ProcManager.Register(new ProcessConfig
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = "CameraService", // 内部标识
|
|
|
|
|
|
DisplayName = "视频接入服务", // UI显示名称
|
|
|
|
|
|
// 请确保路径正确,建议用相对路径 AppDomain.CurrentDomain.BaseDirectory + "SHH.CameraService.exe"
|
2026-01-09 12:30:36 +08:00
|
|
|
|
ExePath = @"E:\Codes2026\Ayay\SHH.CameraService\bin\Debug\net8.0\SHH.CameraService.exe",
|
2026-01-05 14:54:06 +08:00
|
|
|
|
Arguments = serviceArgs, // ★★★ 核心:注入参数 ★★★
|
|
|
|
|
|
StartupOrder = 1, // 优先级
|
|
|
|
|
|
RestartDelayMs = 2000, // 崩溃后2秒重启
|
|
|
|
|
|
Visible = false // 不显示黑框
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// =========================================================
|
|
|
|
|
|
// 4. 发射!启动所有注册的进程
|
|
|
|
|
|
// =========================================================
|
|
|
|
|
|
_ = ProcManager.StartAllAsync();
|
|
|
|
|
|
|
2026-01-01 22:40:32 +08:00
|
|
|
|
// 3. 启动主窗口
|
|
|
|
|
|
// 注意:如果 LoadAsync 耗时较长,这里可能会导致启动画面停留,
|
|
|
|
|
|
// 实际项目中可以搞一个 Splash Screen (启动屏) 来做这件事。
|
|
|
|
|
|
var mainWin = new MainWindow();
|
|
|
|
|
|
mainWin.Show();
|
|
|
|
|
|
}
|
2025-12-30 10:53:02 +08:00
|
|
|
|
|
2026-01-09 12:30:36 +08:00
|
|
|
|
private void SetupAutomaticConfiguration(RegisterPayload client)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"[自动化] 新服务上线: {client.InstanceId}");
|
|
|
|
|
|
|
2026-01-12 18:27:58 +08:00
|
|
|
|
//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<CameraConfigSubscribeDto>
|
|
|
|
|
|
// {
|
|
|
|
|
|
// // 第一条:显示帧,要求 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);
|
|
|
|
|
|
//});
|
2026-01-07 10:59:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-01 22:40:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 全局统一退出入口
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static void ShutdownApp()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 这里可以处理统一的资源清理逻辑 (如停止摄像头推流、关闭数据库连接)
|
|
|
|
|
|
// 2. 保存用户配置
|
|
|
|
|
|
// 3. 彻底退出
|
2026-01-05 14:54:06 +08:00
|
|
|
|
StreamReceiverService.Instance.Dispose();
|
|
|
|
|
|
CommandServer.Instance.Dispose();
|
|
|
|
|
|
// 停止所有子进程
|
|
|
|
|
|
ProcManager.StopAll();
|
|
|
|
|
|
|
2026-01-01 22:40:32 +08:00
|
|
|
|
Current.Shutdown();
|
|
|
|
|
|
}
|
2026-01-05 14:54:06 +08:00
|
|
|
|
|
2026-01-01 22:40:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|