Files
XplorePlane/XP.Common/License/Implementations/LicenseService.cs
T

596 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Configuration;
using System.Reflection;
using System.Text;
using System.Threading;
using XP.Common.License.Configs;
using XP.Common.License.Enums;
using XP.Common.License.Interfaces;
using XP.Common.License.Native;
using XP.Common.Logging.Interfaces;
namespace XP.Common.License.Implementations
{
/// <summary>
/// 授权服务实现 | License service implementation
/// </summary>
public class LicenseService : ILicenseService, IDisposable
{
/// <summary>
/// 临时测试模式初始时间(秒)| Temporary test mode initial time (seconds)
/// </summary>
private const int TestModeInitialSeconds = 900;
/// <summary>
/// 授权配置 | License configuration
/// </summary>
private readonly LicenseConfig _config;
/// <summary>
/// 日志服务 | Logger service
/// </summary>
private readonly ILoggerService _logger;
/// <summary>
/// 同步锁对象 | Synchronization lock object
/// </summary>
private readonly object _lock = new object();
/// <summary>
/// 临时测试模式计时器 | Temporary test mode timer
/// </summary>
private Timer? _testModeTimer;
/// <summary>
/// 临时测试模式剩余时间(秒)| Temporary test mode remaining time (seconds)
/// </summary>
private int _remainingTestSeconds = TestModeInitialSeconds;
/// <summary>
/// 授权到期日期 | License expiration date
/// </summary>
private DateTime? _expirationDate;
/// <summary>
/// SMA到期日期 | SMA expiration date
/// </summary>
private DateTime? _smaDate;
/// <summary>
/// 浮动许可IP地址 | Floating license IP address
/// </summary>
private string? _floatingLicenseIp;
/// <summary>
/// 浮动许可端口 | Floating license port
/// </summary>
private string? _floatingLicensePort;
/// <summary>
/// 是否已释放 | Whether disposed
/// </summary>
private bool _disposed;
/// <summary>
/// 构造函数 | Constructor
/// </summary>
/// <param name="config">授权配置 | License configuration</param>
/// <param name="logger">日志服务 | Logger service</param>
public LicenseService(LicenseConfig config, ILoggerService logger)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_logger = logger?.ForModule<LicenseService>() ?? throw new ArgumentNullException(nameof(logger));
LicenseMode = (LicenseMode)_config.LicenseMode;
}
/// <summary>
/// 当前会话是否已授权 | Whether the current session is authorized
/// </summary>
public bool IsAuthorized { get; private set; }
/// <summary>
/// 当前授权模式 | Current license mode
/// </summary>
public LicenseMode LicenseMode { get; private set; }
/// <summary>
/// 临时测试模式超时事件(到期时触发)| Temporary test mode timeout event (fires when expired)
/// </summary>
public event EventHandler? TestModeTimeout;
/// <summary>
/// 临时测试模式剩余5分钟警告事件 | Temporary test mode 5-minute warning event
/// </summary>
public event EventHandler? TestModeWarning5Min;
/// <summary>
/// 临时测试模式剩余1分钟警告事件 | Temporary test mode 1-minute warning event
/// </summary>
public event EventHandler? TestModeWarning1Min;
/// <summary>
/// 是否已触发5分钟警告 | Whether 5-minute warning has been fired
/// </summary>
private bool _warning5MinFired;
/// <summary>
/// 是否已触发1分钟警告 | Whether 1-minute warning has been fired
/// </summary>
private bool _warning1MinFired;
/// <summary>
/// 执行授权检查 | Perform authorization check
/// </summary>
/// <returns>授权检查结果 | License check result</returns>
public LicenseCheckResult CheckAuthorization()
{
lock (_lock)
{
_logger.Info("开始授权检查,模式={Mode} | Starting authorization check, mode={Mode}", (int)LicenseMode);
if (LicenseMode == Enums.LicenseMode.TemporaryTest)
{
return HandleTemporaryTestMode();
}
return HandleClmsAuthorization();
}
}
/// <summary>
/// 获取授权到期日期 | Get license expiration date
/// </summary>
/// <returns>授权到期日期,未授权时返回 null | License expiration date, null if not authorized</returns>
public DateTime? GetExpirationDate()
{
lock (_lock)
{
return _expirationDate;
}
}
/// <summary>
/// 检查模块是否授权 | Check if module is licensed
/// </summary>
/// <param name="moduleId">模块ID | Module ID</param>
/// <returns>模块是否授权 | Whether the module is licensed</returns>
public bool IsModuleLicensed(ushort moduleId)
{
lock (_lock)
{
if (!IsAuthorized)
return false;
try
{
ushort mod = moduleId;
ushort type = 0;
return NativeMethods.CLM_ModuleIsLicensed(ref mod, ref type);
}
catch (DllNotFoundException ex)
{
_logger.Error(ex, "MORCODE.dll 加载失败 | Failed to load MORCODE.dll");
return false;
}
catch (EntryPointNotFoundException ex)
{
_logger.Error(ex, "MORCODE.dll 中缺少入口点 | Missing entry point in MORCODE.dll");
return false;
}
}
}
/// <summary>
/// 获取SMA到期日期 | Get SMA expiration date
/// </summary>
/// <returns>SMA到期日期,未启用时返回 null | SMA expiration date, null if not enabled</returns>
public DateTime? GetSmaDate()
{
lock (_lock)
{
return _smaDate;
}
}
/// <summary>
/// 获取临时测试模式剩余时间 | Get remaining time in temporary test mode
/// </summary>
/// <returns>剩余时间(秒),非测试模式返回 -1 | Remaining time in seconds, -1 if not in test mode</returns>
public int GetRemainingTestTime()
{
if (LicenseMode != Enums.LicenseMode.TemporaryTest)
return -1;
return Interlocked.CompareExchange(ref _remainingTestSeconds, 0, 0);
}
/// <summary>
/// 释放资源 | Release resources
/// </summary>
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
// 停止计时器 | Stop timer
_testModeTimer?.Dispose();
_testModeTimer = null;
// 尝试登出 | Try to logout
try
{
NativeMethods.CLM_Logout();
}
catch (Exception ex)
{
_logger.Error(ex, "CLM_Logout 调用失败 | CLM_Logout call failed");
}
}
/// <summary>
/// 处理临时测试模式 | Handle temporary test mode
/// </summary>
/// <returns>授权检查结果 | License check result</returns>
private LicenseCheckResult HandleTemporaryTestMode()
{
IsAuthorized = true;
_remainingTestSeconds = TestModeInitialSeconds;
// 启动计时器,每秒递减 | Start timer, decrement every second
_testModeTimer?.Dispose();
_testModeTimer = new Timer(TestModeTimerCallback, null, 1000, 1000);
_logger.Info("临时测试模式已启动,剩余时间={Seconds}秒 | Temporary test mode started, remaining time={Seconds}s", TestModeInitialSeconds);
WriteLicenseStateToConfig(LicenseState.Success);
return new LicenseCheckResult(
isAuthorized: true,
message: "临时测试模式已启动,有效时间15分钟 | Temporary test mode started, valid for 15 minutes",
licenseMode: Enums.LicenseMode.TemporaryTest,
moduleId: _config.ModuleId,
expirationDate: null,
smaDate: null,
floatingLicenseIp: null,
floatingLicensePort: null);
}
/// <summary>
/// 临时测试模式计时器回调 | Temporary test mode timer callback
/// </summary>
/// <param name="state">状态对象 | State object</param>
private void TestModeTimerCallback(object? state)
{
int remaining = Interlocked.Decrement(ref _remainingTestSeconds);
// 剩余5分钟(300秒)时触发警告 | Fire warning at 5 minutes (300 seconds) remaining
if (remaining <= 300 && !_warning5MinFired)
{
_warning5MinFired = true;
_logger.Warn("临时测试模式剩余5分钟 | Temporary test mode: 5 minutes remaining");
TestModeWarning5Min?.Invoke(this, EventArgs.Empty);
}
// 剩余1分钟(60秒)时触发警告 | Fire warning at 1 minute (60 seconds) remaining
if (remaining <= 60 && !_warning1MinFired)
{
_warning1MinFired = true;
_logger.Warn("临时测试模式剩余1分钟 | Temporary test mode: 1 minute remaining");
TestModeWarning1Min?.Invoke(this, EventArgs.Empty);
}
// 到期时触发超时事件 | Fire timeout event when expired
if (remaining <= 0)
{
// 停止计时器 | Stop timer
_testModeTimer?.Dispose();
_testModeTimer = null;
_logger.Info("临时测试模式已超时 | Temporary test mode has timed out");
// 触发超时事件 | Raise timeout event
TestModeTimeout?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// 处理 CLMS 正式授权流程 | Handle CLMS formal authorization flow
/// 兼容新旧版本 SDK:对可能不存在的入口点使用 TryInvoke 优雅降级 |
/// Compatible with old/new SDK: gracefully degrades for missing entry points via TryInvoke
/// </summary>
/// <returns>授权检查结果 | License check result</returns>
private LicenseCheckResult HandleClmsAuthorization()
{
try
{
// 步骤 1:检查系统时间(可选,老版本 SDK 可能不支持)| Step 1: Check system time (optional, old SDK may not support)
if (!TryInvokeOptional(() => NativeMethods.CLM_CheckSystemTime(), "CLM_CheckSystemTime", out bool checkTimeResult))
{
// 入口点不存在,跳过此步骤 | Entry point not found, skip this step
_logger.Warn("CLM_CheckSystemTime 入口点不存在,跳过系统时间检查(SDK版本较旧)| CLM_CheckSystemTime entry point not found, skipping system time check (older SDK version)");
}
else if (!checkTimeResult)
{
_logger.Error(new InvalidOperationException("CLM_CheckSystemTime"), "系统时间检查异常 | System time check anomaly");
return CreateFailureResult("系统时间检查异常 | System time check anomaly");
}
else
{
_logger.Info("系统时间检查正常 | System time check: OK");
}
// 步骤 2:登录验证(核心,必须存在)| Step 2: Login verification (core, must exist)
StringBuilder password = new StringBuilder("FnEoFWSNLpVeoNWYhVoHLfgITRvieSszJfylVsXOTsLkphgkPzPhbLQzQrvRbNOkVVIQyMWkyGVjWSaiYUEksfQsRmklksLxrmeTksKKNMoZoWfZeDaLDSyWwEmtQakvSNxBMBLHoLEZHtaoXNpTWiaUGaSLQdsHFZnbRyPehytarNTKpaNNqnjFNggqWifhFsrZasDsWbIGWDrhnGrdtUNDMjJdhlTunsssxCzYpsLQrWBxUkuUUEJraSbTlbuX");
if (!NativeMethods.CLM_Login(password))
{
_logger.Error(new InvalidOperationException("CLM_Login"), "CLM_Login 登录验证失败 | CLM_Login verification failed");
return CreateFailureResult("CLM_Login 登录验证失败 | CLM_Login verification failed");
}
_logger.Info("CLM_Login 登录验证成功 | CLM_Login verification: OK");
// 步骤 3:检查许可范围(核心,必须存在)| Step 3: Check license scope (core, must exist)
if (!NativeMethods.CLM_Login_Scope())
{
_logger.Error(new InvalidOperationException("CLM_Login_Scope"), "CLM_Login_Scope 许可范围检查失败 | CLM_Login_Scope scope check failed");
return CreateFailureResult("CLM_Login_Scope 许可范围检查失败 | CLM_Login_Scope scope check failed");
}
_logger.Info("CLM_Login_Scope 许可范围检查成功 | CLM_Login_Scope scope check: OK");
// 步骤 4:SMA 验证(如果启用,可选入口点)| Step 4: SMA validation (if enabled, optional entry point)
if (_config.UseSma)
{
var smaResult = ValidateSma();
if (smaResult != null)
return smaResult;
}
else
{
_logger.Info("放弃检查SMA | SMA check skipped");
}
// 步骤 5:获取浮动许可IP和端口(可选,老版本 SDK 可能不支持)| Step 5: Get floating license IP and port (optional, old SDK may not support)
StringBuilder ip = new StringBuilder(256);
StringBuilder port = new StringBuilder(256);
if (!TryInvokeOptional(() => NativeMethods.CLM_GetIP(ip, port), "CLM_GetIP", out bool getIpResult))
{
_logger.Warn("CLM_GetIP 入口点不存在,跳过浮动许可IP获取(SDK版本较旧)| CLM_GetIP entry point not found, skipping floating license IP retrieval (older SDK version)");
}
else if (!getIpResult)
{
_logger.Error(new InvalidOperationException("CLM_GetIP"), "CLM_GetIP 获取浮动许可IP失败 | CLM_GetIP floating license IP retrieval failed");
return CreateFailureResult("CLM_GetIP 获取浮动许可IP失败 | CLM_GetIP floating license IP retrieval failed");
}
else
{
_floatingLicenseIp = ip.ToString();
_floatingLicensePort = port.ToString();
_logger.Info("CLM_GetIP 成功: ip={Ip}/port={Port} | CLM_GetIP success: ip={Ip}/port={Port}", _floatingLicenseIp, _floatingLicensePort);
}
// 步骤 6:获取错误信息(可选,老版本 SDK 可能不支持)| Step 6: Get error message (optional, old SDK may not support)
StringBuilder error = new StringBuilder(512);
if (!TryInvokeOptional(() => NativeMethods.CLM_GetError(error), "CLM_GetError", out bool getErrorResult))
{
_logger.Warn("CLM_GetError 入口点不存在,跳过错误信息获取(SDK版本较旧)| CLM_GetError entry point not found, skipping error retrieval (older SDK version)");
}
else if (!getErrorResult)
{
_logger.Error(new InvalidOperationException("CLM_GetError"), "CLM_GetError 获取错误信息失败 | CLM_GetError error retrieval failed");
return CreateFailureResult("CLM_GetError 获取错误信息失败 | CLM_GetError error retrieval failed");
}
else
{
_logger.Info("CLM_GetError 成功: {Error} | CLM_GetError success: {Error}", error.ToString());
}
// 步骤 7:检查模块授权(核心,必须存在)| Step 7: Check module authorization (core, must exist)
ushort moduleId = _config.ModuleId;
ushort type = 0;
if (!NativeMethods.CLM_ModuleIsLicensed(ref moduleId, ref type))
{
_logger.Error(new InvalidOperationException("CLM_ModuleIsLicensed"), "模块号码{ModuleId}不可用 | Module {ModuleId} unavailable", moduleId);
return CreateFailureResult($"模块号码{moduleId}不可用 | Module {moduleId} unavailable");
}
_logger.Info("模块号码{ModuleId}有效 | Module {ModuleId} available", moduleId);
// 步骤 8:获取授权到期日期(核心,必须存在)| Step 8: Get warranty expiration date (core, must exist)
int month = 0, day = 0, year = 0;
if (!NativeMethods.CLM_GetWarrantyExpiration(ref month, ref day, ref year))
{
_logger.Error(new InvalidOperationException("CLM_GetWarrantyExpiration"), "获取授权到期日期失败 | Failed to get warranty expiration date");
return CreateFailureResult("获取授权到期日期失败 | Failed to get warranty expiration date");
}
_expirationDate = new DateTime(year, month, day);
_logger.Info("CLM_GetWarrantyExpiration 成功: {Year}/{Month}/{Day} | CLM_GetWarrantyExpiration success: {Year}/{Month}/{Day}", year, month, day);
// 检查是否在30天内到期 | Check if expiring within 30 days
string warningMessage = string.Empty;
TimeSpan timeToExpiry = _expirationDate.Value - DateTime.Now;
if (timeToExpiry.Days <= 30)
{
warningMessage = $"软件授权将于{year}年{month}月{day}日到期,请尽快联系海克斯康 | Software license will expire on {year}-{month}-{day}, please contact Hexagon";
_logger.Warn(warningMessage);
}
// 授权成功 | Authorization successful
IsAuthorized = true;
WriteLicenseStateToConfig(LicenseState.Success);
string successMessage = string.IsNullOrEmpty(warningMessage)
? "授权检查成功 | Authorization check successful"
: $"授权检查成功(警告:{warningMessage}| Authorization check successful (Warning: {warningMessage})";
_logger.Info("授权检查完成 | Authorization check completed: Mode={Mode}, State={State}, Expiration={Expiration}",
(int)LicenseMode,
(int)LicenseState.Success,
_expirationDate?.ToString("yyyy-MM-dd") ?? "N/A");
return new LicenseCheckResult(
isAuthorized: true,
message: successMessage,
licenseMode: LicenseMode,
moduleId: _config.ModuleId,
expirationDate: _expirationDate,
smaDate: _smaDate,
floatingLicenseIp: _floatingLicenseIp,
floatingLicensePort: _floatingLicensePort);
}
catch (DllNotFoundException ex)
{
_logger.Error(ex, "MORCODE.dll 加载失败 | Failed to load MORCODE.dll");
return CreateFailureResult("CLMS SDK 不可用 | CLMS SDK unavailable");
}
}
/// <summary>
/// 尝试调用可选的 SDK 方法,兼容老版本 SDK 中不存在的入口点 |
/// Try to invoke an optional SDK method, compatible with missing entry points in older SDK versions
/// </summary>
/// <param name="action">要调用的方法委托 | Method delegate to invoke</param>
/// <param name="methodName">方法名称(用于日志)| Method name (for logging)</param>
/// <param name="result">方法返回值,入口点不存在时为 default | Method return value, default if entry point not found</param>
/// <returns>true: 入口点存在并已调用; false: 入口点不存在(EntryPointNotFoundException| true: entry point exists and was invoked; false: entry point not found</returns>
private bool TryInvokeOptional(Func<bool> action, string methodName, out bool result)
{
try
{
result = action();
return true;
}
catch (EntryPointNotFoundException)
{
result = default;
return false;
}
}
/// <summary>
/// 验证 SMA | Validate SMA
/// 兼容老版本 SDKCLM_GetSmaDate 入口点不存在时跳过 SMA 验证 |
/// Compatible with old SDK: skips SMA validation if CLM_GetSmaDate entry point not found
/// </summary>
/// <returns>失败时返回失败结果,成功或跳过时返回 null | Returns failure result on failure, null on success or skip</returns>
private LicenseCheckResult? ValidateSma()
{
int yearSma = 0, monthSma = 0, daySma = 0;
// CLM_GetSmaDate 在老版本 SDK 中可能不存在 | CLM_GetSmaDate may not exist in older SDK
try
{
if (!NativeMethods.CLM_GetSmaDate(ref monthSma, ref daySma, ref yearSma))
{
_logger.Error(new InvalidOperationException("CLM_GetSmaDate"), "检查SMA失败 | SMA check failed");
return CreateFailureResult("检查SMA失败 | SMA check failed");
}
}
catch (EntryPointNotFoundException)
{
_logger.Warn("CLM_GetSmaDate 入口点不存在,跳过SMA验证(SDK版本较旧)| CLM_GetSmaDate entry point not found, skipping SMA validation (older SDK version)");
return null;
}
_logger.Info("CLM_GetSmaDate 成功: {Year}/{Month}/{Day} | CLM_GetSmaDate success: {Year}/{Month}/{Day}", yearSma, monthSma, daySma);
// 获取软件版本信息 | Get software version information
var version = Assembly.GetExecutingAssembly().GetName().Version;
int major = version?.Major ?? 0;
int minor = version?.Minor ?? 0;
// SMA 年份 < 软件主版本号 → 失败 | SMA year < software major version → failure
if (yearSma < major)
{
string msg = $"CLMS授权中SMA年份{yearSma}小于软件主版本号(年份){major},请联系海克斯康升级许可 | SMA year {yearSma} is less than software major version {major}, please contact Hexagon to upgrade license";
_logger.Error(new InvalidOperationException("SMA验证失败"), msg);
return CreateFailureResult(msg);
}
// SMA 年份 == 软件主版本号时,校验季度 | When SMA year == software major version, validate quarter
if (yearSma == major)
{
try
{
DateTime smaDate = new DateTime(yearSma, monthSma, daySma);
int smaQuarter = (smaDate.Month - 1) / 3 + 1;
if (minor > smaQuarter)
{
string msg = $"CLMS授权日期{yearSma}/{monthSma}/{daySma}属于{yearSma}年第{smaQuarter}季度,不支持当前{major}年第{minor}季度的软件版本 | SMA date {yearSma}/{monthSma}/{daySma} is in Q{smaQuarter} of {yearSma}, does not support current Q{minor} of {major} software version";
_logger.Error(new InvalidOperationException("SMA季度验证失败"), msg);
return CreateFailureResult(msg);
}
}
catch (Exception ex)
{
string msg = $"SMA授权日期{yearSma}/{monthSma}/{daySma}不合法 | SMA date {yearSma}/{monthSma}/{daySma} is invalid: {ex.Message}";
_logger.Error(ex, msg);
return CreateFailureResult(msg);
}
}
_smaDate = new DateTime(yearSma, monthSma, daySma);
_logger.Info("SMA校验成功,SMA有效期至{Year}/{Month}/{Day} | SMA validation successful, valid until {Year}/{Month}/{Day}", yearSma, monthSma, daySma);
return null;
}
/// <summary>
/// 创建失败结果 | Create failure result
/// </summary>
/// <param name="message">失败消息 | Failure message</param>
/// <returns>授权检查失败结果 | License check failure result</returns>
private LicenseCheckResult CreateFailureResult(string message)
{
IsAuthorized = false;
WriteLicenseStateToConfig(LicenseState.Fail);
_logger.Info("授权检查完成 | Authorization check completed: Mode={Mode}, State={State}, Expiration={Expiration}",
(int)LicenseMode,
(int)LicenseState.Fail,
"N/A");
return new LicenseCheckResult(
isAuthorized: false,
message: message,
licenseMode: LicenseMode,
moduleId: _config.ModuleId,
expirationDate: null,
smaDate: _smaDate,
floatingLicenseIp: _floatingLicenseIp,
floatingLicensePort: _floatingLicensePort);
}
/// <summary>
/// 写入授权状态到配置文件 | Write license state to configuration file
/// </summary>
/// <param name="state">授权状态 | License state</param>
private void WriteLicenseStateToConfig(LicenseState state)
{
try
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var settings = config.AppSettings.Settings;
if (settings["License:LicenseState"] == null)
{
settings.Add("License:LicenseState", ((int)state).ToString());
}
else
{
settings["License:LicenseState"].Value = ((int)state).ToString();
}
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
_logger.Info("授权状态已写入配置: {State} | License state written to config: {State}", (int)state);
}
catch (Exception ex)
{
_logger.Error(ex, "写入授权状态到配置失败 | Failed to write license state to config");
}
}
}
}