using System; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; namespace ivf_tl_Operate.Debug { /// /// operate 端调试会话客户端:封 acquire/command/release/heartbeat 的本地 HTTP + 心跳定时器 + 会话失效回调。 /// 一次会话:AcquireAsync 成功起心跳,ReleaseAsync 停心跳。 /// 任一请求返回 code=SESSION_EXPIRED(control 端 TTL 已回收该会话)→ 触发 OnSessionExpired(UI 据此提示"请重新进入调试")并停心跳。 /// 心跳是必需的:control 端 10s TTL 看门狗收不到心跳会自动回收会话、把借用的舱还回去恢复采集。 /// public sealed class DebugSessionClient : IDisposable { private readonly string _baseUrl; private readonly HttpClient _http; private string _sessionId; private Timer _heartbeatTimer; private volatile bool _expiredFired; /// 心跳间隔(ms),默认 2.5s(远小于 control 端 10s TTL,容忍偶发卡顿)。 public int HeartbeatIntervalMs { get; set; } = 2500; /// 会话失效回调(收到 SESSION_EXPIRED 时触发一次)。 public Action OnSessionExpired { get; set; } public string SessionId => _sessionId; public DebugSessionClient(string baseUrl, HttpClient http = null) { _baseUrl = baseUrl.TrimEnd('/'); _http = http ?? new HttpClient(); } private async Task PostAsync(string path, object body) { var content = new StringContent(body == null ? "{}" : JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); var resp = await _http.PostAsync($"{_baseUrl}{path}", content); string s = await resp.Content.ReadAsStringAsync(); var r = JsonConvert.DeserializeObject(s) ?? new AcquireResult { Ok = false, Error = "空响应" }; if (r.Code == "SESSION_EXPIRED" && !_expiredFired) { _expiredFired = true; StopHeartbeat(); try { OnSessionExpired?.Invoke(); } catch { } } return r; } /// 借用某舱。成功则记录 sessionId 并起心跳。返回体含 cultivating/embryoCount(供��认框)。 public async Task AcquireAsync(int houseSn) { var r = await PostAsync("/debug/acquire", new { houseSn }); if (r.Ok && !string.IsNullOrEmpty(r.SessionId)) { _sessionId = r.SessionId; _expiredFired = false; StartHeartbeat(); } return r; } /// 在当前会话上执行一个操作(电机/读数/阀门等)。 public Task CommandAsync(string op, object args) => PostAsync("/debug/command", new { sessionId = _sessionId, op, args }); /// 归还会话(幂等):停心跳、清 sessionId、通知 control。多次调用不抛。 public async Task ReleaseAsync() { StopHeartbeat(); var sid = _sessionId; _sessionId = null; if (sid != null) { try { await PostAsync("/debug/release", new { sessionId = sid }); } catch { } } } private void StartHeartbeat() { StopHeartbeat(); _heartbeatTimer = new Timer(async _ => { var sid = _sessionId; if (sid == null) return; try { await PostAsync("/debug/heartbeat", new { sessionId = sid }); } catch { } }, null, HeartbeatIntervalMs, HeartbeatIntervalMs); } private void StopHeartbeat() { try { _heartbeatTimer?.Dispose(); } catch { } _heartbeatTimer = null; } public void Dispose() { StopHeartbeat(); } } }