using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace GeoSigmaDrawLib { public class LicenseServerConnection { public delegate void LicenseInvalidateEventHandler(int status, string msg); public LicenseInvalidateEventHandler LicenseInvalidateEvent; private string rootPath = string.Empty; public string UrlRootPath { get { return rootPath; } set { rootPath = value; } } private string processID = string.Empty; public string ProcessID { get { if (string.IsNullOrEmpty(processID)) { processID = Guid.NewGuid().ToString(); } return processID; } } private readonly HttpClient _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; // 使用 CancellationTokenSource 来控制后台任务的启停。 private CancellationTokenSource _cts; private readonly object _lock = new object(); // 将硬编码的URL和间隔时间定义为常量或静态变量,便于修改。 private const string ServiceUrlTemplate = "requestPermission?ip={0}&userName={1}&hostName={2}&token={3}&feature={4}&version={5}"; public async Task TestServerAsync(string serverUrl) { string strServicePath = $"{serverUrl}/sayHello"; HttpClient httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(20) }; try { string stringResult = await httpClient.GetStringAsync(strServicePath); ReplyMessage reply = JsonConvert.DeserializeObject(stringResult); return reply; } catch (Exception ex) { ReplyMessage msg = new ReplyMessage(); msg.Code = 0; msg.Message = ex.Message; return msg; } } /// /// 启动或更新后台心跳任务 /// /// 新的工作路径 /// 任务执行的间隔 public void StartOrUpdateTask(string workPath, TimeSpan interval) { this.rootPath = workPath; lock (_lock) { // 如果已有任务在运行,先发送取消信号并等待其结束 _cts?.Cancel(); _cts?.Dispose(); // 创建一个新的CancellationTokenSource来启动新任务 _cts = new CancellationTokenSource(); // 使用 Task.Run 启动一个长期的后台任务 Task.Run(() => RequestPermission(workPath, interval, _cts.Token), _cts.Token); } } /// /// 后台任务的主循环,替代了原来的Timer /// private async Task RequestPermission(string workPath, TimeSpan interval, CancellationToken cancellationToken) { //interval = TimeSpan.FromSeconds(10); Trace.WriteLine("后台分析任务已启动..."); while (!cancellationToken.IsCancellationRequested) { try { LicenseInfo license = Security.CurentLicenseInfo; // --- 单次任务的核心逻辑 --- Trace.WriteLine($"开始执行分析... WorkPath: {workPath}"); string strServicePath = $"{workPath}/{ServiceUrlTemplate}"; string requestUrl = string.Format(strServicePath, Uri.EscapeDataString(GetActiveIPv4Address()), Uri.EscapeDataString(Environment.UserName), Uri.EscapeDataString(Environment.MachineName), Uri.EscapeDataString(ProcessID), Uri.EscapeDataString(license.Products), Uri.EscapeDataString(license.Version)); // 使用静态的 HttpClient 实例发送请求 string stringResult = await SendHeartbeatRequestAsync(requestUrl); try { // 反序列化并处理结果 ReplyMessage reply = JsonConvert.DeserializeObject(stringResult); LicenseInvalidateEvent?.Invoke(reply.Code, reply.Message); } catch { LicenseInvalidateEvent?.Invoke(-1, "连接错误!"); } //Trace.WriteLine($"服务调用成功,返回: {cleanString}"); } catch (HttpRequestException ex) { Trace.WriteLine($"服务调用失败(HTTP): {ex.Message}"); LicenseInvalidateEvent?.Invoke(0, ex.Message); } catch (TaskCanceledException ex) { // 检查这个取消是否由HttpClient超时引起 if (ex.CancellationToken.IsCancellationRequested == false) { string timeoutMessage = $"服务调用超时(超过 {_httpClient.Timeout.TotalSeconds} 秒)。"; LicenseInvalidateEvent?.Invoke(0, timeoutMessage); // 触发超时事件 } else { // 如果是由外部的cancellationToken引发的,则是正常停止 Trace.WriteLine("任务被外部取消。"); break; // 正常退出循环 } break; } catch (Exception ex) { LicenseInvalidateEvent?.Invoke(2, ex.Message); // 捕获所有其他异常,防止后台任务崩溃 Trace.WriteLine($"后台任务执行出错: {ex.Message}"); } //// 验证许可成功 //LicenseInvalidateEvent?.Invoke(1, "验证成功!"); try { // 使用 Task.Delay 代替 Timer 的 period,它能被安全地取消。 await Task.Delay(interval, cancellationToken); } catch (TaskCanceledException ex) { // 检查这个取消是否由HttpClient超时引起 if (ex.CancellationToken.IsCancellationRequested == false) { string timeoutMessage = $"服务调用超时(超过 {_httpClient.Timeout.TotalSeconds} 秒)。"; LicenseInvalidateEvent?.Invoke(0, timeoutMessage); // 触发超时事件 } else { // 如果是由外部的cancellationToken引发的,则是正常停止 Trace.WriteLine("任务被外部取消。"); break; // 正常退出循环 } break; } } Trace.WriteLine("后台分析任务已优雅地停止。"); } private async Task SendHeartbeatRequestAsync(string url) { int maxRetries = 5; string lastErrorMessage = string.Empty; for (int i = 0; i <= maxRetries; i++) { try { // 每次循环都创建新的 Request (HttpRequestMessage 不能复用) using (var request = new HttpRequestMessage(HttpMethod.Get, url)) { request.Headers.ConnectionClose = true; using (var response = await _httpClient.SendAsync(request)) { if (response.StatusCode == System.Net.HttpStatusCode.BadGateway || response.StatusCode == System.Net.HttpStatusCode.GatewayTimeout) { if (i < maxRetries) { LoggerUtil.Logger.Warn($"Heartbeat 502/504 detected. Retrying {i + 1}/{maxRetries}..."); await Task.Delay(1000); // 等待1秒再重试,给服务器喘息时间 continue; } } response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } } } catch (Exception ex) { lastErrorMessage = ex.Message; LoggerUtil.Logger.Error($"Heartbeat attempt {i + 1} failed: {ex.Message}"); if (i == maxRetries) { return ex.Message; } } } return lastErrorMessage; } /// /// 停止后台任务 /// public void StopTask() { lock (_lock) { _cts?.Cancel(); // 发送取消信号 _cts?.Dispose(); _cts = null; Trace.WriteLine("已发送停止信号。"); } } public Task SendOfflineAsync() { string offlineUrlTemplate = "requestOffline?token={0}"; LicenseInfo license = Security.CurentLicenseInfo; string strServicePath = $"{this.rootPath}/{offlineUrlTemplate}"; string requestUrl = string.Format(strServicePath, Uri.EscapeDataString(ProcessID)); // 使用静态的 HttpClient 实例发送请求 return _httpClient.GetStringAsync(requestUrl); } public static string GetActiveIPv4Address() { // 1. 获取所有网络接口 var allInterfaces = NetworkInterface.GetAllNetworkInterfaces(); // 2. 筛选出我们感兴趣的接口 var activeInterface = allInterfaces.FirstOrDefault(ni => // a. 接口必须是活动的 (Up) ni.OperationalStatus == OperationalStatus.Up && // b. 接口不能是环回接口 (e.g., 127.0.0.1) ni.NetworkInterfaceType != NetworkInterfaceType.Loopback && // c. 接口必须有网关地址 (这是关键!) ni.GetIPProperties().GatewayAddresses.Any()); if (activeInterface == null) { return "未找到活动的网络接口。"; } // 3. 从该接口的 IP 属性中找到 IPv4 地址 var ipProperties = activeInterface.GetIPProperties(); var ipv4Address = ipProperties.UnicastAddresses .FirstOrDefault(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork); return ipv4Address?.Address.ToString() ?? string.Empty; } } /// /// 应答消息类. /// public class ReplyMessage { /// /// Initializes a new instance of the class. /// public ReplyMessage() { } /// /// 消息代码 /// [JsonProperty(PropertyName = "code")] public int Code { get; set; } = 0; /// /// 消息内容 /// [JsonProperty(PropertyName = "msg")] public string Message { get; set; } = string.Empty; } }