using Ayay.SerilogLogs; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using SHH.CameraSdk; namespace SHH.CameraService; public class Program { private static bool _isExiting = false; /// /// 主程序 /// /// /// public static async Task Main(string[] args) { // 1. [核心环境] 必须在所有网络操作前开启 AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); // 2. 解析配置与初始化日志 var (config, opts, isDebugArgs) = Bootstrapper.ParseConfigAndInitLogger(args); var sysLog = Log.ForContext("SourceContext", LogModules.Core); // ============================================================= // 1. 启动日志 // ============================================================= sysLog.Warning($"[Core] 🚀 视频取流进程启动, 日志组件初始化完毕 => 进程: {opts.AppId}"); string argString = string.Join(" ", args); sysLog.Debug($"[Core] 🚀 启动参数({(isDebugArgs ? "调试环境" : "生产环境")}): {argString}"); AppDomain.CurrentDomain.ProcessExit += (s, e) => HandleExit("ProcessExit"); Console.CancelKeyPress += (s, e) => { e.Cancel = true; // 阻止立即强制杀死进程 HandleExit("CancelKeyPress"); }; // ============================================================= // 2. 硬件预热、端口扫描、gRpc链接 // ============================================================= Bootstrapper.WarmUpHardware(sysLog); // 端口自动扫描 (必须做,否则端口冲突) int activePort = Bootstrapper.ScanForAvailablePort(config, sysLog); if (activePort == -1) { sysLog.Fatal("[Core] 💀 无法启动:配置范围内无可用端口"); Bootstrapper.Shutdown("无法启动:配置范围内无可用端口", exitCode: 1); return; } config.UpdateActualPort(activePort); // 回填端口 // 具体的 gRpc 链接逻辑封装在 Bootstrapper 中,保持 Main 清爽但逻辑可见 await Bootstrapper.RegisterToGatewayAsync(config); // ============================================================= // 3. 构建 Web 主机环境 // ============================================================= var builder = WebApplication.CreateBuilder(args); // 👇👇👇 核心修复开始 👇👇👇 // ★ 1. 接管日志系统:告诉 Host 使用我们刚才配置好的 Serilog // dispose: true 表示程序结束时自动刷新日志 builder.Host.UseSerilog(dispose: true); // ★ 2. 斩草除根:清除 .NET 默认注入的 Console/Debug 日志提供程序 // 这一步是解决 "info: Microsoft.Hosting.Lifetime..." 重复输出的关键 builder.Logging.ClearProviders(); // ★ 3. (可选) 彻底静音:禁止 Kestrel 打印 "Now listening on..." 这种启动横幅 // 如果你只想看你自己的 "[WebApi] 🚀 服务启动...",就把这行加上 builder.WebHost.SuppressStatusMessages(true); // ★ 核心改动:一行代码注册所有业务 (SDK, Workers, gRpc, 视频流) builder.Services.AddCameraBusinessServices(config, sysLog); // ★ 核心改动:注册 Web 基础 (Controller, Swagger, Cors) builder.Services.AddWebSupport(config); // ============================================================= // 6. 启动服务 // ============================================================= var app = builder.Build(); // 激活 SDK 管理器并启动业务点火 await StartBusinessLogic(app, sysLog); // ★ 核心改动:配置 HTTP 管道 (Swagger, MapControllers 等) app.ConfigurePipeline(config); // 启动监听 string url = $"http://0.0.0.0:{config.BasePort}"; sysLog.Information($"[WebApi] 🚀 服务启动,监听: {url}"); await app.RunAsync(url); } /// /// 激活单例并启动相机管理器 /// /// /// static async Task StartBusinessLogic(WebApplication app, Serilog.ILogger logger) { var manager = app.Services.GetRequiredService(); // 激活哨兵 _ = app.Services.GetRequiredService(); await manager.StartAsync(); // 2. Optimized: 主动拉起所有已加载设备的物理连接 // 理由:当本地配置了 video 推送目标时,不再等待远端 command 下发启动指令 var allDevices = manager.GetAllDevices(); foreach (var device in allDevices) { if (device.IsRunning && !device.IsActived) { logger.Information($"[Core] 🚀 自动激活设备流: ID:{device.Id} IP:{device.Config.IpAddress}"); // 使用 Fire-and-forget 启动,避免阻塞主线程 _ = device.StartAsync(); } } var sysLog = Log.ForContext("SourceContext", LogModules.Core); sysLog.Information($"[Core] 🚀 核心业务逻辑已激活, 设备管理器已就绪."); } /// /// 退出, 刷新日志 /// /// private static void HandleExit(string source) { if (_isExiting) return; _isExiting = true; Log.ForContext("SourceContext", LogModules.Core) .Warning("// Modified: 处理手动关闭请求。来源: {Source}", source); // TODO: 执行 SHH.CameraService 的清理逻辑 (释放海康/大华 SDK 句柄) Log.ForContext("SourceContext", LogModules.Core) .Warning("SHH.CameraService 已安全关闭,日志已刷新。"); // 必须显式调用,否则在 ProcessExit 触发时异步日志可能丢失 Log.CloseAndFlush(); Environment.Exit(0); } } /* 🚀 启动/发布 程序启动、服务预热、开始监听 🏁 结束/终点 批量任务全部完成、程序正常退出 🔄 重试/循环 正在重试连接、定时任务触发、同步数据中 ⏳ 等待/耗时 长耗时操作开始、排队中 💤 休眠/闲置 线程挂起、服务进入待机模式 🌐 网络/HTTP HTTP 请求、API 调用、Web 服务 🔌 连接 数据库连接建立、Socket 连接 📡 信号/广播 发送 MQ 消息、广播通知、取流 💾 存储/磁盘 写入文件、数据库落盘、缓存读写 🔒 安全/锁 加密、解密、登录成功、获取分布式锁 ⚙️ 配置/系统 加载配置、系统底层操作 🐞 Bug/调试 捕捉到的异常、临时调试信息 🧪 测试/实验 单元测试环境、灰度测试代码 🔍 搜索/检查 查询数据库、检查文件是否存在 💡 提示/发现 逻辑分支提示、参数值打印 🔴 红:致命/严重 (Fatal/Error) 🟡 黄:警告 (Warning) 🟢 绿:正常/成功 (Info/Success) 🔵 蓝:数据/网络 (Data/Network) ⚪ 灰:细节/忽略 (Debug/Verbose) ✅ Check Mark Button \u{2705} ✅ 🆗 OK Button \u{1F197} 🆗 🔚 END Arrow (逻辑结束) \u{1F51A} 🔚 💯 Hundred Points (完美结束) \u{1F4AF} 💯 🛑 Stop Sign (最强提示) \u{1F6D1} 🛑 ⛔ No Entry (禁止/中断) \u{26D4} ⛔ 🚫 Prohibited (非法操作终止) \u{1F6AB} 🚫 ⏹️ Stop Button (播放器风格) \u{23F9} ⏹ ❌ Cross Mark (任务失败结束) \u{274C} ❌ 💀 Skull (进程被 Kill) \u{1F480} 💀 ⚰️ Coffin (彻底销毁) \u{26B0} ⚰ 👻 Ghost (变成孤儿进程) \u{1F47B} 👻 */