using Ayay.SerilogLogs; using CoreWCF; using CoreWCF.Configuration; using CoreWCF.Description; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Player.MJPEG; using Serilog; using System.Diagnostics; using System.Net; namespace SHH.MjpegPlayer { public static class Bootstrapper { private static readonly ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core); #region LoadConfig /// /// 加载配置文件 /// /// public static MjpegConfig LoadConfig() { try { // [修复] 路径处理脆弱性:使用 BaseDirectory 拼接,避免相对路径替换的风险 // 生产环境:强制使用绝对路径确保能找到配置文件 if (!Debugger.IsAttached) { if (!string.IsNullOrEmpty(JsonConfigUris.MjpegConfig)) JsonConfigUris.MjpegConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileName(JsonConfigUris.MjpegConfig)); } // 加载配置文件 MjpegConfig? cfg = null; if (!string.IsNullOrEmpty(JsonConfigUris.MjpegConfig)) { cfg = JsonConfig.Load(JsonConfigUris.MjpegConfig); if (cfg == null) { cfg = new MjpegConfig(); JsonConfig.Save(cfg, JsonConfigUris.MjpegConfig, "MjpegServer配置项"); _sysLog.Warning("未找到配置文件,已生成默认配置: {Path}", JsonConfigUris.MjpegConfig); } MjpegStatics.Cfg = cfg; } if (cfg == null) cfg = new MjpegConfig(); return cfg; } catch(Exception ex) { _sysLog.Error("加载配置文件失败."); Console.ReadLine(); return new MjpegConfig(); } } #endregion #region ValidateEnvironment /// /// 检查 IP 与端口 /// public static void ValidateEnvironment() { var cfg = MjpegStatics.Cfg; // IP 地址检查 IPAddress? ipAddress; if (!IPAddress.TryParse(cfg.SvrMjpegIp, out ipAddress)) { ipAddress = IPAddress.Any; _sysLog.Warning("配置的 IP 地址非法,将使用 IPAddress. Any: {Ip}.", cfg.SvrMjpegIp); } // 端口检查 => Wcf 接收图片接口 var portsToCheck = new List { cfg.WcfPushImagePort, cfg.SvrMjpegPortBegin, cfg.SvrMjpegPortEnd }; foreach (var port in portsToCheck) { if (!port.IsServerPort()) { _sysLog.Error("端口配置无效, Port: {Port}.", port); ExitApp($"端口配置无效, Port: {port}"); } } // 端口检查 => Mjpeg 服务端口 if (!cfg.SvrMjpegPortBegin.IsServerPort()) { _sysLog.Fatal("WCF 接收端口被占用, Port:{Port}.", cfg.WcfPushImagePort); // 退出应用 ExitApp("端口占用."); } // [修复] 循环逻辑错误:将 < 改为 <=,确保最后一个端口也被检测 for (var i = cfg.SvrMjpegPortBegin; i <= cfg.SvrMjpegPortEnd; i++) { if (!i.PortOccupiedProc()) { // 退出应用 _sysLog.Fatal("MJPEG 监听端口被占用, Port:{Port}", i); ExitApp($"MJPEG 监听端口被占用, Port:{i}"); } } } #endregion #region StartWcfEngine /// /// 内部 WCF 引擎初始化 (CoreWCF) /// public static void StartWcfEngine(MjpegConfig cfg) { // Optimized: 内存监控提升 MemoryWatchdog.Start(300, 2048); var builder = WebApplication.CreateBuilder(); builder.WebHost.UseUrls($"http://*:{cfg.WcfPushImagePort}"); // 托管日志 builder.Host.UseSerilog(_sysLog); builder.Services.AddServiceModelServices(); builder.Services.AddServiceModelMetadata(); builder.Services.AddSingleton() .AddSingleton(); var app = builder.Build(); var wsBinding = new WSHttpBinding(SecurityMode.None); wsBinding.MaxReceivedMessageSize = cfg.SvrPushImageMaxRecMsgSize; // Modified: [原因] 强制转换 IApplicationBuilder 修复 UseServiceModel 的二义性 ((IApplicationBuilder)app).UseServiceModel(serviceBuilder => { serviceBuilder.AddService(opt => { opt.BaseAddresses.Add(new Uri($"http://0.0.0.0:{cfg.WcfPushImagePort}")); }) .AddServiceEndpoint( wsBinding, $"/{cfg.SvrNamePushImage}", new Uri($"http://0.0.0.0:{cfg.WcfPushImagePort}/{cfg.SvrNamePushImage}") ); }); // 关闭元数据暴露增强安全性 var meta = app.Services.GetRequiredService(); meta.HttpGetEnabled = false; meta.HttpsGetEnabled = false; Task.Run(() => app.Run()); } #endregion #region ExitApp /// /// 应用程序退出 /// /// /// public static void ExitApp(string exitMsg, int waitSeconds = 5) { // [修复] 尝试停止所有 MjpegServer 监听 try { MjpegServer.StopAll(); } catch { } var iSleep = waitSeconds * 2; for (var i = 0; i < iSleep; i++) { Thread.Sleep(500); } // 退出程序 Environment.Exit(0); } #endregion } }