SignalRScreenClientHub.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. 
  2. using Azure.Storage.Blobs;
  3. using Azure.Storage.Blobs.Models;
  4. using Microsoft.AspNetCore.Hosting.Server;
  5. using Microsoft.AspNetCore.Hosting.Server.Features;
  6. using Microsoft.AspNetCore.SignalR.Client;
  7. using Microsoft.Extensions.Configuration;
  8. using System.Diagnostics;
  9. using System.Management;
  10. using System.Net;
  11. using System.Net.Http.Headers;
  12. using System.Net.NetworkInformation;
  13. using System.Runtime.InteropServices;
  14. using System.Text;
  15. using System.Text.Json;
  16. using System.Text.Json.Nodes;
  17. using System.Text.RegularExpressions;
  18. using System.Threading.Tasks;
  19. using System.Web;
  20. using TEAMModelOS.SDK;
  21. using TEAMModelOS.SDK.DI.Device;
  22. using TEAMModelOS.SDK.Extension;
  23. namespace HTEX.ScreenClient.Services
  24. {
  25. public class SignalRScreenClientHub : BackgroundService, IDisposable
  26. {
  27. private readonly IConfiguration _configuration;
  28. private readonly ILogger<SignalRScreenClientHub> _logger;
  29. private TEAMModelOS.SDK.ScreenClient? device;
  30. private readonly CoreDevice _device;
  31. private readonly IHttpClientFactory _httpClientFactory;
  32. public SignalRScreenClientHub(IConfiguration configuration,ILogger<SignalRScreenClientHub> logger,IHttpClientFactory httpClientFactory, CoreDevice device)
  33. {
  34. _configuration=configuration;
  35. _logger=logger;
  36. _httpClientFactory=httpClientFactory;
  37. _device = device;
  38. }
  39. protected async override Task ExecuteAsync(CancellationToken stoppingToken)
  40. {
  41. var coreDevice = await _device.GetCoreDevice();
  42. device=coreDevice.ToJsonString().ToObject<TEAMModelOS.SDK.ScreenClient>();
  43. string clientid = device.deviceId!;
  44. string? CenterUrl = _configuration.GetSection("ScreenClient:CenterUrl").Value;
  45. string? ScreenUrl = _configuration.GetSection("ScreenClient:ScreenUrl").Value;
  46. long Timeout = _configuration.GetValue<long>("ScreenClient:Timeout");
  47. long Delay = _configuration.GetValue<long>("ScreenClient:Delay");
  48. device.timeout = Timeout;
  49. device.delay = Delay;
  50. device.screenUrl = ScreenUrl;
  51. await StartHubConnectionAsync( clientid, CenterUrl);
  52. }
  53. private async Task StartHubConnectionAsync(string clientid, string? CenterUrl)
  54. {
  55. //重写重连策略,防止服务端更新,断线后,客户端达到最大连接次数,依然连线不上服务端。
  56. var reconnectPolicy = new ExponentialBackoffReconnectPolicy(TimeSpan.FromSeconds(10), _logger); // 尝试重连的最大次数,这里使用 int.MaxValue 表示无限次
  57. reconnectPolicy.MaxRetryCount = int.MaxValue;
  58. HubConnection hubConnection = new HubConnectionBuilder()
  59. .WithUrl($"{CenterUrl}/signalr/screen?grant_type=bookjs_api&clientid={clientid}&device={HttpUtility.UrlEncode(device.ToJsonString(), Encoding.Unicode)}") //only one slash
  60. .WithAutomaticReconnect(reconnectPolicy)
  61. .ConfigureLogging(logging =>
  62. {
  63. logging.SetMinimumLevel(LogLevel.Information);
  64. logging.AddConsole();
  65. })
  66. .Build();
  67. try {
  68. hubConnection.On<ConnectionMessage>("ReceiveConnection", (message) =>
  69. {
  70. _logger.LogInformation($"连接成功:{message.ToJsonString()}");
  71. //重置重连次数。
  72. reconnectPolicy.Reset();
  73. });
  74. hubConnection.On<ScreenProcessMessage>("ReceiveMessage", async (message) =>
  75. {
  76. if (message.message_type.Equals(MessageType.task_send_success))
  77. {
  78. var data = await ReceiveMessage(message);
  79. _logger.LogInformation($"任务执行完成,执行状态{data.status},消息:{data.msg},任务信息:{data.task?.ToJsonString()}");
  80. message.content=data.task?.ToJsonString();
  81. message.result=data.status;
  82. message.msg=data.msg;
  83. if (data.status==2)
  84. {
  85. message.message_type= MessageType.task_execute_success;
  86. }
  87. else
  88. {
  89. message.message_type= MessageType.task_execute_error;
  90. }
  91. await hubConnection.InvokeAsync<ScreenProcessMessage>("ReceiveMessage", message);
  92. }
  93. else
  94. {
  95. _logger.LogInformation($"任务领取失败,{message.ToJsonString()}");
  96. }
  97. });
  98. await hubConnection.StartAsync();
  99. }
  100. catch (Exception ex)
  101. {
  102. _logger.LogError("初次启动连接SignalR失败,等待重连......");
  103. int retryCount = 0;
  104. const int maxRetries = 360;
  105. const int retryDelaySeconds = 10;
  106. while (retryCount < maxRetries)
  107. {
  108. try
  109. {
  110. await Task.Delay(retryDelaySeconds * 1000); // 等待一段时间后重试
  111. await hubConnection.StartAsync();
  112. _logger.LogInformation("SignalR连接成功(重试后)!");
  113. break; // 连接成功,退出循环
  114. }
  115. catch (Exception retryEx)
  116. {
  117. retryCount++;
  118. _logger.LogInformation($"SignalR连接重试失败: {retryEx.Message}。重试次数: {retryCount}/{maxRetries}");
  119. // 可以在这里决定是否因为某种原因停止重试
  120. if (retryCount == maxRetries)
  121. {
  122. _logger.LogInformation("达到最大重试次数,停止重试。");
  123. break;
  124. }
  125. }
  126. }
  127. }
  128. }
  129. public async Task<(int status, string msg, PDFGenQueue? task )> ReceiveMessage(ScreenProcessMessage message)
  130. {
  131. int status = 0;
  132. string msg= string.Empty;
  133. PDFGenQueue? task = null;
  134. try
  135. {
  136. task = message.content.ToObject<PDFGenQueue>();
  137. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  138. var client = _httpClientFactory.CreateClient();
  139. string urlpdf = $"{device.screenUrl}/api/pdf";
  140. string jsonElement = new
  141. {
  142. pageUrl = task.pageUrl,
  143. timeout = task.timeout,
  144. delay = task.delay,
  145. checkPageCompleteJs = task.checkPageCompleteJs
  146. }.ToJsonString();
  147. var request = new HttpRequestMessage
  148. {
  149. Method = new HttpMethod("POST"),
  150. RequestUri = new Uri(urlpdf),
  151. Content = new StringContent(jsonElement)
  152. };
  153. var mediaTypeHeader = new MediaTypeHeaderValue("application/json")
  154. {
  155. CharSet = "UTF-8"
  156. };
  157. request.Content.Headers.ContentType = mediaTypeHeader;
  158. HttpResponseMessage responseMessage = await client.SendAsync(request);
  159. if (responseMessage.IsSuccessStatusCode)
  160. {
  161. string content = await responseMessage.Content.ReadAsStringAsync();
  162. JsonNode jsonNode = content.ToObject<JsonNode>();
  163. var code = jsonNode["code"];
  164. var file = jsonNode["data"]?["file"];
  165. if (code!=null && $"{code}".Equals("0") && file!= null && !string.IsNullOrWhiteSpace($"{file}"))
  166. {
  167. try
  168. {
  169. Stream stream = await client.GetStreamAsync($"{device.screenUrl}/{file}");
  170. string content_type = "application/octet-stream";
  171. ContentTypeDict.dict.TryGetValue(".pdf", out string? contenttype);
  172. if (!string.IsNullOrEmpty(contenttype))
  173. {
  174. content_type = contenttype;
  175. }
  176. BlobClient blockBlob = new BlobClient(new Uri(task.blobFullUrl));
  177. await blockBlob.UploadAsync(stream, true);
  178. blockBlob.SetHttpHeaders(new BlobHttpHeaders { ContentType = content_type });
  179. status=2;
  180. msg = $"PDF回传保存成功!保存地址{task.blobFullUrl}";
  181. }
  182. catch (Exception ex) {
  183. status=6;
  184. msg = $"PDF回传保存异常!异常信息{ex.Message},相关参数信息:保存地址{task.blobFullUrl}";
  185. }
  186. }
  187. else {
  188. if (code!= null && $"{code}".Equals("99999"))
  189. {
  190. status= 4;
  191. msg = "PDF生成接口调用超时!";
  192. }
  193. else
  194. {
  195. status= 3;
  196. msg =$"PDF生成接口返回参数异常!返回内容:{content}";
  197. }
  198. }
  199. }
  200. else {
  201. status=3;
  202. msg =$"PDF生成接口调用异常!接口状态:{responseMessage.StatusCode}";
  203. }
  204. }
  205. catch (Exception ex)
  206. {
  207. status = 3;
  208. msg = $"PDF生成接口调用异常,异常信息“{ex.Message}";
  209. }
  210. return (status, msg, task);
  211. }
  212. }
  213. public class ExponentialBackoffReconnectPolicy : IRetryPolicy
  214. {
  215. private readonly TimeSpan _retryInterval;
  216. private int _retryCount;
  217. public int MaxRetryCount { get; set; } =int.MaxValue;
  218. public readonly ILogger<SignalRScreenClientHub> _logger;
  219. public ExponentialBackoffReconnectPolicy(TimeSpan retryInterval, ILogger<SignalRScreenClientHub> logger)
  220. {
  221. _retryInterval = retryInterval;
  222. _retryCount = 0;
  223. _logger = logger;
  224. }
  225. public TimeSpan? NextRetryDelay(RetryContext retryContext)
  226. {
  227. _logger.LogInformation($"重连次数: {_retryCount}");
  228. if (_retryCount < MaxRetryCount)
  229. {
  230. _retryCount++;
  231. // 计算下一次重连的延迟时间
  232. return _retryInterval;
  233. }
  234. return null; // 达到最大重连次数后不再重连
  235. }
  236. public void Reset()
  237. {
  238. _retryCount = 0;
  239. }
  240. }
  241. }