# LicenseManager 新手使用与集成开发手册

本文档专为初次接触 **LicenseManager 软件授权管理系统** 的开发者与系统管理员设计。涵盖系统准备、环境搭建、8 种授权密钥（Key）类型的业务场景、集成调用示例与常见问题排查。

---

## 目录

- [一、准备工作：需要准备什么](#一准备工作需要准备什么)
  - [1.1 运行环境与依赖](#11-运行环境与依赖)
  - [1.2 安全密钥配置](#12-安全密钥配置)
  - [1.3 业务基础数据初始化](#13-业务基础数据初始化)
- [二、8 种授权 Key 类型详解与集成实战](#二8-种授权-key-类型详解与集成实战)
  - [1. 节点锁定型（Node-Locked Key）](#1-节点锁定型node-locked-key)
  - [2. 限时试用型（Trial Key）](#2-限时试用型trial-key)
  - [3. 永久买断型（Perpetual Key）](#3-永久买断型perpetual-key)
  - [4. 订阅租赁型（Subscription Key）](#4-订阅租赁型subscription-key)
  - [5. 并发浮动型（Concurrent / Floating Key）](#5-并发浮动型concurrent--floating-key)
  - [6. 按量消费/点数型（Consumption Key）](#6-按量消费点数型consumption-key)
  - [7. 命名用户型（Named User Key）](#7-命名用户型named-user-key)
  - [8. 隔离网络纯离线激活（Air-Gapped / Offline File Key）](#8-隔离网络纯离线激活air-gapped--offline-file-key)
- [三、客户端与第三方系统集成方式](#三客户端与第三方系统集成方式)
  - [3.1 C# / .NET 客户端 SDK 极简接入](#31-c--net-客户端-sdk-极简接入)
  - [3.2 跨语言通用 REST API 接入（Java/Python/Go/C++）](#32-跨语言通用-rest-api-接入javapythongoc)
  - [3.3 业务系统（ERP/CRM/商城）自动签发序列号流程](#33-业务系统erpcrm商城自动签发序列号流程)
  - [3.4 Webhook 异步事件监听](#34-webhook-异步事件监听)
- [四、新手常见问题与避坑指南 (FAQ)](#四新手常见问题与避坑指南-faq)

---

## 一、准备工作：需要准备什么

### 1.1 运行环境与依赖

| 组件 | 要求 | 说明 |
| :--- | :--- | :--- |
| **.NET SDK** | .NET 8.0+ | 后端 API 与核心库运行环境 |
| **Node.js** | v18+ (推荐 v20) | Web 管理后台构建与运行 |
| **数据库** | 5 选 1 即可 | **SQLite**（开箱即用无依赖）、**PostgreSQL**、**MySQL**、**SQL Server**、**Oracle** |
| **客户端网络** | 在线 / 离线均可 | 支持联网实时激活与纯隔离断网（Airgap）离线激活 |

### 1.2 安全密钥配置

在启动系统前，需在 `appsettings.json` 或环境变量中准备以下核心密钥：

1. **JWT 签名密钥 (`Jwt:Secret`)**：用于管理端与客户门户登录 Token 签名（至少 32 字符）。
2. **数据保护对称密钥 (`LICENSEMANAGER_DATA_PROTECTION_KEY`)**：用于敏感凭据、私钥的数据库加密（32 字节 Base64 字符串）。
3. **RSA / 非对称签名公私钥对**：
   - **私钥 (Private Key)**：由服务端安全保存，用于签发防伪 License。
   - **公钥 (Public Key)**：硬编码或内嵌到你的客户端软件中，用于本地秒级验签。

```bash
# 生成 32 字节 AES 密钥示例 (PowerShell)
$bytes = New-Object byte[] 32; (New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($bytes); [Convert]::ToBase64String($bytes)
```

### 1.3 业务基础数据初始化

在签发具体 Key 前，需登录管理端（默认地址 `http://localhost:5173`）或通过 API 完成三项基础定义：

```mermaid
flowchart LR
    A[1. 创建产品 Product] --> B[2. 定义功能特性 Feature Catalog]
    B --> C[3. 创建授权模板 License Template]
    C --> D[4. 签发序列号 Issue License]
```

1. **创建产品 (Product)**：如 `CAD-2026`（三维设计软件）。
2. **定义特性目录 (Feature Catalog)**：如 `Module_3D_Render`（渲染模块）、`Export_PDF`（导出功能）。
3. **创建模板 (License Template)**：定义预设规则（如节点锁定期限 365 天、包含哪些模块）。
4. **同步/录入客户 (Customer)**：绑定授权的所属企业或个人。

---

## 二、8 种授权 Key 类型详解与集成实战

系统支持灵活的授权策略，以下逐一介绍不同 Key 类型的业务场景、配置方法与集成代码。

---

### 1. 节点锁定型（Node-Locked Key）

#### 业务场景
- 传统桌面软件、工业上位机、服务器单机授权。
- 一机一码：许可证与设备硬件特征码（CPU、MAC、主板）强绑定，严防盗版拷贝。

#### 集成步骤
1. **获取硬件码**：客户端采集机器指纹（如 `HWID-A1B2-C3D4`）。
2. **发起激活**：调用激活接口，服务端自动将该 Key 锁定到该硬件码。
3. **本地存证**：客户端保存服务端返回的签名载荷（Payload + Signature）。
4. **换机支持**：用户换电脑时，调用反激活（`deactivate`）解绑，即可在新机激活。

```csharp
// 【客户端 C# 示例】：节点锁定激活与验证
using LicenseManager.Core;
using LicenseManager.Core.Abstractions.Models;

var client = new HttpClient { BaseAddress = new Uri("http://localhost:8080") };
var hardwareId = "HWID-8848-CPU-MAC-99"; // 采集自本地机器特征

// 1. 首次使用：输入序列号激活
var activateResp = await client.PostAsJsonAsync("/v1/customer/licenses/activate", new {
    ActivationKey = "ABCD-EFGH-IJKL-MNOP",
    HardwareId = hardwareId,
    Hostname = Environment.MachineName,
    OsVersion = Environment.OSVersion.ToString(),
    SdkVersion = "1.0.0"
});

// 2. 日常启动：在线校验
var verifyResp = await client.PostAsJsonAsync("/v1/customer/licenses/verify", new {
    LicenseKey = "ABCD-EFGH-IJKL-MNOP",
    HardwareId = hardwareId
});
```

---

### 2. 限时试用型（Trial Key）

#### 业务场景
- 供潜在客户免费体验 14 天 / 30 天，到期后软件自动锁定并引导购买正式版。
- 内置**时钟防回拨检测**（防止用户修改操作系统时间作弊）。

#### 集成要点
- 模板配置：设置 `IsTrial = true`，有效期 `Validity = TimeSpan.FromDays(14)`。
- 客户端离线验签时，`LicenseValidator` 会自动校验 `IssuedAt`、`ExpirationDate` 与上次记录的 `LastKnownTime`。

```csharp
// 【客户端 C# 示例】：试用期校验
var validator = new LicenseValidator(rsaPublicKey);
var validationResult = validator.Validate(signedLicensePayload, new LicenseValidationContext {
    HardwareId = currentHwId,
    LastKnownTime = DateTimeOffset.UtcNow // 自动防时钟篡改
});

if (!validationResult.IsValid) {
    ShowPurchaseDialog("您的 14 天试用已结束，请购买正式序列号！");
}
```

---

### 3. 永久买断型（Perpetual Key）

#### 业务场景
- 一次性付费、终身使用的大型行业软件。
- 可选绑定“维保支持截止日期（Maintenance Expiry）”：在维保期内可免费升级新大版本，超期后仍可终身运行旧版本。

#### 集成要点
- 模板配置：`IsPerpetual = true`，`Validity = null`。
- 客户端可在完全断网环境下使用 RSA 公钥进行纯本地毫秒级验签。

---

### 4. 订阅租赁型（Subscription Key）

#### 业务场景
- 按月/按年付费 SaaS 或客户端软件。
- 支持宽限期（Grace Period）：当网络短暂中断或扣费失败时，允许继续使用 3~7 天。

#### 交互流程

```mermaid
sequenceDiagram
    participant App as 客户端软件
    participant Server as 授权服务器
    App->>Server: 1. POST /v1/customer/licenses/verify (在线校验)
    Server-->>App: 2. 校验成功，返回有效截止日
    Note over App: 网络断开时...
    App->>App: 3. 启动本地离线宽限期倒计时 (Grace Period: 7天)
    App->>Server: 4. 网络恢复: POST /v1/customer/licenses/refresh-file
    Server-->>App: 5. 获取续费后的最新签名证书
```

---

### 5. 并发浮动型（Concurrent / Floating Key）

#### 业务场景
- 企业购买了 10 个并发席位，局域网内 100 台电脑均可安装软件，但同时运行的实例不超过 10 个。

#### 集成步骤
1. **启动软件**：调用租约借出接口 `POST /v1/leases/checkout`，占用 1 个席位（同一硬件重复借出复用既有租约）。
2. **运行期间**：每隔 60 秒发送心跳 `POST /v1/leases/heartbeat` 保持席位（未知令牌返回 404、已过期返回 410，需重新 checkout）。
3. **关闭软件**：调用 `POST /v1/leases/release` 释放席位（返回 204）；若软件意外崩溃，服务端租约超时（默认 **1800 秒**，即 30 分钟）自动回收。

> 浮动服务为独立进程 `LicenseManager.Server`（代码未固定端口，经 `ASPNETCORE_URLS`/`--urls` 指定，本文以约定示例端口 8083 演示），认证头为 `X-Api-Key`（配置 `FloatingLicensing:ApiKey`，未配置时租约端点 fail-closed 返回 503）。响应字段为 snake_case（`lease_token` / `lease_expires_at`）。

```csharp
// 【客户端 C# 示例】：浮动租约借出与心跳
// 注意：浮动租约 API 由独立进程 LicenseManager.Server 提供（下例用约定示例端口 http://localhost:8083，
// 实际端口经 ASPNETCORE_URLS/--urls 指定），不要复用主 API（8080）的 HttpClient；认证头为 X-Api-Key（配置 FloatingLicensing:ApiKey）。
var floatingClient = new HttpClient { BaseAddress = new Uri("http://localhost:8083") };
floatingClient.DefaultRequestHeaders.Add("X-Api-Key", "<FloatingLicensing:ApiKey>");
// 1. 借出席位（leaseDurationSeconds 可选，默认 1800 秒）
var lease = await floatingClient.PostAsJsonAsync("/v1/leases/checkout", new {
    HardwareId = hardwareId,
    LeaseDurationSeconds = 1800
});
// 响应: { "lease_token": "...", "hardware_id": "...", "lease_expires_at": "..." }

// 2. 定时心跳 (如每 60 秒一次)
var timer = new PeriodicTimer(TimeSpan.FromSeconds(60));
while (await timer.WaitForNextTickAsync()) {
    await floatingClient.PostAsJsonAsync("/v1/leases/heartbeat", new { LeaseToken = leaseToken });
}

// 3. 退出释放
await floatingClient.PostAsJsonAsync("/v1/leases/release", new { LeaseToken = leaseToken });
```

---

### 6. 按量消费/点数型（Consumption Key）

#### 业务场景
- AI 生成点数、3D 渲染帧数、批量导出调用次数扣减。
- 账户预充值 10,000 点，每次调用按需扣费，点数归零时禁止执行对应功能。

#### 集成调用

```http
POST /v1/customer/licenses/{licenseId}/features/{featureCode}/consume?hardwareId=HWID-01
Content-Type: application/json

{
  "licenseKey": "KEY-1234",
  "delta": 5.0
}
```

> 注：许可证密钥请经请求体 `licenseKey` 字段（或 `X-License-Key` 头）传输；
> 查询参数 `?licenseKey=` 已过时（会进入访问日志/代理日志），仅为兼容旧版 SDK 保留，
> 命中时响应携带 `Deprecation` 头。

**响应示例**：
```json
{
  "featureCode": "AI_IMAGE_GEN",
  "currentConsumption": 15.0,
  "success": true
}
```

---

### 7. 命名用户型（Named User Key）

#### 业务场景
- 绑定特定员工的企业邮箱（如 `alice@company.com`）或工号。
- 与企业 LDAP / OIDC / SSO 单点登录对接，限制一人一号。

#### 集成要点
- 在签发或激活时传入 `UserId` 或通过 SSO 获取的登录身份。
- 校验时验证当前登录账户与授权凭据中的 `User` 标识是否一致。

---

### 8. 隔离网络纯离线激活（Air-Gapped / Offline File Key）

#### 业务场景
- 军工、涉密单位、银行内网、封闭机房，完全物理隔绝互联网。

#### 离线激活 4 步法

```mermaid
flowchart LR
    A[1. 离线机生成请求文件 req.lic] --> B[2. U盘拷至联网电脑]
    B --> C[3. 网页门户导入生成激活文件 act.lic]
    C --> D[4. U盘拷回离线机导入完成激活]
```

1. **生成离线请求**：离线客户端调用本地 SDK 导出包含本机器硬件指纹的 `request.lic` 字符串。
2. **导出至外网**：将请求文件通过 U 盘拷贝至可上网电脑。
3. **门户签发**：在客户门户或管理端上传请求文件，服务端私钥签署并生成 `activation.lic`。
4. **导入激活**：将 `activation.lic` 拷回离线机器导入，客户端本地内置公钥验签通过，永久解锁。

> 离线相关真实端点映射（以源码为准）：`GET /v1/keys`（匿名公钥分发，离线验签前先联网缓存）；
> `POST /v1/customer/licenses/airgap-activate` / `POST /v1/customer/licenses/airgap-decode`（客户离线激活与解码）；
> `GET /v1/customer/licenses/revocations`（离线吊销列表核验）；
> `POST /v1/customer/licenses/{licenseId}/airgap-sync`（离线消费增量同步）；
> 门户侧为 `POST /v1/portal/offline/airgap-activate` 与 `POST /v1/portal/offline/upload-req`。

---

## 三、客户端与第三方系统集成方式

### 3.1 C# / .NET 客户端 SDK 极简接入

只需引用核心库并使用内置的 `LicenseValidator`：

```csharp
// 1. 初始化验签器（仅需填入管理端导出的 RSA 真实公钥）
string rsaPublicKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----";
var validator = new LicenseValidator(rsaPublicKey);

// 2. 本地纯离线校验（纳秒级响应，无需联网）
var result = validator.Validate(signedLicenseText, new LicenseValidationContext {
    HardwareId = "CURRENT-DEVICE-HWID",
    RequiredFeatures = new[] { "Pro_Features" }
});

if (result.IsValid) {
    Console.WriteLine("✓ 授权合法，正常进入软件！");
} else {
    Console.WriteLine($"✗ 授权失效: {result.FailureReason}");
}
```

---

### 3.2 跨语言通用 REST API 接入（Java/Python/Go/C++）

任何语言均可通过标准 HTTP JSON 协议对接：

#### Python 示例
```python
import requests

BASE_URL = "http://your-license-server:8080"

# 激活序列号
response = requests.post(f"{BASE_URL}/v1/customer/licenses/activate", json={
    "ActivationKey": "KEY-ABCD-1234",
    "HardwareId": "LINUX-SERVER-001",
    "Hostname": "prod-node-01"
})

if response.status_code == 200:
    data = response.json()
    print("激活成功:", data)
else:
    print("激活失败:", response.json())
```

---

### 3.3 业务系统（ERP/CRM/商城）自动签发序列号流程

第三方销售/电商系统（用户在其支付或下单流程完成后）通过管理 API 自动发码（平台已不再内置支付/收款能力，资金流由外部销售系统承接）：

```http
POST /v1/admin/licenses
Authorization: Bearer <Admin_JWT_Token>
Content-Type: application/json

{
  "productId": "88888888-4444-4444-4444-121212121212",
  "customerId": "99999999-5555-5555-5555-343434343434",
  "templateId": "77777777-3333-3333-3333-565656565656",
  "quantity": 1,
  "maxDevices": 1
}
```

**返回结果**：
```json
{
  "success": true,
  "data": {
    "licenseIds": ["a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d"]
  }
}
```
业务系统随后调用 `GET /v1/admin/licenses/{id}` 获取明文 `licenseKey` 并通过邮件或短信发送给买家。

---

### 3.4 Webhook 异步事件监听

在管理端后台配置你的 Webhook 接收地址（如 `https://your-api.com/webhooks/license`），系统会在以下生命周期发生时自动推送通知：

- `license.issued`：新序列号签发
- `license.activated`：客户端首次激活
- `license.suspended`：序列号被暂停
- `license.resumed`：序列号恢复
- `license.revoked`：序列号吊销作废
- `license.expires`：授权到期提醒

> 完整事件目录由服务端 `WebhookEventCatalogService` 维护，可通过管理端点查询（含 `license.renewed`、`license.extended`、`subscription.created`、`subscription.renewed`、`subscription.payment_failed` 等；原 `payment.refunded` 等支付域事件已随商业化裁剪移除）。

---

## 四、新手常见问题与避坑指南 (FAQ)

### Q1: 客户端离线验证需要联网吗？
> **不需要**。客户端软件只需内置服务端的 **RSA 公钥**，即可在不连外网、甚至完全断网的环境下对 `SignedLicense` 进行非对称密码学校验，无法被伪造。

### Q2: 用户修改系统时间来延长试用期，系统如何防范？
> 系统在签名载荷中记录了签发时间戳，并在本地验证上下文中持久化 `LastKnownTime`。当检测到 `当前时间 < 上次运行时间` 时，将直接判定为时钟篡改（`CLOCK_TAMPERED`）并拒绝通行。

### Q3: 用户更换了电脑硬件（如更换主板/网卡），序列号无法激活怎么办？
> 1. 用户可在原电脑软件中点击“解除绑定（反激活）”，调用 `POST /v1/customer/licenses/deactivate`。
> 2. 若原电脑已损坏，管理员可在 Web 管理后台进入该序列号详情页，点击 **【解绑设备】** 或 **【重置激活】**，释放硬件绑定。

### Q4: 客户欠费或退款后（退款由外部销售系统处理），如何立刻让客户端停用？
> 在管理端或通过 API 调用 `POST /v1/admin/licenses/{id}/suspend`（暂停）或 `revoke`（吊销）。客户端下一次在线校验或心跳同步时将立即被拦截（返回 HTTP 422），功能即刻锁定。
