using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.Configuration; using System.Diagnostics; using System.Management; using System.Net; using System.Net.Http.Headers; using System.Net.NetworkInformation; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using TEAMModelOS.SDK; using TEAMModelOS.SDK.DI.Device; using TEAMModelOS.SDK.Extension; namespace HTEX.ScreenClient.Services { public class SignalRScreenClientHub : BackgroundService, IDisposable { private readonly IConfiguration _configuration; private readonly ILogger _logger; private TEAMModelOS.SDK.ScreenClient? device; private readonly CoreDevice _device; private readonly IHttpClientFactory _httpClientFactory; public SignalRScreenClientHub(IConfiguration configuration,ILogger logger,IHttpClientFactory httpClientFactory, CoreDevice device) { _configuration=configuration; _logger=logger; _httpClientFactory=httpClientFactory; _device = device; } protected async override Task ExecuteAsync(CancellationToken stoppingToken) { var coreDevice = await _device.GetCoreDevice(); device=coreDevice.ToJsonString().ToObject(); string clientid = device.deviceId!; string? CenterUrl = _configuration.GetSection("ScreenClient:CenterUrl").Value; string? ScreenUrl = _configuration.GetSection("ScreenClient:ScreenUrl").Value; long Timeout = _configuration.GetValue("ScreenClient:Timeout"); long Delay = _configuration.GetValue("ScreenClient:Delay"); device.timeout = Timeout; device.delay = Delay; device.screenUrl = ScreenUrl; await StartHubConnectionAsync( clientid, CenterUrl); } private async Task StartHubConnectionAsync(string clientid, string? CenterUrl) { //重写重连策略,防止服务端更新,断线后,客户端达到最大连接次数,依然连线不上服务端。 var reconnectPolicy = new ExponentialBackoffReconnectPolicy(TimeSpan.FromSeconds(10), _logger); // 尝试重连的最大次数,这里使用 int.MaxValue 表示无限次 reconnectPolicy.MaxRetryCount = int.MaxValue; HubConnection hubConnection = new HubConnectionBuilder() .WithUrl($"{CenterUrl}/signalr/screen?grant_type={ScreenConstant.grant_type}&clientid={clientid}&device={HttpUtility.UrlEncode(device.ToJsonString(), Encoding.Unicode)}") //only one slash .WithAutomaticReconnect(reconnectPolicy) .ConfigureLogging(logging => { logging.SetMinimumLevel(LogLevel.Information); logging.AddConsole(); }) .Build(); try { hubConnection.On("ReceiveConnection", (message) => { _logger.LogInformation($"连接成功:{message.ToJsonString()}"); //重置重连次数。 reconnectPolicy.Reset(); }); hubConnection.On("ReceiveMessage", async (message) => { if (message.message_type.Equals(MessageType.task_send_success)) { var data = await ReceiveMessage(message); _logger.LogInformation($"任务执行完成,执行状态{data.status},消息:{data.msg},任务信息:{data.task?.ToJsonString()}"); message.content=data.task?.ToJsonString(); message.result=data.status; message.msg=data.msg; if (data.status==2) { message.message_type= MessageType.task_execute_success; } else { message.message_type= MessageType.task_execute_error; } await hubConnection.InvokeAsync("ReceiveMessage", message); } else { _logger.LogInformation($"任务领取失败,{message.ToJsonString()}"); } }); await hubConnection.StartAsync(); } catch (Exception ex) { _logger.LogError("初次启动连接SignalR失败,等待重连......"); int retryCount = 0; const int maxRetries = 360; const int retryDelaySeconds = 10; while (retryCount < maxRetries) { try { await Task.Delay(retryDelaySeconds * 1000); // 等待一段时间后重试 await hubConnection.StartAsync(); _logger.LogInformation("SignalR连接成功(重试后)!"); break; // 连接成功,退出循环 } catch (Exception retryEx) { retryCount++; _logger.LogInformation($"SignalR连接重试失败: {retryEx.Message}。重试次数: {retryCount}/{maxRetries}"); // 可以在这里决定是否因为某种原因停止重试 if (retryCount == maxRetries) { _logger.LogInformation("达到最大重试次数,停止重试。"); break; } } } } } public async Task<(int status, string msg, PDFGenQueue? task )> ReceiveMessage(ScreenProcessMessage message) { int status = 0; string msg= string.Empty; PDFGenQueue? task = null; try { task = message.content.ToObject(); long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var client = _httpClientFactory.CreateClient(); string urlpdf = $"{device.screenUrl}/api/pdf"; string jsonElement = new { pageUrl = task.pageUrl, timeout = task.timeout, delay = task.delay, checkPageCompleteJs = task.checkPageCompleteJs }.ToJsonString(); var request = new HttpRequestMessage { Method = new HttpMethod("POST"), RequestUri = new Uri(urlpdf), Content = new StringContent(jsonElement) }; var mediaTypeHeader = new MediaTypeHeaderValue("application/json") { CharSet = "UTF-8" }; request.Content.Headers.ContentType = mediaTypeHeader; HttpResponseMessage responseMessage = await client.SendAsync(request); if (responseMessage.IsSuccessStatusCode) { string content = await responseMessage.Content.ReadAsStringAsync(); JsonNode jsonNode = content.ToObject(); var code = jsonNode["code"]; var file = jsonNode["data"]?["file"]; if (code!=null && $"{code}".Equals("0") && file!= null && !string.IsNullOrWhiteSpace($"{file}")) { try { Stream stream = await client.GetStreamAsync($"{device.screenUrl}/{file}"); string content_type = "application/octet-stream"; ContentTypeDict.dict.TryGetValue(".pdf", out string? contenttype); if (!string.IsNullOrEmpty(contenttype)) { content_type = contenttype; } BlobClient blockBlob = new BlobClient(new Uri(task.blobFullUrl)); await blockBlob.UploadAsync(stream, true); blockBlob.SetHttpHeaders(new BlobHttpHeaders { ContentType = content_type }); status=2; msg = $"PDF回传保存成功!保存地址{task.blobFullUrl}"; } catch (Exception ex) { status=6; msg = $"PDF回传保存异常!异常信息{ex.Message},相关参数信息:保存地址{task.blobFullUrl}"; } } else { if (code!= null && $"{code}".Equals("99999")) { status= 4; msg = "PDF生成接口调用超时!"; } else { status= 3; msg =$"PDF生成接口返回参数异常!返回内容:{content}"; } } } else { status=3; msg =$"PDF生成接口调用异常!接口状态:{responseMessage.StatusCode}"; } } catch (Exception ex) { status = 3; msg = $"PDF生成接口调用异常,异常信息“{ex.Message}"; } return (status, msg, task); } } public class ExponentialBackoffReconnectPolicy : IRetryPolicy { private readonly TimeSpan _retryInterval; private int _retryCount; public int MaxRetryCount { get; set; } =int.MaxValue; public readonly ILogger _logger; public ExponentialBackoffReconnectPolicy(TimeSpan retryInterval, ILogger logger) { _retryInterval = retryInterval; _retryCount = 0; _logger = logger; } public TimeSpan? NextRetryDelay(RetryContext retryContext) { _logger.LogInformation($"重连次数: {_retryCount}"); if (_retryCount < MaxRetryCount) { _retryCount++; // 计算下一次重连的延迟时间 return _retryInterval; } return null; // 达到最大重连次数后不再重连 } public void Reset() { _retryCount = 0; } } }