| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- using System;
- using System.Diagnostics;
- using System.Threading;
- using Aivfo.OperationLog; // D1-10/D3-04:control 侧操作日志(审计埋点)
- using ivf_tl_Control; // AppData, StartMain
- using IvfTl.Control.Services; // Log4netHelper(程序集 ivf_tl_Control_Services;切勿写 operate 端 ivf_tl_Services)
- namespace IvfTl.ControlHost
- {
- public static class Program
- {
- private static Mutex _singleton;
- private static ControlHttpServer _http;
- private static volatile bool _started;
- private static ManualResetEventSlim _exitEvent;
- [STAThread]
- public static int Main(string[] args)
- {
- // 1) 单实例:已有 control 在跑则立即退出(永远只有一个驱动机器)。
- bool isNew;
- _singleton = new Mutex(true, @"Global\ivf_tl_control_singleton", out isNew);
- if (!isNew)
- {
- Log4netHelper.WriteLog("ControlHost: 已有实例在运行,本进程退出");
- return 0;
- }
- try
- {
- var hostArgs = HostArgs.Parse(args);
- Log4netHelper.WriteLog($"ControlHost 启动 port={hostArgs.Port} account={hostArgs.Account}");
- // D1-10/D3-04:先起操作日志组件(审计埋点),Project=control。紧接记一条"进程启动"生命周期审计
- // (即刻入库,与硬件埋点共同构成 control 真实物理动作的合规审计)。全 try 兜底,绝不影响启动。
- InitControlOperationLog();
- try
- {
- OperationLogger.Log("生命周期", "control 进程启动",
- input: new { port = hostArgs.Port, account = hostArgs.Account, pid = Process.GetCurrentProcess().Id },
- result: "成功");
- }
- catch { }
- // 2) 先起 HTTP(让 operate 能尽早探到"在启动中"),started=false。
- // 阶段2:/status rich + /serial/pause|resume + /shutdown。
- _http = new ControlHttpServer(
- hostArgs.Port,
- BuildStatus, // /ping 轻量
- BuildRichStatus, // /status 完整快照(§6 三块)
- HandleShutdown, // /shutdown 受护栏停机
- HandleSerialPause, // /serial/pause 借串口让路
- HandleSerialResume, // /serial/resume 归还恢复
- msg => Log4netHelper.WriteLog(msg));
- _http.Start();
- // 3) 账号守卫(对齐 operate 空账号跳过逻辑)。
- if (!hostArgs.IsValid)
- {
- Log4netHelper.WriteLog("ControlHost: 账号/密码为空,不启动采集(仅 HTTP 存活)");
- }
- else
- {
- // 4) 启动序放后台线程(复刻 operate MainWindow 的 Task.Run 形态):
- // StartRun 内部会阻塞(InitTL 串口握手 + StartAsync().Wait()),不能占住主线程,
- // 否则下面的 _exitEvent.Wait() 永不可达、阶段2 /shutdown 也无从优雅停。
- // 主线程只负责驻留;HTTP 在独立 Task 上,采集起没起都能被 operate 探活。
- System.Threading.Tasks.Task.Run(() => RunStartupSequence(hostArgs));
- }
- // 5) 驻留:主线程阻塞等退出信号(阶段2 的 /shutdown 会 Set 此事件)。
- _exitEvent = new ManualResetEventSlim(false);
- _exitEvent.Wait();
- return 0;
- }
- catch (Exception ex)
- {
- Log4netHelper.WriteLog("ControlHost 致命异常", ex);
- return 1;
- }
- finally
- {
- try { _http?.Stop(); } catch { }
- try { _singleton?.ReleaseMutex(); } catch { }
- }
- }
- /// <summary>
- /// control 启动序(后台线程跑,复刻 operate MainWindow 顺序,顺序不可变):
- /// Login → 设缓存盘 → HAL.ScanDevices → StartMain.StartRun。
- /// 任一步失败仅记日志降级,不退进程(HTTP 仍存活,operate 可探活到"未就绪")。
- /// </summary>
- private static void RunStartupSequence(HostArgs hostArgs)
- {
- try
- {
- if (!AppData.Instance.Login(hostArgs.Account, hostArgs.Password))
- {
- Log4netHelper.WriteLog("ControlHost: control 登录失败");
- return;
- }
- if (!string.IsNullOrEmpty(hostArgs.CacheDisk))
- {
- ivf_tl_UtilHelper.PathHelper.pan = hostArgs.CacheDisk;
- AppData.Instance.LogService.Pan = hostArgs.CacheDisk;
- }
- try
- {
- IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.Log =
- msg => Log4netHelper.WriteLog(msg);
- var devices = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.ScanDevices();
- Log4netHelper.WriteLog($"ControlHost: HAL 发现 {devices.Count} 个舱");
- }
- catch (Exception hex)
- {
- Log4netHelper.WriteLog("ControlHost: HAL 发现异常(降级):" + hex.Message);
- }
- var startMain = new StartMain();
- string err = startMain.StartRun(); // 阻塞:InitTL→InitHouse→StartAsync
- if (!string.IsNullOrEmpty(err))
- Log4netHelper.WriteLog("ControlHost: control 启动失败:" + err);
- else
- {
- _started = true;
- Log4netHelper.WriteLog("ControlHost: control 启动成功,常驻运行");
- // D1-10:采集启动成功生命周期审计(带 tlSn)。
- try
- {
- string tlSn = null;
- try { tlSn = AppData.Instance.TLSetting?.tlSn; } catch { }
- OperationLogger.Log("生命周期", "control 采集启动成功",
- output: new { tlSn, started = true }, result: "成功", tlSn: tlSn);
- }
- catch { }
- }
- }
- catch (Exception ex)
- {
- Log4netHelper.WriteLog("ControlHost: 启动序异常(降级,HTTP 仍存活)", ex);
- }
- }
- /// <summary>提供给 HTTP /status 的快照(阶段1:基础存活;阶段2 接 GetMonitorSnapshot 补全)。</summary>
- private static StatusDto BuildStatus()
- {
- string tlSn = "";
- try { tlSn = AppData.Instance.TLSetting?.tlSn ?? ""; } catch { }
- return new StatusDto
- {
- Ok = true,
- Pid = Process.GetCurrentProcess().Id,
- TlSn = tlSn,
- Started = _started
- };
- }
- /// <summary>/status 完整快照:基础存活 + control 现有 MonitorSnapshot(阶段2 §6 三块已在快照内补全)。</summary>
- private static object BuildRichStatus()
- {
- var head = BuildStatus();
- object snapshot = null;
- try { snapshot = AppData.Instance.GetMonitorSnapshot(); } catch { }
- return new
- {
- ok = head.Ok,
- pid = head.Pid,
- tlSn = head.TlSn,
- started = head.Started,
- snapshot
- };
- }
- /// <summary>/shutdown 受护栏停机:校验工程师口令(App.config engineerPwd,默认 tl13579),通过则安全停机。</summary>
- private static bool HandleShutdown(string token)
- {
- string engineerPwd = "tl13579";
- try
- {
- var cfg = System.Configuration.ConfigurationManager.AppSettings["engineerPwd"];
- if (!string.IsNullOrEmpty(cfg)) engineerPwd = cfg;
- }
- catch { }
- if (string.IsNullOrEmpty(token) || token != engineerPwd)
- {
- Log4netHelper.WriteLog("ControlHost: /shutdown 口令校验未通过,拒绝停机");
- return false;
- }
- Log4netHelper.WriteLog("ControlHost: /shutdown 口令通过,执行安全停机");
- // 异步触发,先把 HTTP 200 回给 operate,再停机退出。
- System.Threading.Tasks.Task.Run(() =>
- {
- try { System.Threading.Thread.Sleep(300); SafeShutdown(); }
- catch (Exception ex) { Log4netHelper.WriteLog("ControlHost: 停机异常", ex); _exitEvent?.Set(); }
- });
- return true;
- }
- /// <summary>统一安全停机:关相机/串口句柄(ShutdownAll)→ Set 退出事件(主线程 finally 停 HTTP+释放 Mutex)。</summary>
- private static void SafeShutdown()
- {
- // D1-10:停机生命周期审计 + flush 操作日志(确保停机前在途审计落库),全 try 兜底。
- try { OperationLogger.Log("生命周期", "control 安全停机", result: "成功"); } catch { }
- try { OperationLogger.Shutdown(); } catch { }
- try
- {
- IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.ShutdownAll();
- Log4netHelper.WriteLog("ControlHost: HardwareAccessLayer.ShutdownAll 完成");
- }
- catch (Exception ex) { Log4netHelper.WriteLog("ControlHost: ShutdownAll 异常", ex); }
- _exitEvent?.Set();
- }
- /// <summary>
- /// D1-10/D3-04:初始化 control 侧操作日志组件。Project=control,Kafka 取 App.config kfkaIP/kfkaPort,topic=tl-oplog
- /// (与 operate/front 同管道);ConfigFilePath=exe 同目录 oplog-config.json(≤15s 热加载,按模块开关)。
- /// 全 try 兜底:日志初始化失败绝不影响 control 驱动机器。
- /// </summary>
- private static void InitControlOperationLog()
- {
- try
- {
- string kafkaIp = System.Configuration.ConfigurationManager.AppSettings["kfkaIP"]?.ToString() ?? "127.0.0.1";
- string kafkaPort = System.Configuration.ConfigurationManager.AppSettings["kfkaPort"]?.ToString() ?? "9092";
- OperationLogger.Init(o =>
- {
- o.Project = "control";
- o.KafkaBootstrapServers = $"{kafkaIp}:{kafkaPort}";
- o.Topic = "tl-oplog";
- o.ConfigFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "oplog-config.json");
- });
- Log4netHelper.WriteLog($"[D1-10]control 操作日志组件已初始化 kafka={kafkaIp}:{kafkaPort} topic=tl-oplog project=control");
- }
- catch (Exception ex)
- {
- try { Log4netHelper.WriteLog($"[D1-10]control 操作日志组件初始化失败(已忽略):{ex.Message}"); } catch { }
- }
- }
- /// <summary>/serial/pause 借串口:置该舱 HouseGate 暂停标志,control 采集节拍据此让路(不驱动电机)。</summary>
- private static bool HandleSerialPause(int houseSn)
- {
- try
- {
- var gate = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.GetHouseGate(houseSn);
- if (gate == null) return false;
- gate.PauseCapture();
- Log4netHelper.WriteLog($"ControlHost: /serial/pause 舱{houseSn} 已让路(暂停采集)");
- return true;
- }
- catch (Exception ex) { Log4netHelper.WriteLog($"ControlHost: /serial/pause 舱{houseSn} 异常", ex); return false; }
- }
- /// <summary>/serial/resume 归还:清该舱让路标志,恢复采集。</summary>
- private static bool HandleSerialResume(int houseSn)
- {
- try
- {
- var gate = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.GetHouseGate(houseSn);
- if (gate == null) return false;
- gate.ResumeCapture();
- Log4netHelper.WriteLog($"ControlHost: /serial/resume 舱{houseSn} 已恢复采集");
- return true;
- }
- catch (Exception ex) { Log4netHelper.WriteLog($"ControlHost: /serial/resume 舱{houseSn} 异常", ex); return false; }
- }
- }
- }
|