You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kev/Drawer/UCDraw/GeoSigmaDrawLib/LicenseServerConnection.cs

240 lines
9.1 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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(1000) };
// 使用 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<ReplyMessage> 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<ReplyMessage>(stringResult);
return reply;
}
catch (Exception ex)
{
ReplyMessage msg = new ReplyMessage();
msg.Code = 0;
msg.Message = ex.Message;
return msg;
}
}
/// <summary>
/// 启动或更新后台心跳任务
/// </summary>
/// <param name="workPath">新的工作路径</param>
/// <param name="interval">任务执行的间隔</param>
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);
}
}
/// <summary>
/// 后台任务的主循环替代了原来的Timer
/// </summary>
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 _httpClient.GetStringAsync(requestUrl);
// 反序列化并处理结果
ReplyMessage reply = JsonConvert.DeserializeObject<ReplyMessage>(stringResult);
LicenseInvalidateEvent?.Invoke(reply.Code, reply.Message);
//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)
{
// 等待期间任务被取消,正常退出
break;
}
}
Trace.WriteLine("后台分析任务已优雅地停止。");
}
/// <summary>
/// 停止后台任务
/// </summary>
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;
}
}
/// <summary>
/// 应答消息类.
/// </summary>
public class ReplyMessage
{
/// <summary>
/// Initializes a new instance of the <see cref="ReplyMessage"/> class.
/// </summary>
public ReplyMessage()
{
}
/// <summary>
/// 消息代码
/// </summary>
public int Code { get; set; } = 0;
/// <summary>
/// 消息内容
/// </summary>
public string Message { get; set; } = string.Empty;
}
}