DebugSessionClient.cs 4.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. using System;
  2. using System.Net.Http;
  3. using System.Text;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Newtonsoft.Json;
  7. namespace ivf_tl_Operate.Debug
  8. {
  9. /// <summary>
  10. /// operate 端调试会话客户端:封 acquire/command/release/heartbeat 的本地 HTTP + 心跳定时器 + 会话失效回调。
  11. /// 一次会话:AcquireAsync 成功起心跳,ReleaseAsync 停心跳。
  12. /// 任一请求返回 code=SESSION_EXPIRED(control 端 TTL 已回收该会话)→ 触发 OnSessionExpired(UI 据此提示"请重新进入调试")并停心跳。
  13. /// 心跳是必需的:control 端 10s TTL 看门狗收不到心跳会自动回收会话、把借用的舱还回去恢复采集。
  14. /// </summary>
  15. public sealed class DebugSessionClient : IDisposable
  16. {
  17. private readonly string _baseUrl;
  18. private readonly HttpClient _http;
  19. private string _sessionId;
  20. private Timer _heartbeatTimer;
  21. private volatile bool _expiredFired;
  22. /// <summary>心跳间隔(ms),默认 2.5s(远小于 control 端 10s TTL,容忍偶发卡顿)。</summary>
  23. public int HeartbeatIntervalMs { get; set; } = 2500;
  24. /// <summary>会话失效回调(收到 SESSION_EXPIRED 时触发一次)。</summary>
  25. public Action OnSessionExpired { get; set; }
  26. public string SessionId => _sessionId;
  27. public DebugSessionClient(string baseUrl, HttpClient http = null)
  28. {
  29. _baseUrl = baseUrl.TrimEnd('/');
  30. _http = http ?? new HttpClient();
  31. }
  32. private async Task<AcquireResult> PostAsync(string path, object body)
  33. {
  34. var content = new StringContent(body == null ? "{}" : JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
  35. var resp = await _http.PostAsync($"{_baseUrl}{path}", content);
  36. string s = await resp.Content.ReadAsStringAsync();
  37. var r = JsonConvert.DeserializeObject<AcquireResult>(s) ?? new AcquireResult { Ok = false, Error = "空响应" };
  38. if (r.Code == "SESSION_EXPIRED" && !_expiredFired)
  39. {
  40. _expiredFired = true;
  41. StopHeartbeat();
  42. try { OnSessionExpired?.Invoke(); } catch { }
  43. }
  44. return r;
  45. }
  46. /// <summary>借用某舱。成功则记录 sessionId 并起心跳。返回体含 cultivating/embryoCount(供��认框)。</summary>
  47. public async Task<AcquireResult> AcquireAsync(int houseSn)
  48. {
  49. var r = await PostAsync("/debug/acquire", new { houseSn });
  50. if (r.Ok && !string.IsNullOrEmpty(r.SessionId))
  51. {
  52. _sessionId = r.SessionId;
  53. _expiredFired = false;
  54. StartHeartbeat();
  55. }
  56. return r;
  57. }
  58. /// <summary>在当前会话上执行一个操作(电机/读数/阀门等)。</summary>
  59. public Task<AcquireResult> CommandAsync(string op, object args)
  60. => PostAsync("/debug/command", new { sessionId = _sessionId, op, args });
  61. /// <summary>归还会话(幂等):停心跳、清 sessionId、通知 control。多次调用不抛。</summary>
  62. public async Task ReleaseAsync()
  63. {
  64. StopHeartbeat();
  65. var sid = _sessionId;
  66. _sessionId = null;
  67. if (sid != null) { try { await PostAsync("/debug/release", new { sessionId = sid }); } catch { } }
  68. }
  69. private void StartHeartbeat()
  70. {
  71. StopHeartbeat();
  72. _heartbeatTimer = new Timer(async _ =>
  73. {
  74. var sid = _sessionId;
  75. if (sid == null) return;
  76. try { await PostAsync("/debug/heartbeat", new { sessionId = sid }); } catch { }
  77. }, null, HeartbeatIntervalMs, HeartbeatIntervalMs);
  78. }
  79. private void StopHeartbeat()
  80. {
  81. try { _heartbeatTimer?.Dispose(); } catch { }
  82. _heartbeatTimer = null;
  83. }
  84. public void Dispose() { StopHeartbeat(); }
  85. }
  86. }