XP.Common 类库中新增授权管理(License Management)功能模块,支持两种授权模式:CLMS 正式授权和临时测试模式。开发统一的授权服务接口,并在主项目中完成集成。

This commit is contained in:
QI Mingxuan
2026-05-15 15:50:35 +08:00
parent ad719d157b
commit 94f0649af8
21 changed files with 1153 additions and 1564 deletions
Binary file not shown.
-43
View File
@@ -1,43 +0,0 @@
using System;
using System.IO;
namespace XP.Common.Configs
{
/// <summary>
/// Serilog日志配置实体(从App.config读取)
/// </summary>
public class SerilogConfig
{
/// <summary>
/// 日志输出根路径(默认:AppData/Files/Logs
/// </summary>
public string LogPath { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Files", "Logs");
/// <summary>
/// 最低日志级别(Debug/Info/Warn/Error/Fatal
/// </summary>
public string MinimumLevel { get; set; } = "Info";
/// <summary>
/// 是否输出到控制台(调试环境=true,生产环境=false
/// </summary>
public bool EnableConsole { get; set; } = true;
/// <summary>
/// 日志文件分割规则(Day/Month/Hour/Size
/// </summary>
public string RollingInterval { get; set; } = "Day";
/// <summary>
/// 单个日志文件最大大小(MB,仅Size分割时生效)
/// </summary>
public long FileSizeLimitMB { get; set; } = 100;
/// <summary>
/// 保留日志文件数量(默认30天)
/// </summary>
public int RetainedFileCountLimit { get; set; } = 30;
}
}
-56
View File
@@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XP.Common.Configs
{
/// <summary>
/// SQLite 配置实体
/// </summary>
public class SqliteConfig
{
/// <summary>
/// 数据库文件路径
/// </summary>
public string DbFilePath { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Files", "Data", "XP.db");
/// <summary>
/// 连接超时时间(秒,默认30
/// </summary>
public int ConnectionTimeout { get; set; } = 30;
/// <summary>
/// 数据库不存在时是否自动创建(默认true)
/// </summary>
public bool CreateIfNotExists { get; set; } = true;
/// <summary>
/// 是否启用 WAL 模式(提升并发性能,默认true)
/// </summary>
public bool EnableWalMode { get; set; } = true;
/// <summary>
/// 是否开启日志记录(记录所有SQL操作,默认false)
/// </summary>
public bool EnableSqlLogging { get; set; } = false;
/// <summary>
/// 获取SQLite连接字符串
/// </summary>
public string GetConnectionString()
{
var builder = new Microsoft.Data.Sqlite.SqliteConnectionStringBuilder
{
DataSource = DbFilePath,
Cache = Microsoft.Data.Sqlite.SqliteCacheMode.Default,
DefaultTimeout = ConnectionTimeout
};
return builder.ToString();
}
}
}
-91
View File
@@ -1,91 +0,0 @@
using System.Configuration;
using XP.Common.Configs;
using XP.Common.Dump.Configs;
namespace XP.Common.Helpers
{
/// <summary>
/// 通用配置加载工具(读取App.config)
/// </summary>
public static class ConfigLoader
{
/// <summary>
/// 加载Serilog配置
/// </summary>
public static SerilogConfig LoadSerilogConfig()
{
var config = new SerilogConfig();
var logPath = ConfigurationManager.AppSettings["Serilog:LogPath"];
if (!string.IsNullOrEmpty(logPath)) config.LogPath = logPath;
var minLevel = ConfigurationManager.AppSettings["Serilog:MinimumLevel"];
if (!string.IsNullOrEmpty(minLevel)) config.MinimumLevel = minLevel;
var enableConsole = ConfigurationManager.AppSettings["Serilog:EnableConsole"];
if (bool.TryParse(enableConsole, out var console)) config.EnableConsole = console;
var rollingInterval = ConfigurationManager.AppSettings["Serilog:RollingInterval"];
if (!string.IsNullOrEmpty(rollingInterval)) config.RollingInterval = rollingInterval;
var fileSize = ConfigurationManager.AppSettings["Serilog:FileSizeLimitMB"];
if (long.TryParse(fileSize, out var size)) config.FileSizeLimitMB = size;
var retainCount = ConfigurationManager.AppSettings["Serilog:RetainedFileCountLimit"];
if (int.TryParse(retainCount, out var count)) config.RetainedFileCountLimit = count;
return config;
}
/// <summary>
/// 加载SQLite配置
/// </summary>
public static SqliteConfig LoadSqliteConfig()
{
var config = new SqliteConfig();
var dbPath = ConfigurationManager.AppSettings["Sqlite:DbFilePath"];
if (!string.IsNullOrEmpty(dbPath)) config.DbFilePath = dbPath;
var timeout = ConfigurationManager.AppSettings["Sqlite:ConnectionTimeout"];
if (int.TryParse(timeout, out var t)) config.ConnectionTimeout = t;
var createIfNotExists = ConfigurationManager.AppSettings["Sqlite:CreateIfNotExists"];
if (bool.TryParse(createIfNotExists, out var c)) config.CreateIfNotExists = c;
var enableWal = ConfigurationManager.AppSettings["Sqlite:EnableWalMode"];
if (bool.TryParse(enableWal, out var w)) config.EnableWalMode = w;
var enableSqlLog = ConfigurationManager.AppSettings["Sqlite:EnableSqlLogging"];
if (bool.TryParse(enableSqlLog, out var l)) config.EnableSqlLogging = l;
return config;
}
/// <summary>
/// 加载 Dump 配置 | Load Dump configuration
/// </summary>
public static DumpConfig LoadDumpConfig()
{
var config = new DumpConfig();
var storagePath = ConfigurationManager.AppSettings["Dump:StoragePath"];
if (!string.IsNullOrEmpty(storagePath)) config.StoragePath = storagePath;
var enableScheduled = ConfigurationManager.AppSettings["Dump:EnableScheduledDump"];
if (bool.TryParse(enableScheduled, out var enabled)) config.EnableScheduledDump = enabled;
var interval = ConfigurationManager.AppSettings["Dump:ScheduledIntervalMinutes"];
if (int.TryParse(interval, out var min)) config.ScheduledIntervalMinutes = min;
var sizeLimit = ConfigurationManager.AppSettings["Dump:MiniDumpSizeLimitMB"];
if (long.TryParse(sizeLimit, out var size)) config.MiniDumpSizeLimitMB = size;
var retentionDays = ConfigurationManager.AppSettings["Dump:RetentionDays"];
if (int.TryParse(retentionDays, out var days)) config.RetentionDays = days;
return config;
}
}
}
+102
View File
@@ -0,0 +1,102 @@
using System.Configuration;
namespace XP.Common.License.Configs
{
/// <summary>
/// 授权配置加载器,从 App.config 读取授权相关配置项 | License configuration loader, reads license-related configuration from App.config
/// </summary>
public static class ConfigLoader
{
/// <summary>
/// 配置键前缀 | Configuration key prefix
/// </summary>
private const string KeyPrefix = "License:";
/// <summary>
/// LicenseMode 有效值集合 | Valid values for LicenseMode
/// </summary>
private static readonly int[] ValidLicenseModes = { 0, 885 };
/// <summary>
/// LicenseState 有效值集合 | Valid values for LicenseState
/// </summary>
private static readonly int[] ValidLicenseStates = { 10, 20 };
/// <summary>
/// 从 App.config 加载授权配置 | Load license configuration from App.config
/// </summary>
/// <returns>授权配置实体,缺失或无效配置项使用默认值 | License configuration entity, uses default values for missing or invalid items</returns>
public static LicenseConfig LoadLicenseConfig()
{
var config = new LicenseConfig();
// 加载 LicenseMode | Load LicenseMode
var licenseModeStr = ConfigurationManager.AppSettings[KeyPrefix + "LicenseMode"];
if (int.TryParse(licenseModeStr, out var licenseMode) && IsValidLicenseMode(licenseMode))
{
config.LicenseMode = licenseMode;
}
// 加载 ModuleId | Load ModuleId
var moduleIdStr = ConfigurationManager.AppSettings[KeyPrefix + "ModuleId"];
if (ushort.TryParse(moduleIdStr, out var moduleId) && IsValidModuleId(moduleId))
{
config.ModuleId = moduleId;
}
// 加载 UseSma | Load UseSma
var useSmaStr = ConfigurationManager.AppSettings[KeyPrefix + "UseSma"];
if (bool.TryParse(useSmaStr, out var useSma))
{
config.UseSma = useSma;
}
// 加载 LicenseState | Load LicenseState
var licenseStateStr = ConfigurationManager.AppSettings[KeyPrefix + "LicenseState"];
if (int.TryParse(licenseStateStr, out var licenseState) && IsValidLicenseState(licenseState))
{
config.LicenseState = licenseState;
}
return config;
}
/// <summary>
/// 验证 LicenseMode 值是否有效 | Validate whether LicenseMode value is valid
/// </summary>
/// <param name="value">待验证的值 | Value to validate</param>
/// <returns>true 表示有效,false 表示无效 | true if valid, false if invalid</returns>
private static bool IsValidLicenseMode(int value)
{
foreach (var valid in ValidLicenseModes)
{
if (value == valid) return true;
}
return false;
}
/// <summary>
/// 验证 ModuleId 值是否在有效范围内 | Validate whether ModuleId value is within valid range
/// </summary>
/// <param name="value">待验证的值 | Value to validate</param>
/// <returns>true 表示有效,false 表示无效 | true if valid, false if invalid</returns>
private static bool IsValidModuleId(ushort value)
{
return value >= 1 && value <= 65535;
}
/// <summary>
/// 验证 LicenseState 值是否有效 | Validate whether LicenseState value is valid
/// </summary>
/// <param name="value">待验证的值 | Value to validate</param>
/// <returns>true 表示有效,false 表示无效 | true if valid, false if invalid</returns>
private static bool IsValidLicenseState(int value)
{
foreach (var valid in ValidLicenseStates)
{
if (value == valid) return true;
}
return false;
}
}
}
@@ -0,0 +1,28 @@
namespace XP.Common.License.Configs
{
/// <summary>
/// 授权配置实体 | License configuration entity
/// </summary>
public class LicenseConfig
{
/// <summary>
/// 授权模式 | License mode
/// </summary>
public int LicenseMode { get; set; } = 0;
/// <summary>
/// 模块ID | Module ID
/// </summary>
public ushort ModuleId { get; set; } = 4;
/// <summary>
/// 是否使用SMA | Whether to use SMA
/// </summary>
public bool UseSma { get; set; } = false;
/// <summary>
/// 授权状态 | License state
/// </summary>
public int LicenseState { get; set; } = 20;
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace XP.Common.License.Enums;
/// <summary>
/// 授权模式枚举 | License mode enumeration
/// </summary>
public enum LicenseMode : int
{
/// <summary>
/// CLMS 正式授权 | CLMS formal authorization
/// </summary>
Clms = 0,
/// <summary>
/// 临时测试模式(15分钟)| Temporary test mode (15 minutes)
/// </summary>
TemporaryTest = 885
}
+17
View File
@@ -0,0 +1,17 @@
namespace XP.Common.License.Enums;
/// <summary>
/// 授权状态枚举 | License state enumeration
/// </summary>
public enum LicenseState : int
{
/// <summary>
/// 授权成功 | Authorization successful
/// </summary>
Success = 10,
/// <summary>
/// 授权失败 | Authorization failed
/// </summary>
Fail = 20
}
@@ -0,0 +1,91 @@
using System;
using XP.Common.License.Enums;
namespace XP.Common.License.Implementations
{
/// <summary>
/// 授权检查结果(不可变)| License check result (immutable)
/// </summary>
public sealed class LicenseCheckResult
{
/// <summary>
/// 消息最大长度 | Maximum message length
/// </summary>
private const int MaxMessageLength = 512;
/// <summary>
/// 是否授权成功 | Whether authorization is successful
/// </summary>
public bool IsAuthorized { get; }
/// <summary>
/// 结果消息(最大512字符)| Result message (maximum 512 characters)
/// </summary>
public string Message { get; }
/// <summary>
/// 授权模式 | License mode
/// </summary>
public LicenseMode LicenseMode { get; }
/// <summary>
/// 模块ID | Module ID
/// </summary>
public ushort ModuleId { get; }
/// <summary>
/// 授权到期日期 | License expiration date
/// </summary>
public DateTime? ExpirationDate { get; }
/// <summary>
/// SMA到期日期 | SMA expiration date
/// </summary>
public DateTime? SmaDate { get; }
/// <summary>
/// 浮动许可IP地址 | Floating license IP address
/// </summary>
public string? FloatingLicenseIp { get; }
/// <summary>
/// 浮动许可端口 | Floating license port
/// </summary>
public string? FloatingLicensePort { get; }
/// <summary>
/// 构造函数 | Constructor
/// </summary>
/// <param name="isAuthorized">是否授权成功 | Whether authorization is successful</param>
/// <param name="message">结果消息 | Result message</param>
/// <param name="licenseMode">授权模式 | License mode</param>
/// <param name="moduleId">模块ID | Module ID</param>
/// <param name="expirationDate">授权到期日期 | License expiration date</param>
/// <param name="smaDate">SMA到期日期 | SMA expiration date</param>
/// <param name="floatingLicenseIp">浮动许可IP地址 | Floating license IP address</param>
/// <param name="floatingLicensePort">浮动许可端口 | Floating license port</param>
public LicenseCheckResult(
bool isAuthorized,
string message,
LicenseMode licenseMode,
ushort moduleId,
DateTime? expirationDate,
DateTime? smaDate,
string? floatingLicenseIp,
string? floatingLicensePort)
{
IsAuthorized = isAuthorized;
Message = string.IsNullOrEmpty(message)
? string.Empty
: message.Length > MaxMessageLength
? message[..MaxMessageLength]
: message;
LicenseMode = licenseMode;
ModuleId = moduleId;
ExpirationDate = expirationDate;
SmaDate = smaDate;
FloatingLicenseIp = floatingLicenseIp;
FloatingLicensePort = floatingLicensePort;
}
}
}
@@ -0,0 +1,595 @@
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");
}
}
}
}
@@ -0,0 +1,67 @@
using System;
using XP.Common.License.Enums;
using XP.Common.License.Implementations;
namespace XP.Common.License.Interfaces;
/// <summary>
/// 授权服务接口 | License service interface
/// </summary>
public interface ILicenseService
{
/// <summary>
/// 执行授权检查 | Perform authorization check
/// </summary>
/// <returns>授权检查结果 | License check result</returns>
LicenseCheckResult CheckAuthorization();
/// <summary>
/// 当前会话是否已授权 | Whether the current session is authorized
/// </summary>
bool IsAuthorized { get; }
/// <summary>
/// 获取授权到期日期 | Get license expiration date
/// </summary>
/// <returns>授权到期日期,未授权时返回 null | License expiration date, null if not authorized</returns>
DateTime? GetExpirationDate();
/// <summary>
/// 检查模块是否授权 | Check if module is licensed
/// </summary>
/// <param name="moduleId">模块ID | Module ID</param>
/// <returns>模块是否授权 | Whether the module is licensed</returns>
bool IsModuleLicensed(ushort moduleId);
/// <summary>
/// 获取SMA到期日期 | Get SMA expiration date
/// </summary>
/// <returns>SMA到期日期,未启用时返回 null | SMA expiration date, null if not enabled</returns>
DateTime? GetSmaDate();
/// <summary>
/// 当前授权模式 | Current license mode
/// </summary>
LicenseMode LicenseMode { get; }
/// <summary>
/// 临时测试模式超时事件(到期时触发,应用应执行正常关闭流程)| Temporary test mode timeout event (fires when expired, app should perform graceful shutdown)
/// </summary>
event EventHandler TestModeTimeout;
/// <summary>
/// 临时测试模式剩余5分钟警告事件 | Temporary test mode 5-minute warning event
/// </summary>
event EventHandler TestModeWarning5Min;
/// <summary>
/// 临时测试模式剩余1分钟警告事件 | Temporary test mode 1-minute warning event
/// </summary>
event EventHandler TestModeWarning1Min;
/// <summary>
/// 获取临时测试模式剩余时间 | Get remaining time in temporary test mode
/// </summary>
/// <returns>剩余时间(秒),非测试模式返回 0 | Remaining time in seconds, 0 if not in test mode</returns>
int GetRemainingTestTime();
}
+97
View File
@@ -0,0 +1,97 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using XP.Common.License.Enums;
namespace XP.Common.License.Native
{
/// <summary>
/// CLMS SDK 原生方法封装 | CLMS SDK native methods encapsulation
/// </summary>
internal static class NativeMethods
{
/// <summary>
/// 登录验证 | Login verification
/// </summary>
/// <param name="str">验证字符串 | Verification string</param>
/// <returns>TRUE: 成功 | TRUE: Success; FALSE: 失败 | FALSE: Failure</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_Login", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern bool CLM_Login(StringBuilder str);
/// <summary>
/// 退出登录 | Logout
/// </summary>
/// <returns>TRUE: 成功 | TRUE: Success; FALSE: 失败 | FALSE: Failure</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_Logout", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool CLM_Logout();
/// <summary>
/// 检查许可范围 | Check license scope
/// </summary>
/// <returns>TRUE: 有许可 | TRUE: Has license; FALSE: 无许可 | FALSE: No license</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_Login_Scope", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool CLM_Login_Scope();
/// <summary>
/// 检查模块是否授权 | Check if module is licensed
/// </summary>
/// <param name="mod">模块ID | Module ID</param>
/// <param name="type">类型(暂无定义)| Type (undefined)</param>
/// <returns>TRUE: 模块可用 | TRUE: Module available; FALSE: 模块不可用 | FALSE: Module unavailable</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_ModuleIsLicensed", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool CLM_ModuleIsLicensed(ref ushort mod, ref ushort type);
/// <summary>
/// 获取保修到期日期 | Get warranty expiration date
/// </summary>
/// <param name="mon">月份 | Month</param>
/// <param name="day">日期 | Day</param>
/// <param name="year">年份 | Year</param>
/// <returns>TRUE: 成功 | TRUE: Success; FALSE: 失败 | FALSE: Failure</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_GetWarrantyExpiration", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool CLM_GetWarrantyExpiration(ref int mon, ref int day, ref int year);
/// <summary>
/// 获取浮动许可的IP地址和端口 | Get floating license IP and port
/// </summary>
/// <param name="ip">IP地址 | IP address</param>
/// <param name="port">端口 | Port</param>
/// <returns>TRUE: 成功 | TRUE: Success; FALSE: 失败 | FALSE: Failure</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_GetIP", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern bool CLM_GetIP(StringBuilder ip, StringBuilder port);
/// <summary>
/// 获取错误信息 | Get error message
/// </summary>
/// <param name="error">错误信息 | Error message</param>
/// <returns>TRUE: 成功 | TRUE: Success; FALSE: 失败 | FALSE: Failure</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_GetError", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern bool CLM_GetError(StringBuilder error);
/// <summary>
/// 检查系统时间 | Check system time
/// </summary>
/// <returns>TRUE: 系统时间正常 | TRUE: System time normal; FALSE: 系统时间异常 | FALSE: System time anomaly</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_CheckSystemTime", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool CLM_CheckSystemTime();
/// <summary>
/// 获取SmartService信息 | Get SmartService information
/// </summary>
/// <param name="ControllerId">控制器ID | Controller ID</param>
/// <param name="UserName">用户名 | User name</param>
/// <returns>TRUE: 成功 | TRUE: Success; FALSE: 失败 | FALSE: Failure</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_SmartService", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern bool CLM_SmartService(StringBuilder ControllerId, StringBuilder UserName);
/// <summary>
/// 获取SMA日期 | Get SMA date
/// </summary>
/// <param name="mon">月份 | Month</param>
/// <param name="day">日期 | Day</param>
/// <param name="year">年份 | Year</param>
/// <returns>TRUE: 成功 | TRUE: Success; FALSE: 失败 | FALSE: Failure</returns>
[DllImport("MORCODE.dll", EntryPoint = "CLM_GetSmaDate", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool CLM_GetSmaDate(ref int mon, ref int day, ref int year);
}
}
+11 -2
View File
@@ -4,7 +4,8 @@ using Prism.Modularity;
using XP.Common.Dump.Configs;
using XP.Common.Dump.Implementations;
using XP.Common.Dump.Interfaces;
using XP.Common.Helpers;
using XP.Common.License.Configs;
using XP.Common.License.Interfaces;
using XP.Common.Localization.Configs;
using XP.Common.Localization.Extensions;
using XP.Common.Localization.Implementations;
@@ -12,6 +13,8 @@ using XP.Common.Localization.Interfaces;
using XP.Common.Logging.Interfaces;
using XP.Common.PdfViewer.Implementations;
using XP.Common.PdfViewer.Interfaces;
using DumpConfigLoader = XP.Common.Dump.Configs.ConfigLoader;
using LicenseConfigLoader = XP.Common.License.Configs.ConfigLoader;
namespace XP.Common.Module
{
@@ -65,7 +68,7 @@ namespace XP.Common.Module
containerRegistry.RegisterSingleton<ILocalizationService, ResxLocalizationService>();
// 注册 Dump 配置为单例(通过工厂方法加载)| Register Dump config as singleton (via factory method)
containerRegistry.RegisterSingleton<DumpConfig>(() => ConfigLoader.LoadDumpConfig());
containerRegistry.RegisterSingleton<DumpConfig>(() => DumpConfigLoader.LoadDumpConfig());
// 注册 Dump 服务为单例 | Register Dump service as singleton
containerRegistry.RegisterSingleton<IDumpService, DumpService>();
@@ -75,6 +78,12 @@ namespace XP.Common.Module
// 注册 PDF 查看服务为单例 | Register PDF viewer service as singleton
containerRegistry.RegisterSingleton<IPdfViewerService, PdfViewerService>();
// 注册授权配置为单例(通过工厂方法加载)| Register license config as singleton (via factory method)
containerRegistry.RegisterSingleton<LicenseConfig>(() => LicenseConfigLoader.LoadLicenseConfig());
// 注册授权服务为单例 | Register license service as singleton
containerRegistry.RegisterSingleton<ILicenseService, XP.Common.License.Implementations.LicenseService>();
}
}
}
Binary file not shown.
+3
View File
@@ -62,4 +62,7 @@
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Remove="License\Demo.cs" />
</ItemGroup>
</Project>
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -5,7 +5,7 @@ using Serilog;
using System;
using System.Resources;
using System.Windows;
using XP.Common.Configs;
using XP.Common.Logging.Configs;
using XP.Common.Localization;
using XP.Common.Localization.Interfaces;
using XP.Common.Logging;
@@ -118,7 +118,7 @@ namespace XP.Hardware.PLC.Sentry
};
// 加载并初始化 Serilog | Load and initialize Serilog
SerilogConfig serilogConfig = XP.Common.Helpers.ConfigLoader.LoadSerilogConfig();
SerilogConfig serilogConfig = XP.Common.Logging.Configs.ConfigLoader.LoadSerilogConfig();
SerilogInitializer.Initialize(serilogConfig);
_logger.Information("PLC Sentry Monitor 启动开始 | PLC Sentry Monitor startup started");
+3 -3
View File
@@ -2,11 +2,11 @@ using System;
using System.Windows;
using Prism.Container.DryIoc;
using Prism.Ioc;
using XP.Common.Configs;
using XP.Common.Dump.Configs;
using XP.Common.Dump.Implementations;
using XP.Common.Dump.Interfaces;
using XP.Common.Helpers;
using XP.Common.Logging.Configs;
using DumpConfigLoader = XP.Common.Dump.Configs.ConfigLoader;
using XP.Common.Localization.Configs;
using XP.Common.Localization.Extensions;
using XP.Common.Localization;
@@ -71,7 +71,7 @@ namespace XP.Scan
containerRegistry.RegisterSingleton<ILocalizationService, ResxLocalizationService>();
// Dump 服务
containerRegistry.RegisterSingleton<DumpConfig>(() => ConfigLoader.LoadDumpConfig());
containerRegistry.RegisterSingleton<DumpConfig>(() => DumpConfigLoader.LoadDumpConfig());
containerRegistry.RegisterSingleton<IDumpService, DumpService>();
// 注册 XPScanView 用于区域导航
@@ -5,7 +5,7 @@ using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using XP.Common.Configs;
using XP.Common.Database.Configs;
using XP.Common.Database.Implementations;
using XP.Common.Database.Interfaces;
using XP.Common.Logging.Interfaces;
+5
View File
@@ -11,6 +11,11 @@
<add key="UserManual" value="UserManual.pdf" />
<add key="DeviceId" value="PlanerCT001" />
<!-- 授权配置 | License configuration -->
<add key="License:LicenseMode" value="0" />
<add key="License:ModuleId" value="4" />
<add key="License:UseSma" value="false" />
<add key="License:LicenseState" value="20" />
<!-- Serilog日志配置 -->
<add key="Serilog:LogPath" value="D:\XplorePlane\Logs" />
+113 -5
View File
@@ -10,14 +10,18 @@ using System.Threading;
using System.Windows;
using Telerik.Windows.Controls;
using XP.Camera;
using XP.Common.Configs;
using XP.Common.Database.Configs;
using XP.Common.Database.Implementations;
using XP.Common.Database.Interfaces;
using XP.Common.Dump.Configs;
using XP.Common.Dump.Implementations;
using XP.Common.Dump.Interfaces;
using XP.Common.GeneralForm.Views;
using XP.Common.Helpers;
using XP.Common.Logging.Configs;
using DumpConfigLoader = XP.Common.Dump.Configs.ConfigLoader;
using LoggingConfigLoader = XP.Common.Logging.Configs.ConfigLoader;
using DatabaseConfigLoader = XP.Common.Database.Configs.ConfigLoader;
using XP.Common.License.Interfaces;
using XP.Common.Localization.Configs;
using XP.Common.Localization.Extensions;
using XP.Common.Localization.Implementations;
@@ -110,7 +114,7 @@ namespace XplorePlane
private void ConfigureLogging()
{
// 加载 Serilog 配置 | Load Serilog configuration
SerilogConfig serilogConfig = ConfigLoader.LoadSerilogConfig();
SerilogConfig serilogConfig = LoggingConfigLoader.LoadSerilogConfig();
// 初始化 Serilog(全局唯一)| Initialize Serilog (global singleton)
SerilogInitializer.Initialize(serilogConfig);
@@ -315,6 +319,13 @@ namespace XplorePlane
_modulesInitialized = true;
}
// 执行授权检查 | Perform license authorization check
if (!PerformLicenseCheck())
{
Application.Current.Shutdown();
return null;
}
var shell = Container.Resolve<MainWindow>();
// 主窗口加载完成后再连接相机,确保所有模块和原生 DLL 已完成初始化
@@ -353,6 +364,103 @@ namespace XplorePlane
return shell;
}
/// <summary>
/// 执行授权检查,授权失败时显示错误消息 | Perform license check, show error message on failure
/// </summary>
/// <returns>授权是否成功 | Whether authorization succeeded</returns>
private bool PerformLicenseCheck()
{
try
{
var licenseService = Container.Resolve<ILicenseService>();
var result = licenseService.CheckAuthorization();
if (!result.IsAuthorized)
{
Log.Error("授权检查失败 | Authorization check failed: {Message}", result.Message);
MessageBox.Show(
result.Message,
"授权失败 | Authorization Failed",
MessageBoxButton.OK,
MessageBoxImage.Error);
return false;
}
// 订阅临时测试模式事件 | Subscribe to temporary test mode events
licenseService.TestModeWarning5Min += OnTestModeWarning5Min;
licenseService.TestModeWarning1Min += OnTestModeWarning1Min;
licenseService.TestModeTimeout += OnTestModeTimeout;
// 临时测试模式启动提示 | Temporary test mode startup notification
if (licenseService.LicenseMode == XP.Common.License.Enums.LicenseMode.TemporaryTest)
{
MessageBox.Show(
"当前为临时测试模式,软件将在15分钟后自动关闭。\nCurrently in temporary test mode, the software will automatically shut down after 15 minutes.",
"临时测试模式 | Temporary Test Mode",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
Log.Information("授权检查通过 | Authorization check passed, Mode={Mode}", licenseService.LicenseMode);
return true;
}
catch (Exception ex)
{
Log.Error(ex, "授权检查过程中发生异常 | Exception during authorization check");
MessageBox.Show(
$"授权检查异常 | Authorization check exception: {ex.Message}",
"授权失败 | Authorization Failed",
MessageBoxButton.OK,
MessageBoxImage.Error);
return false;
}
}
/// <summary>
/// 处理临时测试模式剩余5分钟警告 | Handle temporary test mode 5-minute warning
/// </summary>
private void OnTestModeWarning5Min(object? sender, EventArgs e)
{
Application.Current.Dispatcher.Invoke(() =>
{
Log.Warning("临时测试模式剩余5分钟 | Temporary test mode: 5 minutes remaining");
MessageBox.Show(
"临时测试模式将在5分钟后到期,请尽快保存您的工作。\nTemporary test mode will expire in 5 minutes, please save your work.",
"测试模式提醒 | Test Mode Reminder",
MessageBoxButton.OK,
MessageBoxImage.Information);
});
}
/// <summary>
/// 处理临时测试模式剩余1分钟警告 | Handle temporary test mode 1-minute warning
/// </summary>
private void OnTestModeWarning1Min(object? sender, EventArgs e)
{
Application.Current.Dispatcher.Invoke(() =>
{
Log.Warning("临时测试模式剩余1分钟 | Temporary test mode: 1 minute remaining");
MessageBox.Show(
"临时测试模式将在1分钟后到期,软件即将关闭,请立即保存您的工作。\nTemporary test mode will expire in 1 minute, the software will shut down soon. Please save your work immediately.",
"测试模式即将到期 | Test Mode Expiring",
MessageBoxButton.OK,
MessageBoxImage.Warning);
});
}
/// <summary>
/// 处理临时测试模式超时事件(执行正常关闭流程)| Handle temporary test mode timeout event (perform graceful shutdown)
/// </summary>
private void OnTestModeTimeout(object? sender, EventArgs e)
{
Application.Current.Dispatcher.Invoke(() =>
{
Log.Warning("临时测试模式已超时,执行正常关闭流程 | Temporary test mode timed out, performing graceful shutdown");
// 使用正常关闭流程,确保资源正确释放 | Use graceful shutdown to ensure proper resource release
Application.Current.MainWindow?.Close();
});
}
/// <summary>
/// 在主线程上检索并连接导航相机。
/// pylon SDK 要求在主线程(STA)上操作,不能放到后台线程。
@@ -428,14 +536,14 @@ namespace XplorePlane
containerRegistry.Register<XP.Hardware.RaySource.ViewModels.RaySourceOperateViewModel>();
// 注册 SQLite 配置和数据库上下文(FilamentLifetimeService 依赖)
var sqliteConfig = XP.Common.Helpers.ConfigLoader.LoadSqliteConfig();
var sqliteConfig = DatabaseConfigLoader.LoadSqliteConfig();
containerRegistry.RegisterInstance(sqliteConfig);
containerRegistry.RegisterSingleton<IDbContext, SqliteContext>();
// 注册通用模块的服务(本地化、Dump)
containerRegistry.RegisterSingleton<ILocalizationConfig, LocalizationConfig>();
containerRegistry.RegisterSingleton<ILocalizationService, ResxLocalizationService>();
containerRegistry.RegisterSingleton<DumpConfig>(() => XP.Common.Helpers.ConfigLoader.LoadDumpConfig());
containerRegistry.RegisterSingleton<DumpConfig>(() => DumpConfigLoader.LoadDumpConfig());
containerRegistry.RegisterSingleton<IDumpService, DumpService>();
// ── CNC / 矩阵编排 / 测量数据服务(单例)──