Merged PR 82: 授权服务功能合并至开发分支

新增授权服务,XplorePlane 模块 ID 4、零件号 LS950-0071-5-1。
- 支持两种授权模式:CLMS 授权 (0) 和临时测试15分钟模式 (885);
- 支持通过接口查询授权信息。

配置项目如下:
```xml
<appSettings>
  <!-- 授权配置 | License configuration -->
  <add key="License:LicenseMode" value="0" />     <!-- 授权模式:0=CLMS 正式授权,885=临时测试模式 -->
  <add key="License:ModuleId" value="4" />        <!-- 模块 ID,XplorePlane 固定为 4 -->
  <add key="License:UseSma" value="false" />      <!-- 是否启用 SMA 检查 -->
  <add key="License:LicenseState" value="20" />   <!-- 上次授权状态:10=成功,20=失败(运行时由 LicenseService 自动写回)-->
</appSettings>
```

Readme文档详见 `XplorePlane\XP.Common\Documents\License.README.md`。
This commit is contained in:
QI Mingxuan
2026-05-20 10:11:10 +08:00
committed by SONG Tian
67 changed files with 12963 additions and 260 deletions
+9
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" />
@@ -163,6 +168,10 @@
<!-- 运行参数 | Runtime parameters -->
<add key="MotionControl:PollingInterval" value="500" />
<add key="MotionControl:DefaultVelocity" value="500" />
<!-- 射线源与探测器Z轴联动配置 | Source-Detector Z-axis linkage configuration -->
<add key="MotionControl:SourceDetectorZLinkage:Enabled" value="true" />
<add key="MotionControl:SourceDetectorZLinkage:TriggerThreshold" value="1.0" />
<add key="MotionControl:SourceDetectorZLinkage:SpeedPercent" value="100" />
<!-- 报告输出文件夹路径(绝对路径)| Report output directory (absolute path) -->
<add key="Report:OutputDirectory" value="D:\XplorePlane\Report" />
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -2422,6 +2422,7 @@
"Serilog": "4.3.1",
"Serilog.Sinks.Console": "6.1.1",
"Serilog.Sinks.File": "7.0.0",
"XP.Common": "1.0.0",
"XP.ImageProcessing.Core": "1.0.0"
},
"runtime": {
Binary file not shown.
+9 -62
View File
@@ -5,127 +5,88 @@
</sectionGroup>
</configSections>
<appSettings>
<!-- 语言配置 可选值: ZhCN, ZhTW, EnUS| Language Configuration -->
<add key="Language" value="ZhCN" />
<add key="XpData:RootPath" value="D:\XPData" />
<add key="UserManual" value="UserManual.pdf" />
<add key="DeviceId" value="PlanerCT001" />
<!-- Serilog日志配置 -->
<add key="License:LicenseMode" value="0" />
<add key="License:ModuleId" value="4" />
<add key="License:UseSma" value="false" />
<add key="License:LicenseState" value="10" />
<add key="Serilog:LogPath" value="D:\XplorePlane\Logs" />
<add key="Serilog:MinimumLevel" value="Debug" />
<add key="Serilog:EnableConsole" value="true" />
<add key="Serilog:RollingInterval" value="Day" />
<add key="Serilog:FileSizeLimitMB" value="100" />
<add key="Serilog:RetainedFileCountLimit" value="365" />
<!-- 数据库SQLite配置 -->
<add key="Sqlite:DbFilePath" value="D:\XplorePlane\DataBase\XP.db" />
<add key="Sqlite:ConnectionTimeout" value="10" />
<add key="Sqlite:CreateIfNotExists" value="true" />
<!-- 是否启用SQLite WAL模式(提升并发读写性能,默认true) -->
<add key="Sqlite:EnableWalMode" value="true" />
<!-- 是否开启SQL操作日志(记录所有执行的SQL语句,默认false -->
<add key="Sqlite:EnableSqlLogging" value="false" />
<!-- 射线源配置 -->
<!-- 射线源类型 | Ray Source Type -->
<!-- 可选值: Comet225 | Available values: Comet225 -->
<add key="RaySource:SourceType" value="Comet225" />
<add key="RaySource:SerialNumber" value="SN08602861" />
<add key="RaySource:TotalLifeThreshold" value="10" />
<!-- PVI通讯参数 | PVI Communication Parameters -->
<add key="RaySource:PlcIpAddress" value="192.168.12.10" />
<add key="RaySource:PlcPort" value="11159" />
<add key="RaySource:PortNumber" value="11" />
<add key="RaySource:StationNumber" value="1" />
<add key="RaySource:CpuName" value="cpu" />
<add key="RaySource:ConnectionTimeout" value="30000" />
<!-- 硬件参数范围 | Hardware Parameter Ranges -->
<add key="RaySource:MinVoltage" value="20" />
<add key="RaySource:MaxVoltage" value="225" />
<add key="RaySource:MinCurrent" value="10" />
<add key="RaySource:MaxCurrent" value="1440" />
<!-- 外部程序配置 | External Program Configuration -->
<add key="RaySource:AdvanceExePath" value="C:\Program Files (x86)\Feinfocus\FXEControl_3.1.1.65\FXEControl.exe" />
<!-- 操作超时配置 | Operation Timeout Configuration -->
<add key="RaySource:RaySource:InitializationTimeout" value="30000" />
<add key="RaySource:WarmUpTimeout" value="600000" />
<add key="RaySource:StartUpTimeout" value="1800000" />
<add key="RaySource:AutoCenterTimeout" value="1200000" />
<add key="RaySource:FilamentAdjustTimeout" value="1200000" />
<add key="RaySource:GeneralOperationTimeout" value="100000" />
<!-- 探测器配置 -->
<!-- 探测器类型 | Detector Type -->
<!-- 可选值: Varex, IRay, Hamamatsu | Available values: Varex, IRay, Hamamatsu -->
<add key="Detector:Type" value="Varex" />
<!-- 通用配置 | Common Configuration -->
<add key="Detector:IP" value="192.168.1.200" />
<add key="Detector:Port" value="5000" />
<add key="Detector:SavePath" value="D:\XplorePlane\DetectorImages" />
<add key="Detector:AutoSave" value="true" />
<!-- Varex 探测器专属配置 | Varex Detector Specific Configuration -->
<!-- Binning 模式: Bin1x1, Bin2x2, Bin4x4 | Binning mode: Bin1x1, Bin2x2, Bin4x4 -->
<add key="Detector:Varex:BinningMode" value="Bin1x1" />
<!-- 增益模式: Low, High | Gain mode: Low, High -->
<add key="Detector:Varex:GainMode" value="High" />
<!-- 曝光时间(毫秒)| Exposure time (milliseconds) -->
<add key="Detector:Varex:ExposureTime" value="100" />
<!-- ROI 区域 | ROI Region -->
<add key="Detector:Varex:ROI_X" value="0" />
<add key="Detector:Varex:ROI_Y" value="0" />
<add key="Detector:Varex:ROI_Width" value="2880" />
<add key="Detector:Varex:ROI_Height" value="2880" />
<!-- iRay 探测器专属配置 | iRay Detector Specific Configuration -->
<!-- 采集模式: Continuous, SingleFrame | Acquisition mode: Continuous, SingleFrame -->
<add key="Detector:IRay:AcquisitionMode" value="Continuous" />
<!-- 默认增益值 | Default gain value -->
<add key="Detector:IRay:DefaultGain" value="1.0" />
<!-- 校正配置 | Correction Configuration -->
<add key="Detector:Correction:DarkFrameCount" value="10" />
<add key="Detector:Correction:GainFrameCount" value="10" />
<add key="Detector:Correction:SaveCorrectionData" value="true" />
<!-- 操作超时配置 | Operation Timeout Configuration -->
<add key="Detector:InitializationTimeout" value="30000" />
<add key="Detector:AcquisitionTimeout" value="10000" />
<add key="Detector:CorrectionTimeout" value="60000" />
<!-- 主界面实时图像与探测器帧流水线 -->
<add key="MainViewport:RealtimeEnabledDefault" value="true" />
<add key="DetectorPipeline:AcquireQueueCapacity" value="16" />
<add key="DetectorPipeline:ProcessQueueCapacity" value="8" />
<add key="DetectorPipeline:ProcessEveryNFrames" value="1" />
<!-- Dump 配置 | Dump Configuration -->
<add key="Dump:StoragePath" value="D:\XplorePlane\Dump" />
<add key="Dump:EnableScheduledDump" value="false" />
<add key="Dump:ScheduledIntervalMinutes" value="60" />
<add key="Dump:MiniDumpSizeLimitMB" value="100" />
<add key="Dump:RetentionDays" value="7" />
<!-- PLC 配置 | PLC Configuration -->
<add key="Plc:IpAddress" value="192.168.0.1" />
<add key="Plc:Port" value="102" />
<add key="Plc:Rack" value="0" />
<add key="Plc:Slot" value="1" />
<!-- PlcType可选值: S200Smart, S300, S400, S1200, S1500 | PlcType Available values: S200Smart, S300, S400, S1200, S1500 -->
<add key="Plc:PlcType" value="S1200" />
<!-- 数据块配置 | Data Block Configuration -->
<add key="Plc:ReadDbBlock" value="DB31" />
<add key="Plc:WriteDbBlock" value="DB31" />
<add key="Plc:ReadStartAddress" value="0" />
<add key="Plc:ReadLength" value="200" />
<!-- 批量读取周期(毫秒)| Bulk read interval (ms) -->
<add key="Plc:BulkReadIntervalMs" value="250" />
<!-- 超时配置 | Timeout Configuration -->
<add key="Plc:ConnectTimeoutMs" value="3000" />
<add key="Plc:ReadTimeoutMs" value="1000" />
<add key="Plc:WriteTimeoutMs" value="1000" />
<!-- 自动重连 | Auto Reconnection -->
<add key="Plc:bReConnect" value="true" />
<!-- 直线轴配置(单位:mm| Linear axis config (unit: mm) -->
<add key="MotionControl:SourceZ:Min" value="-500" />
<add key="MotionControl:SourceZ:Max" value="500" />
<add key="MotionControl:SourceZ:Origin" value="0" />
@@ -138,7 +99,6 @@
<add key="MotionControl:StageY:Min" value="-150" />
<add key="MotionControl:StageY:Max" value="150" />
<add key="MotionControl:StageY:Origin" value="0" />
<!-- 旋转轴配置(单位:度)| Rotary axis config (unit: degrees) -->
<add key="MotionControl:DetectorSwing:Min" value="-45" />
<add key="MotionControl:DetectorSwing:Max" value="45" />
<add key="MotionControl:DetectorSwing:Origin" value="0" />
@@ -151,46 +111,33 @@
<add key="MotionControl:FixtureRotation:Max" value="90" />
<add key="MotionControl:FixtureRotation:Origin" value="0" />
<add key="MotionControl:FixtureRotation:Enabled" value="false" />
<!-- 几何原点(mm| Geometry origins (mm) -->
<add key="MotionControl:Geometry:SourceZOrigin" value="0" />
<add key="MotionControl:Geometry:DetectorZOrigin" value="600" />
<add key="MotionControl:Geometry:StageRotationCenterZ" value="300" />
<!-- 探测器摆动几何参数(mm| Detector swing geometry parameters (mm) -->
<!-- SwingPivotOffset: Pivot 相对于 DetectorZ 绝对坐标的 Z 方向偏移,正值表示 Pivot 在 DetectorZ_abs 上方 -->
<add key="MotionControl:Geometry:SwingPivotOffset" value="80" />
<!-- SwingRadius: Pivot 到探测器感光面中心的距离,为 0 时退化为无摆动模型 -->
<add key="MotionControl:Geometry:SwingRadius" value="200" />
<!-- 运行参数 | Runtime parameters -->
<add key="MotionControl:PollingInterval" value="500" />
<add key="MotionControl:DefaultVelocity" value="500" />
<!-- 报告输出文件夹路径(绝对路径)| Report output directory (absolute path) -->
<add key="MotionControl:SourceDetectorZLinkage:Enabled" value="true" />
<add key="MotionControl:SourceDetectorZLinkage:TriggerThreshold"
value="1.0" />
<add key="MotionControl:SourceDetectorZLinkage:SpeedPercent"
value="100" />
<add key="Report:OutputDirectory" value="D:\XplorePlane\Report" />
<!-- 报告模板文件路径(相对于应用程序目录或绝对路径)| Template file path (relative to app dir or absolute) -->
<add key="Report:TemplatePath" value="Templates\StandardReportTemplate.json" />
<!-- 输出文件名模式 | File name pattern -->
<add key="Report:FileNamePattern" value="{Date}_{ProductName}_{WorkpieceSN}_{ReportId}" />
<!-- 文件名重复时是否自动累加序号 | Auto-increment suffix when file name duplicates-->
<add key="Report:AutoIncrementOnDuplicate" value="true" />
<!-- 生成后是否自动打开 PDF 阅读器 | Auto-open PDF viewer after generation (true/false) -->
<add key="Report:AutoOpenAfterGenerate" value="false" />
<!-- 默认页面尺寸 | Default page size (A4) -->
<add key="Report:DefaultPageSize" value="A4" />
<!-- 默认页面方向 | Default orientation (Portrait / Landscape) -->
<add key="Report:DefaultOrientation" value="Portrait" />
<!-- 页面边距(mm),有效范围 0-100 | Page margins (mm), valid range 0-100 -->
<add key="Report:MarginTop" value="20" />
<add key="Report:MarginBottom" value="20" />
<add key="Report:MarginLeft" value="20" />
<add key="Report:MarginRight" value="20" />
<!-- 报告中显示的公司名称 | Company name displayed in report -->
<add key="Report:CompanyName" value="海克斯康制造智能技术(青岛)有限公司" />
<!-- 公司 Logo 图片路径(可选,为空则不显示)| Company logo path (optional, empty = no logo) -->
<add key="Report:CompanyLogo" value="Templates\Logo.png" />
<!-- 软件信息配置 | Software Information Configuration -->
<add key="Report:SoftwareName" value="XplorePlane" />
<add key="Report:SoftwareLogo" value="Templates\Logo2.png" />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,46 @@
using System.Configuration;
namespace XP.Common.Database.Configs
{
/// <summary>
/// SQLite 配置加载器,从 App.config 读取数据库相关配置项 | SQLite configuration loader, reads database-related configuration from App.config
/// </summary>
public static class ConfigLoader
{
/// <summary>
/// 配置键前缀 | Configuration key prefix
/// </summary>
private const string KeyPrefix = "Sqlite:";
/// <summary>
/// 从 App.config 加载 SQLite 配置 | Load SQLite configuration from App.config
/// </summary>
/// <returns>SQLite 配置实体,缺失或无效配置项使用默认值 | SQLite configuration entity, uses default values for missing or invalid items</returns>
public static SqliteConfig LoadSqliteConfig()
{
var config = new SqliteConfig();
// 加载数据库文件路径 | Load database file path
var dbPath = ConfigurationManager.AppSettings[KeyPrefix + "DbFilePath"];
if (!string.IsNullOrEmpty(dbPath)) config.DbFilePath = dbPath;
// 加载连接超时时间 | Load connection timeout
var timeout = ConfigurationManager.AppSettings[KeyPrefix + "ConnectionTimeout"];
if (int.TryParse(timeout, out var t) && t > 0) config.ConnectionTimeout = t;
// 加载是否自动创建 | Load create if not exists
var createIfNotExists = ConfigurationManager.AppSettings[KeyPrefix + "CreateIfNotExists"];
if (bool.TryParse(createIfNotExists, out var c)) config.CreateIfNotExists = c;
// 加载是否启用 WAL 模式 | Load enable WAL mode
var enableWal = ConfigurationManager.AppSettings[KeyPrefix + "EnableWalMode"];
if (bool.TryParse(enableWal, out var w)) config.EnableWalMode = w;
// 加载是否开启 SQL 日志 | Load enable SQL logging
var enableSqlLog = ConfigurationManager.AppSettings[KeyPrefix + "EnableSqlLogging"];
if (bool.TryParse(enableSqlLog, out var l)) config.EnableSqlLogging = l;
return config;
}
}
}
@@ -1,46 +1,42 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XP.Common.Configs
namespace XP.Common.Database.Configs
{
/// <summary>
/// SQLite 配置实体
/// SQLite 配置实体(从 App.config 读取)| SQLite configuration entity (loaded from App.config)
/// </summary>
public class SqliteConfig
{
/// <summary>
/// 数据库文件路径
/// 数据库文件路径 | Database file path
/// </summary>
public string DbFilePath { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Files", "Data", "XP.db");
/// <summary>
/// 连接超时时间(秒,默认30
/// 连接超时时间(秒,默认30| Connection timeout (seconds, default 30)
/// </summary>
public int ConnectionTimeout { get; set; } = 30;
/// <summary>
/// 数据库不存在时是否自动创建(默认true)
/// 数据库不存在时是否自动创建(默认true)| Whether to auto-create if not exists (default true)
/// </summary>
public bool CreateIfNotExists { get; set; } = true;
/// <summary>
/// 是否启用 WAL 模式(提升并发性能,默认true)
/// 是否启用 WAL 模式(提升并发性能,默认true)| Whether to enable WAL mode (default true)
/// </summary>
public bool EnableWalMode { get; set; } = true;
/// <summary>
/// 是否开启日志记录(记录所有SQL操作,默认false)
/// 是否开启日志记录(记录所有SQL操作,默认false)| Whether to enable SQL logging (default false)
/// </summary>
public bool EnableSqlLogging { get; set; } = false;
/// <summary>
/// 获取SQLite连接字符串
/// 获取 SQLite 连接字符串 | Get SQLite connection string
/// </summary>
public string GetConnectionString()
{
@@ -7,7 +7,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using XP.Common.Configs;
using XP.Common.Database.Configs;
using XP.Common.Database.Interfaces;
using XP.Common.Database.Models;
using XP.Common.Helpers;
+184
View File
@@ -0,0 +1,184 @@
# 授权服务使用指南 | License Service Usage Guide
## 概述 | Overview
XplorePlane 通过 `XP.Common.License` 命名空间下的 `ILicenseService` 提供统一的授权管理。底层基于海克斯康 CLMSComputational License Management SystemSDK,使用 `MORCODE.dll` 进行许可证校验。
## 产品授权信息 | Product License Information
| 项目 | 值 |
| --- | --- |
| CLMS 模块 IDModule ID | `4` |
| 零件号(Part Number | `LS950-0071-5-1` |
`Module ID` 是 CLMS 在 SDK 中标识本产品的唯一编号,调用 `CLM_ModuleIsLicensed` 时必须传入此值;`Part Number` 是产品在 CLMS 许可发行系统中的物料编号,用于申请、签发、续期许可证。
## 授权模式 | License Modes
`LicenseMode` 枚举定义见 `XP.Common.License.Enums.LicenseMode`
| 模式 | 枚举值 | 数值 | 说明 |
| --- | --- | --- | --- |
| CLMS 正式授权 | `Clms` | `0` | 通过 CLMS SDK 进行完整授权校验,包含登录、许可范围、模块、SMA、到期日期等检查 |
| 临时测试模式 | `TemporaryTest` | `885` | 不调用 CLMS SDK,直接放行 15 分钟,用于研发/测试场景 |
## 配置项 | Configuration Items
授权配置位于主应用 `App.config` 中,键前缀为 `License:`,对应 `XP.Common.License.Configs.LicenseConfig`
```xml
<appSettings>
<!-- 授权配置 | License configuration -->
<add key="License:LicenseMode" value="0" /> <!-- 授权模式:0=CLMS 正式授权,885=临时测试模式 -->
<add key="License:ModuleId" value="4" /> <!-- 模块 IDXplorePlane 固定为 4 -->
<add key="License:UseSma" value="false" /> <!-- 是否启用 SMA 检查 -->
<add key="License:LicenseState" value="20" /> <!-- 上次授权状态:10=成功,20=失败(运行时由 LicenseService 自动写回)-->
</appSettings>
```
| 键 | 类型 | 默认值 | 有效值 | 说明 |
| --- | --- | --- | --- | --- |
| `License:LicenseMode` | int | `0` | `0``885` | 授权模式 |
| `License:ModuleId` | ushort | `4` | `1` ~ `65535` | CLMS 模块 ID |
| `License:UseSma` | bool | `false` | `true``false` | 是否启用 SMA(软件维护协议)校验 |
| `License:LicenseState` | int | `20` | `10``20` | 上次运行时的授权结果,由服务自动维护 |
> 配置中无效或缺失的键会回退到默认值,详见 `XP.Common.License.Configs.ConfigLoader`
## 正式授权流程 | Formal Authorization Flow
`LicenseMode = 0``Clms`)时,`LicenseService.CheckAuthorization()` 依次执行以下步骤:
1. **系统时间检查**(可选):调用 `CLM_CheckSystemTime`,老版本 SDK 缺失此入口点时跳过。
2. **登录验证**:调用 `CLM_Login`(核心步骤,必须存在)。
3. **许可范围检查**:调用 `CLM_Login_Scope`(核心步骤)。
4. **SMA 验证**(可选):仅当 `License:UseSma = true` 时执行。比较 SMA 年份/季度与当前软件主版本号/次版本号,季度不匹配会判定失败。
5. **浮动许可信息**(可选):通过 `CLM_GetIP` 获取 IP 与端口。
6. **错误信息读取**(可选):通过 `CLM_GetError` 拉取 SDK 端错误描述。
7. **模块授权检查**:调用 `CLM_ModuleIsLicensed`,传入 `Module ID = 4`
8. **到期日期获取**:调用 `CLM_GetWarrantyExpiration`,剩余 ≤ 30 天时记录警告。
任意核心步骤失败即视为授权失败,写回 `License:LicenseState = 20`,主应用弹窗提示并退出。
## 临时测试模式 | Temporary Test Mode
`LicenseMode = 885``TemporaryTest`)用于跳过 CLMS 校验,便于离线开发和功能演示。
### 行为 | Behavior
- 不加载 `MORCODE.dll`,不调用任何 CLMS API,授权直接通过。
- 启动后立刻开启 15 分钟(900 秒)倒计时,到期触发 `TestModeTimeout` 事件,主应用应执行优雅关闭。
- 倒计时途中分别在剩余 5 分钟、1 分钟时触发 `TestModeWarning5Min``TestModeWarning1Min` 事件用于提醒。
- 上述三个事件只在临时测试模式下被触发,正式授权(`Clms`)下不会创建计时器。
### 启用方式 | How to Enable
修改主应用 `App.config`
```xml
<add key="License:LicenseMode" value="885" />
```
启动后将看到提示:「当前为临时测试模式,软件将在 15 分钟后自动关闭」。
> **注意**:临时测试模式仅用于研发与内部测试场景,**禁止用于生产或交付**。发布前请务必将 `License:LicenseMode` 还原为 `0`
## 使用示例 | Usage Examples
### 注入并校验 | Inject and Verify
```csharp
using XP.Common.License.Interfaces;
using XP.Common.License.Enums;
public class StartupChecker
{
private readonly ILicenseService _licenseService;
private readonly ILoggerService _logger;
public StartupChecker(ILicenseService licenseService, ILoggerService logger)
{
_licenseService = licenseService ?? throw new ArgumentNullException(nameof(licenseService));
_logger = logger?.ForModule<StartupChecker>() ?? throw new ArgumentNullException(nameof(logger));
}
public bool Run()
{
var result = _licenseService.CheckAuthorization();
if (!result.IsAuthorized)
{
_logger.Error(null, "授权失败:{Message} | License failed: {Message}", result.Message);
return false;
}
// 仅在临时测试模式下订阅倒计时事件 | Subscribe countdown events only in test mode
if (_licenseService.LicenseMode == LicenseMode.TemporaryTest)
{
_licenseService.TestModeWarning5Min += (s, e) => _logger.Warn("临时测试模式剩余 5 分钟");
_licenseService.TestModeWarning1Min += (s, e) => _logger.Warn("临时测试模式剩余 1 分钟");
_licenseService.TestModeTimeout += (s, e) => Application.Current.Shutdown();
}
return true;
}
}
```
### 检查特定模块授权 | Check Module Authorization
```csharp
const ushort XplorePlaneModuleId = 4;
if (!_licenseService.IsModuleLicensed(XplorePlaneModuleId))
{
_logger.Warn("模块 {ModuleId} 未授权 | Module {ModuleId} not licensed", XplorePlaneModuleId);
}
```
### 读取授权信息 | Read License Information
```csharp
DateTime? expiration = _licenseService.GetExpirationDate(); // 授权到期日期
DateTime? sma = _licenseService.GetSmaDate(); // SMA 到期日期
int remaining = _licenseService.GetRemainingTestTime(); // 临时测试剩余秒数;非测试模式返回 -1
```
## 接口与事件 | Interface & Events
`ILicenseService` 提供以下成员(详见 `XP.Common.License.Interfaces.ILicenseService`):
- `LicenseCheckResult CheckAuthorization()`:执行完整授权校验。
- `bool IsAuthorized`:当前会话是否已授权。
- `LicenseMode LicenseMode`:当前授权模式。
- `DateTime? GetExpirationDate()`:授权到期日期。
- `DateTime? GetSmaDate()`SMA 到期日期。
- `bool IsModuleLicensed(ushort moduleId)`:检查指定模块是否被授权。
- `int GetRemainingTestTime()`:临时测试模式剩余秒数。
- 事件:`TestModeWarning5Min``TestModeWarning1Min``TestModeTimeout`
## 常见问题 | FAQ
### 1. 启动时提示 `MORCODE.dll 加载失败` | `Failed to load MORCODE.dll`
确认 `MORCODE.dll` 已随 `ReleaseFiles` 一同部署,并位于主程序同级目录。研发阶段可临时切换到临时测试模式绕过。
### 2. 提示「模块号码 4 不可用」| `Module 4 unavailable`
CLMS 服务器签发的许可证未包含 `Module ID = 4`(零件号 `LS950-0071-5-1`)。请联系海克斯康许可团队确认许可范围。
### 3. SMA 校验失败 | SMA validation failed
SMA 年份必须 ≥ 软件主版本号;同年时 SMA 季度必须 ≥ 软件次版本号。若需临时绕过,可将 `License:UseSma` 设为 `false`,但发布版本仍应保持 SMA 校验启用。
### 4. 授权成功后是否还有倒计时?| Will the countdown still trigger after a successful formal authorization?
不会。倒计时仅在 `LicenseMode = TemporaryTest` 时启动,`Clms` 正式授权下三个事件永远不会被触发。
## 相关文件 | Related Files
- `XP.Common/License/Interfaces/ILicenseService.cs` — 服务接口
- `XP.Common/License/Implementations/LicenseService.cs` — 服务实现
- `XP.Common/License/Configs/LicenseConfig.cs` — 配置实体
- `XP.Common/License/Configs/ConfigLoader.cs` — 配置加载器
- `XP.Common/License/Enums/LicenseMode.cs` — 授权模式枚举
- `XP.Common/License/Enums/LicenseState.cs` — 授权状态枚举
- `XP.Common/License/Native/NativeMethods.cs` — CLMS SDK P/Invoke 封装
- `XplorePlane/App.xaml.cs` — 主应用启动时调用 `PerformLicenseCheck()` 的入口
+46
View File
@@ -0,0 +1,46 @@
using System.Configuration;
namespace XP.Common.Dump.Configs
{
/// <summary>
/// Dump 配置加载器,从 App.config 读取 Dump 相关配置项 | Dump configuration loader, reads dump-related configuration from App.config
/// </summary>
public static class ConfigLoader
{
/// <summary>
/// 配置键前缀 | Configuration key prefix
/// </summary>
private const string KeyPrefix = "Dump:";
/// <summary>
/// 从 App.config 加载 Dump 配置 | Load Dump configuration from App.config
/// </summary>
/// <returns>Dump 配置实体,缺失或无效配置项使用默认值 | Dump configuration entity, uses default values for missing or invalid items</returns>
public static DumpConfig LoadDumpConfig()
{
var config = new DumpConfig();
// 加载存储路径 | Load storage path
var storagePath = ConfigurationManager.AppSettings[KeyPrefix + "StoragePath"];
if (!string.IsNullOrEmpty(storagePath)) config.StoragePath = storagePath;
// 加载是否启用定时触发 | Load enable scheduled dump
var enableScheduled = ConfigurationManager.AppSettings[KeyPrefix + "EnableScheduledDump"];
if (bool.TryParse(enableScheduled, out var enabled)) config.EnableScheduledDump = enabled;
// 加载定时触发间隔 | Load scheduled interval
var interval = ConfigurationManager.AppSettings[KeyPrefix + "ScheduledIntervalMinutes"];
if (int.TryParse(interval, out var min) && min > 0) config.ScheduledIntervalMinutes = min;
// 加载 Mini Dump 文件大小上限 | Load Mini Dump size limit
var sizeLimit = ConfigurationManager.AppSettings[KeyPrefix + "MiniDumpSizeLimitMB"];
if (long.TryParse(sizeLimit, out var size) && size > 0) config.MiniDumpSizeLimitMB = size;
// 加载文件保留天数 | Load retention days
var retentionDays = ConfigurationManager.AppSettings[KeyPrefix + "RetentionDays"];
if (int.TryParse(retentionDays, out var days) && days > 0) config.RetentionDays = days;
return config;
}
}
}
-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);
}
}
+50
View File
@@ -0,0 +1,50 @@
using System.Configuration;
namespace XP.Common.Logging.Configs
{
/// <summary>
/// Serilog 配置加载器,从 App.config 读取日志相关配置项 | Serilog configuration loader, reads logging-related configuration from App.config
/// </summary>
public static class ConfigLoader
{
/// <summary>
/// 配置键前缀 | Configuration key prefix
/// </summary>
private const string KeyPrefix = "Serilog:";
/// <summary>
/// 从 App.config 加载 Serilog 配置 | Load Serilog configuration from App.config
/// </summary>
/// <returns>Serilog 配置实体,缺失或无效配置项使用默认值 | Serilog configuration entity, uses default values for missing or invalid items</returns>
public static SerilogConfig LoadSerilogConfig()
{
var config = new SerilogConfig();
// 加载日志路径 | Load log path
var logPath = ConfigurationManager.AppSettings[KeyPrefix + "LogPath"];
if (!string.IsNullOrEmpty(logPath)) config.LogPath = logPath;
// 加载最低日志级别 | Load minimum level
var minLevel = ConfigurationManager.AppSettings[KeyPrefix + "MinimumLevel"];
if (!string.IsNullOrEmpty(minLevel)) config.MinimumLevel = minLevel;
// 加载是否输出到控制台 | Load enable console
var enableConsole = ConfigurationManager.AppSettings[KeyPrefix + "EnableConsole"];
if (bool.TryParse(enableConsole, out var console)) config.EnableConsole = console;
// 加载日志文件分割规则 | Load rolling interval
var rollingInterval = ConfigurationManager.AppSettings[KeyPrefix + "RollingInterval"];
if (!string.IsNullOrEmpty(rollingInterval)) config.RollingInterval = rollingInterval;
// 加载单个日志文件最大大小 | Load file size limit
var fileSize = ConfigurationManager.AppSettings[KeyPrefix + "FileSizeLimitMB"];
if (long.TryParse(fileSize, out var size) && size > 0) config.FileSizeLimitMB = size;
// 加载保留日志文件数量 | Load retained file count limit
var retainCount = ConfigurationManager.AppSettings[KeyPrefix + "RetainedFileCountLimit"];
if (int.TryParse(retainCount, out var count) && count > 0) config.RetainedFileCountLimit = count;
return config;
}
}
}
@@ -1,43 +1,43 @@
using System;
using System.IO;
namespace XP.Common.Configs
namespace XP.Common.Logging.Configs
{
/// <summary>
/// Serilog日志配置实体(从App.config读取)
/// Serilog 日志配置实体(从 App.config 读取)| Serilog logging configuration entity (loaded from App.config)
/// </summary>
public class SerilogConfig
{
/// <summary>
/// 日志输出根路径(默认:AppData/Files/Logs
/// 日志输出根路径(默认:AppData/Files/Logs| Log output root path (default: AppData/Files/Logs)
/// </summary>
public string LogPath { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Files", "Logs");
/// <summary>
/// 最低日志级别(Debug/Info/Warn/Error/Fatal
/// 最低日志级别(Debug/Info/Warn/Error/Fatal| Minimum log level
/// </summary>
public string MinimumLevel { get; set; } = "Info";
/// <summary>
/// 是否输出到控制台(调试环境=true,生产环境=false
/// 是否输出到控制台(调试环境=true,生产环境=false| Whether to output to console
/// </summary>
public bool EnableConsole { get; set; } = true;
/// <summary>
/// 日志文件分割规则(Day/Month/Hour/Size
/// 日志文件分割规则(Day/Month/Hour/Size| Log file rolling interval
/// </summary>
public string RollingInterval { get; set; } = "Day";
/// <summary>
/// 单个日志文件最大大小(MB,仅Size分割时生效)
/// 单个日志文件最大大小(MB,仅 Size 分割时生效)| Single log file max size (MB)
/// </summary>
public long FileSizeLimitMB { get; set; } = 100;
/// <summary>
/// 保留日志文件数量(默认30天)
/// 保留日志文件数量(默认30天)| Retained file count limit (default 30)
/// </summary>
public int RetainedFileCountLimit { get; set; } = 30;
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
using System;
using Serilog;
using Serilog.Events;
using XP.Common.Configs;
using XP.Common.Logging.Configs;
using XP.Common.Logging.ViewModels;
namespace XP.Common.Logging
+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>
+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" />
+185 -75
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;
@@ -65,7 +69,7 @@ namespace XplorePlane
{
protected override void OnStartup(StartupEventArgs e)
{
// 设置 Telerik Windows11 主题,缩 Ribbon 整体尺寸
// 设置 Telerik Windows11 主题,缩 Ribbon 整体尺寸
StyleManager.ApplicationTheme = new Windows11Theme();
// 强制使用中文 UI,确保 ImageProcessing 库显示中文
@@ -75,10 +79,10 @@ namespace XplorePlane
CultureInfo.DefaultThreadCurrentCulture = zhCN;
CultureInfo.DefaultThreadCurrentUICulture = zhCN;
// 置 Serilog 日志系统
// 置 Serilog 日志系统
ConfigureLogging();
// 获未处的异常
// 获未处的异常
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
DispatcherUnhandledException += OnDispatcherUnhandledException;
@@ -114,24 +118,24 @@ namespace XplorePlane
private void ConfigureLogging()
{
// 加载 Serilog 置 | Load Serilog configuration
SerilogConfig serilogConfig = ConfigLoader.LoadSerilogConfig();
// 加载 Serilog 置 | Load Serilog configuration
SerilogConfig serilogConfig = LoggingConfigLoader.LoadSerilogConfig();
// 初始化 Serilog(全局唯一)| Initialize Serilog (global singleton)
SerilogInitializer.Initialize(serilogConfig);
// 记录应用动日志 | Log application startup
// 记录应用动日志 | Log application startup
Log.Information("========================================");
Log.Information("XplorePlane 应用程序启动");
Log.Information("XplorePlane 应用程庝坯动");
Log.Information("========================================");
}
protected override void OnExit(ExitEventArgs e)
{
Log.Information("========================================");
Log.Information("XplorePlane 应用程退出");
Log.Information("XplorePlane 应用程退出");
Log.Information("========================================");
// 释放全局状态服务资
// 释放全局状思朝务资
try
{
var bootstrapper = AppBootstrapper.Instance;
@@ -139,15 +143,15 @@ namespace XplorePlane
{
var appStateService = bootstrapper.Container.Resolve<IAppStateService>();
appStateService?.Dispose();
Log.Information("全局状态服务资已释放");
Log.Information("全局状思朝务资已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "全局状态服务资释放失败");
Log.Error(ex, "全局状思朝务资释放失败");
}
// 释放射线源资源
// 释放射线溝资溝
try
{
var bootstrapper = AppBootstrapper.Instance;
@@ -155,15 +159,15 @@ namespace XplorePlane
{
var raySourceService = bootstrapper.Container.Resolve<IRaySourceService>();
raySourceService?.Dispose();
Log.Information("射线源资源已释放");
Log.Information("射线溝资溝已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "射线源资源释放失败");
Log.Error(ex, "射线溝资溝释放失败");
}
// 先止导航相机实时采集,释放资源,避免回调死
// 先止导航相机实时采集,释放资溝,靿兝回调死
try
{
var bootstrapper = AppBootstrapper.Instance;
@@ -179,7 +183,7 @@ namespace XplorePlane
Log.Error(ex, "导航相机 ViewModel 释放失败");
}
// 释放导航相机务资
// 释放导航相机务资
try
{
var bootstrapper = AppBootstrapper.Instance;
@@ -187,15 +191,15 @@ namespace XplorePlane
{
var cameraService = bootstrapper.Container.Resolve<ICameraService>();
cameraService?.Dispose();
Log.Information("导航相机务资已释放");
Log.Information("导航相机务资已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "导航相机务资释放失败");
Log.Error(ex, "导航相机务资释放失败");
}
// 释放主界面探测器帧水线资
// 释放主界面探测器帧水线资
try
{
var bootstrapper = AppBootstrapper.Instance;
@@ -203,31 +207,31 @@ namespace XplorePlane
{
var detectorFramePipelineService = bootstrapper.Container.Resolve<IDetectorFramePipelineService>();
detectorFramePipelineService?.Dispose();
Log.Information("主界面探测器帧水线资已释放");
Log.Information("主界面探测器帧水线资已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "主界面探测器帧水线资释放失败");
Log.Error(ex, "主界面探测器帧水线资释放失败");
}
// 释放SQLite数库资 | Release SQLite database resources
// 释放SQLite数库资 | Release SQLite database resources
try
{
var bootstrapper = AppBootstrapper.Instance;
if (bootstrapper != null)
{
var dbContext = bootstrapper.Container.Resolve<IDbContext>(); // 从 Prism 容器获 IDbContext 实例(例)| Get IDbContext instance from Prism container (singleton)
var dbContext = bootstrapper.Container.Resolve<IDbContext>(); // 从 Prism 容器获 IDbContext 实例(例)| Get IDbContext instance from Prism container (singleton)
dbContext?.Dispose();
Log.Information("数库资源已成功释放 | Database resources released successfully");
Log.Information("数库资溝已戝功释放 | Database resources released successfully");
}
}
catch (Exception ex)
{
Log.Error(ex, "数库资释放失败,忽略该错误继续退出 | Database resource release failed, ignoring error and continuing exit");
Log.Error(ex, "数库资释放失败,忽略该错误继续退出 | Database resource release failed, ignoring error and continuing exit");
}
// 释放 PLC 资
// 释放 PLC 资
try
{
var bootstrapper = AppBootstrapper.Instance;
@@ -235,15 +239,15 @@ namespace XplorePlane
{
var plcService = bootstrapper.Container.Resolve<IPlcService>();
plcService?.Dispose();
Log.Information("PLC 资已释放");
Log.Information("PLC 资已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "PLC 资释放失败");
Log.Error(ex, "PLC 资释放失败");
}
// 释放探测器资
// 释放探测器资
try
{
var bootstrapper = AppBootstrapper.Instance;
@@ -251,15 +255,15 @@ namespace XplorePlane
{
var detectorService = bootstrapper.Container.Resolve<IDetectorService>();
detectorService?.Dispose();
Log.Information("探测器资已释放");
Log.Information("探测器资已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "探测器资释放失败");
Log.Error(ex, "探测器资释放失败");
}
// 释放录制务资
// 释放录制务资
try
{
var bootstrapper = AppBootstrapper.Instance;
@@ -267,24 +271,24 @@ namespace XplorePlane
{
var recordingService = bootstrapper.Container.Resolve<IViewportRecordingService>();
recordingService?.Dispose();
Log.Information("录制务资已释放");
Log.Information("录制务资已释放");
}
}
catch (Exception ex)
{
Log.Error(ex, "录制务资释放失败");
Log.Error(ex, "录制务资释放失败");
}
Log.CloseAndFlush();
base.OnExit(e);
}
/// <summary>
/// 处理未捕获的异常
/// 处睆未杕获的异常
/// </summary>
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as Exception;
Log.Fatal(exception, "应用程序发生未处的异常");
Log.Fatal(exception, "应用程庝坑生未处的异常");
MessageBox.Show(
$"A fatal error has occurred:\n\n{exception?.Message}\n\nPlease check the log file for details.",
@@ -294,11 +298,11 @@ namespace XplorePlane
}
/// <summary>
/// 处 UI 线程未获的异常
/// 处 UI 线程未获的异常
/// </summary>
private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
Log.Error(e.Exception, "UI 线程生未处的异常");
Log.Error(e.Exception, "UI 线程生未处的异常");
MessageBox.Show(
$"An error has occurred:\n\n{e.Exception.Message}\n\nPlease check the log file for details.",
@@ -306,7 +310,7 @@ namespace XplorePlane
MessageBoxButton.OK,
MessageBoxImage.Error);
// 标记为已处,防止应用程崩溃
// 标记为已处,防止应用程崩溃
e.Handled = true;
}
}
@@ -328,35 +332,42 @@ namespace XplorePlane
protected override Window CreateShell()
{
// 提前初始化模块,确保硬件务在 MainWindow XAML 解析前已注册
// 杝剝初始化模块,确保硬件务在 MainWindow XAML 解枝剝已注册
if (!_modulesInitialized)
{
base.InitializeModules();
_modulesInitialized = true;
}
// 执行授权检查 | Perform license authorization check
if (!PerformLicenseCheck())
{
Application.Current.Shutdown();
return null;
}
var shell = Container.Resolve<MainWindow>();
// 主窗加载完成后再连接相机,确保所有模块和原生 DLL 已完初始化
// 主窗加载完戝坎冝连接相机,确保所有模块和原生 DLL 已完初始化
shell.Loaded += async (s, e) =>
{
// [DEV] 导航相机连接已蔽,开阶段跳过以加快动速度
// [DEV] 导航相机连接已蔽,开阶段跳过以加快动速度
// TryConnectCamera();
// 初始化主界面探测器帧水线,开始接收探测器图事件
// 初始化主界面探测器帧水线,开始接收探测器图事件
try
{
_ = Container.Resolve<IDetectorFramePipelineService>();
}
catch (Exception ex)
{
Log.Error(ex, "初始化主界面探测器帧水线失败");
Log.Error(ex, "初始化主界面探测器帧水线失败");
}
// 若置为模拟探测器,自动初始化并动采集(无需用户手动作)
// 若置为模拟探测器,自动初始化并动采集(无需用户手动作)
await TryAutoStartSimulatedDetectorAsync();
// [DEV] 相机状通知已
// [DEV] 相机状通知已
// try
// {
// var cameraVm = Container.Resolve<NavigationPropertyPanelViewModel>();
@@ -377,8 +388,9 @@ namespace XplorePlane
}
/// <summary>
/// 若 App.config 中 Detector:Type = Simulated,自动初始化并启动连续采集。
/// 真实硬件类型不受影响
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<AUTO GENERATED BY CONFLICT EXTENSION<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Develop/XP
/// 若 App.config 中 Detector:Type = Simulated,自动初始化并坯动连续采集
/// 真实硬件类型丝块影哝。
/// </summary>
private async System.Threading.Tasks.Task TryAutoStartSimulatedDetectorAsync()
{
@@ -398,25 +410,123 @@ namespace XplorePlane
Log.Error("SimulatedDetector 自动初始化失败:{Msg}", initResult.ErrorMessage);
return;
}
Log.Information("[SimulatedDetector] 初始化功,动连续采集...");
Log.Information("[SimulatedDetector] 初始化功,动连续采集...");
var startResult = await detectorService.StartAcquisitionAsync();
if (!startResult.IsSuccess)
{
Log.Error("SimulatedDetector 动采集失败:{Msg}", startResult.ErrorMessage);
Log.Error("SimulatedDetector 动采集失败:{Msg}", startResult.ErrorMessage);
return;
}
Log.Information("[SimulatedDetector] 连续采集已动,帧将以 5 fps 持续发布");
Log.Information("[SimulatedDetector] 连续采集已动,帧将以 5 fps 挝续坑布");
}
catch (Exception ex)
{
Log.Error(ex, "SimulatedDetector 自动动异常");
Log.Error(ex, "SimulatedDetector 自动动异常");
}
}
/// <summary>
====================================AUTO GENERATED BY CONFLICT EXTENSION====================================
/// 执行授权检查,授权失败时显示错误消杯 | 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;
}
// 临时测试模弝专属:订阅倒计时事件并杝示 | Temporary test mode only: subscribe countdown events and notify
if (licenseService.LicenseMode == XP.Common.License.Enums.LicenseMode.TemporaryTest)
{
licenseService.TestModeWarning5Min += OnTestModeWarning5Min;
licenseService.TestModeWarning1Min += OnTestModeWarning1Min;
licenseService.TestModeTimeout += OnTestModeTimeout;
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>
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>AUTO GENERATED BY CONFLICT EXTENSION>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Feature/TURBO-621-License
/// 在主线程上检索并连接导航相机。
/// pylon SDK 求在主线程(STA)上作,能放到后台线程。
/// pylon SDK 求在主线程(STA)上作,能放到坎坰线程。
/// </summary>
private void TryConnectCamera()
{
@@ -430,7 +540,7 @@ namespace XplorePlane
catch (DeviceNotFoundException)
{
Log.Warning("未检测到导航相机");
_cameraError = "未检测到导航相机,请检查连接后重启软件。";
_cameraError = "未检测到导航相机,请检查连接坎針坯软件。";
}
catch (Exception ex)
{
@@ -440,7 +550,7 @@ namespace XplorePlane
}
/// <summary>
/// 模块已在 CreateShell 中提前初始化,此处跳过避免重复加载
/// 模块已在 CreateShell 中杝剝初始化,此处跳过靿兝針夝加载
/// </summary>
protected override void InitializeModules()
{
@@ -456,7 +566,7 @@ namespace XplorePlane
// 注册 Serilog 的 ILogger 实例
containerRegistry.RegisterInstance<ILogger>(Log.Logger);
// 注册 XP.Common.ILoggerService 适
// 注册 XP.Common.ILoggerService 适
containerRegistry.RegisterSingleton<ILoggerService, SerilogLoggerService>();
// 注册视图和视图模型
@@ -465,44 +575,44 @@ namespace XplorePlane
containerRegistry.RegisterSingleton<ViewportPanelViewModel>();
containerRegistry.RegisterSingleton<NavigationPropertyPanelViewModel>();
// 注册图像处理服务与视图
// 注册图僝处睆朝务与视图
containerRegistry.RegisterSingleton<IImageProcessingService, ImageProcessingService>();
containerRegistry.Register<ImageProcessingViewModel>();
// 注册水线务(例,共享 IImageProcessingService
// 注册水线务(例,共享 IImageProcessingService
containerRegistry.RegisterSingleton<IPipelineExecutionService, PipelineExecutionService>();
containerRegistry.RegisterSingleton<IPipelinePersistenceService, PipelinePersistenceService>();
// 注册全局状态服务(例)
// 注册全局状思朝务(例)
containerRegistry.RegisterSingleton<IAppStateService, AppStateService>();
containerRegistry.RegisterSingleton<IXpDataPathService, XpDataPathService>();
containerRegistry.RegisterSingleton<IDebugPanelConfigService, DebugPanelConfigService>();
containerRegistry.Register<DebugPanelViewModel>();
containerRegistry.Register<DebugPanelWindow>();
// 注册检测配方服务(例)
// 注册检测酝方朝务(例)
containerRegistry.RegisterSingleton<IRecipeService, RecipeService>();
// 注册水线 ViewModel次解创建新实例)
// 注册水线 ViewModel次解创建新实例)
containerRegistry.Register<PipelineEditorViewModel>();
containerRegistry.Register<OperatorToolboxViewModel>();
// 注册硬件库的 ViewModel(供 ViewModelLocator 自动装
// 注册硬件库的 ViewModel(供 ViewModelLocator 自动装
containerRegistry.Register<XP.Hardware.RaySource.ViewModels.RaySourceOperateViewModel>();
// 注册 SQLite 置和数库上下文(FilamentLifetimeService 依赖)
var sqliteConfig = XP.Common.Helpers.ConfigLoader.LoadSqliteConfig();
// 注册 SQLite 置和数库上下文(FilamentLifetimeService 依赖)
var sqliteConfig = DatabaseConfigLoader.LoadSqliteConfig();
containerRegistry.RegisterInstance(sqliteConfig);
containerRegistry.RegisterSingleton<IDbContext, SqliteContext>();
// 注册通用模块的务(本地化Dump
// 注册通用模块的务(本地化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 / 矩阵编排 / 测量数据服务(例)──
// ── CNC / 矩阵编排 / 测針数杮朝务(例)──
containerRegistry.RegisterSingleton<ICncProgramService, CncProgramService>();
containerRegistry.RegisterSingleton<IMatrixService, MatrixService>();
containerRegistry.RegisterSingleton<IMeasurementDataService, MeasurementDataService>();
@@ -510,11 +620,11 @@ namespace XplorePlane
containerRegistry.RegisterSingleton<IImagePersistenceService, ImagePersistenceService>();
containerRegistry.RegisterSingleton<ICncExecutionService, CncExecutionService>();
// ── 主界面实时图 / 探测器队列务(例)──
// ── 主界面实时图 / 探测器队列务(例)──
containerRegistry.RegisterSingleton<IMainViewportService, MainViewportService>();
containerRegistry.RegisterSingleton<IDetectorFramePipelineService, DetectorFramePipelineService>();
// ── CNC / 矩阵 ViewModel(瞬)──
// ── CNC / 矩阵 ViewModel(瞬)──
containerRegistry.Register<CncEditorViewModel>();
containerRegistry.Register<MatrixEditorViewModel>();
containerRegistry.Register<MeasurementStatsViewModel>();
@@ -525,21 +635,21 @@ namespace XplorePlane
containerRegistry.RegisterForNavigation<MatrixPageView>();
containerRegistry.Register<InspectionReportViewerWindow>();
// ── 导航相机务(例)──
// ── 导航相机务(例)──
containerRegistry.RegisterSingleton<ICameraFactory, CameraFactory>();
containerRegistry.RegisterSingleton<ICameraController>(() =>
new CameraFactory().CreateController("Basler"));
containerRegistry.RegisterSingleton<ICameraService, CameraService>();
// ── 录制务(例)──
// ── 录制务(例)──
containerRegistry.RegisterSingleton<IViewportRecordingService, ViewportRecordingService>();
Log.Information("依赖注入容器置完");
Log.Information("依赖注入容器置完");
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
// 所有模块务已在 RegisterTypes 中手动注册
// 所有模块务已在 RegisterTypes 中手动注册
// CommonModule: ILocalizationService, IDumpService
// RaySourceModule: IRaySourceService, IRaySourceFactory, IFilamentLifetimeService
+1 -1
View File
@@ -62,7 +62,7 @@
<PropertyGroup>
<TelerikDir>..\ExternalLibraries\Telerik</TelerikDir>
<BaseOutputPath>..\bin\</BaseOutputPath>
<PlatformTarget>x64</PlatformTarget>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="Telerik.Windows.Controls">