# LicenseManager.Core - 重构与开发指南

| 项目 | 内容 |
| --- | --- |
| 文档版本 | v1.0 |
| 文档类型 | 重构与开发指南(Renamespacing & Development Guide) |
| 关联文档 | [RDP v1.0.1](./LicenseManager-Platform-RDP-v1.0.1.md) · [TAD v1.0](./LicenseManager-Platform-TAD-v1.0.md) |
| 上游项目 | [LSXPrime/Aegis](https://github.com/LSXPrime/Aegis) v1.4.0 |
| 目标产物 | `LicenseManager.Core` v1.0-lmc.1(NuGet) |
| 目标读者 | .NET 平台工程师 / LMC 维护者 / 集成方 |
| 文档日期 | 2026-07-23 |

---

## 0. 修订历史

| 版本 | 日期 | 修订内容 |
| --- | --- | --- |
| v1.0 | 2026-07-23 | 初稿 |
| v1.1 | 2026-09 | 按代码实测同步:删除不存在的 `LicenseManager.Core.Extensions` 子项目(扩展以 partial class 内置于 Core);`ILicenseManager` 接口清单补 `ActivateOrVerifyAsync`(返回 `CheckoutResult`);`LicenseContext.HardwareId` 更正为 `string?` 并补 `Features` 字典属性 |

---

## 1. 目标与原则

### 1.1 目标
将 [LSXPrime/Aegis](https://github.com/LSXPrime/Aegis) v1.4.0 源码 **1:1 重构为 `LicenseManager.Core`(LMC)**,作为 LicenseManager 平台的"授权核心库"。

### 1.2 核心原则

| 原则 | 说明 |
| --- | --- |
| **零行为变更** | 重构不改任何运行逻辑;只改命名 |
| **API 兼容** | 公开 API 签名保持(仅命名空间变);旧 Aegis 调用方切换 using 即可 |
| **Abstractions 隔离** | 平台层不直接 `new` 内部类,通过 `LicenseManager.Core.Abstractions` 注入 |
| **上游同步** | 每 2 周合入 Aegis 上游;冲突时优先保留 LMC 命名 |
| **许可证清晰** | MIT(沿用 Aegis) + NOTICE 注明来源 |
| **可独立发布** | LMC 独立 NuGet 包,与平台版本解耦 |

### 1.3 非目标

- ❌ 修改业务逻辑或算法
- ❌ 添加新功能(在 LMC 阶段)
- ❌ 升级 .NET 版本(沿用 .NET 8)
- ❌ 重写单元测试(迁移即可)
- ❌ 改 LICENSE 协议(保持 MIT)

---

## 2. 上游与许可

### 2.1 上游项目

- **仓库**:https://github.com/LSXPrime/Aegis
- **基线版本**:v1.4.0(commit `a1b2c3d4`)
- **协议**:MIT(原版权为 LSXPrime,2024)

### 2.2 我们的协议与署名

- **协议**:MIT(沿用)
- **NOTICE 文件**:
  ```
  LicenseManager.Core
  Copyright (c) 2026 [Your Company]
  
  This product includes software developed at
  The LSXPrime Organization (https://github.com/LSXPrime)
  
  Licensed under the MIT License.
  This product is based on Aegis v1.4.0
  (https://github.com/LSXPrime/Aegis), Copyright (c) 2024 LSXPrime.
  ```

### 2.3 上游同步策略

| 频率 | 内容 |
| --- | --- |
| 每 2 周 | 自动检测 Aegis 上游变更,生成 sync PR |
| 月度 | 由维护者 review 上游重大变更,决定合入策略 |
| 紧急 | Aegis 安全 CVE 公告后 48 小时内合入 |

合入冲突解决:
1. **保留 LMC 命名**(以 LMC 命名空间为准)
2. **保留平台接口契约**(`Abstractions` 接口不变)
3. **记录到 `CHANGELOG.md`**:标 `(rebase from Aegis vX.Y.Z)`
4. **反向回馈**:平台特有的增强(如新 Abstractions)反向 PR 回 Aegis

---

## 3. 项目结构

### 3.1 仓库布局

> 注：以下为目标仓库结构规划，与本仓库当前实际结构存在差异；实际以 `src/` 目录为准。

```
license-manager-core/                      # GitHub: license-manager/core
├── src/
│   ├── LicenseManager.Core/                # ★ 原 Aegis 重命名
│   │   ├── LicenseManager.Core.csproj
│   │   ├── LicenseGenerator.cs
│   │   ├── LicenseBuilder.cs
│   │   ├── LicenseManager.cs
│   │   ├── FeatureManager.cs
│   │   ├── License.cs
│   │   ├── Feature.cs
│   │   ├── IValidationRule.cs
│   │   ├── IValidationRuleGroup.cs
│   │   ├── HardwareUtils.cs
│   │   ├── LicenseUtils.cs
│   │   ├── Enums/
│   │   │   ├── LicenseType.cs
│   │   │   ├── LicenseStatus.cs
│   │   │   ├── FeatureType.cs
│   │   │   └── ...
│   │   ├── Models/
│   │   │   ├── LicenseModel.cs
│   │   │   ├── FeatureModel.cs
│   │   │   └── ...
│   │   ├── Exceptions/
│   │   │   ├── LicenseException.cs
│   │   │   ├── FeatureNotLicensedException.cs
│   │   │   └── ...
│   │   ├── Validation/
│   │   │   ├── HardwareValidationRule.cs
│   │   │   ├── ExpirationValidationRule.cs
│   │   │   └── ...
│   │   └── ...
│   │
│   ├── LicenseManager.Core.Abstractions/    # 平台接口层(新增)
│   │   ├── LicenseManager.Core.Abstractions.csproj
│   │   ├── ILicenseGenerator.cs
│   │   ├── ILicenseManager.cs
│   │   ├── IFeatureManager.cs
│   │   ├── IValidationRule.cs
│   │   ├── IHardwareIdProvider.cs
│   │   ├── Models/
│   │   │   ├── LicenseContext.cs
│   │   │   ├── ValidationContext.cs
│   │   │   └── ...
│   │   └── ...
│   │
│   └── LicenseManager.Server/               # 原 Aegis.Server 重命名(可选)
│       ├── LicenseManager.Server.csproj
│       ├── Services/
│       ├── Data/
│       └── ...
│
├── tests/
│   ├── LicenseManager.Core.Tests/           # 原 Aegis.Tests
│   ├── LicenseManager.Core.Abstractions.Tests/
│   └── LicenseManager.Server.Tests/
│
├── samples/
│   ├── LicenseManager.Sample.Basic/         # 原 Aegis.Sample
│   ├── LicenseManager.Sample.Floating/
│   └── LicenseManager.Sample.AirGapped/
│
├── deploy/
│   └── tools/
│       ├── KeyGen/                          # 密钥生成 CLI
│       └── LicenseCli/                      # License 验证/管理 CLI
│
├── .github/
│   └── workflows/                           # CI/CD
│
├── docs/
│   ├── README.md
│   ├── MIGRATION-FROM-AEGIS.md              # Aegis 用户迁移指南
│   ├── ARCHITECTURE.md
│   └── API.md
│
├── LICENSE                                  # MIT
├── NOTICE                                   # 署名
├── CHANGELOG.md
├── README.md
├── Directory.Build.props
└── LicenseManager.sln
```

### 3.2 项目文件示例

`src/LicenseManager.Core/LicenseManager.Core.csproj`:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>LicenseManager.Core</RootNamespace>
    <AssemblyName>LicenseManager.Core</AssemblyName>
    <PackageId>LicenseManager.Core</PackageId>
    <Version>1.0.0-lmc.1</Version>
    <Title>LicenseManager.Core</Title>
    <Description>License generation, validation, and feature management core for LicenseManager Platform. Based on LSXPrime/Aegis v1.4.0.</Description>
    <Authors>[Your Company]</Authors>
    <Company>[Your Company]</Company>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <PackageProjectUrl>https://github.com/license-manager/core</PackageProjectUrl>
    <PackageTags>license,key,licensing,protect,license-manager,license-manager-core</PackageTags>
    <RepositoryUrl>https://github.com/license-manager/core</RepositoryUrl>
    <RepositoryType>git</RepositoryType>
    <PackageReadmeFile>README.md</PackageReadmeFile>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);CS1591</NoWarn>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  </ItemGroup>

  <ItemGroup>
    <None Include="..\..\README.md" Pack="true" PackagePath="\" />
    <None Include="..\..\NOTICE" Pack="true" PackagePath="\" />
    <None Include="..\..\LICENSE" Pack="true" PackagePath="\" />
  </ItemGroup>
</Project>
```

`src/LicenseManager.Core.Abstractions/LicenseManager.Core.Abstractions.csproj`:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>LicenseManager.Core.Abstractions</RootNamespace>
    <AssemblyName>LicenseManager.Core.Abstractions</AssemblyName>
    <PackageId>LicenseManager.Core.Abstractions</PackageId>
    <Version>1.0.0-lmc.1</Version>
    <Title>LicenseManager.Core.Abstractions</Title>
    <Description>Abstractions and contracts for LicenseManager.Core. Platform code depends on this, not on the concrete implementation.</Description>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
  </PropertyGroup>
</Project>
```

---

## 4. 命名空间映射

### 4.1 完整映射表

| 原 Aegis 命名空间 | 新 LMC 命名空间 | 备注 |
| --- | --- | --- |
| `Aegis` | `LicenseManager.Core` | 主命名空间 |
| `Aegis.Models` | `LicenseManager.Core.Models` | |
| `Aegis.Enums` | `LicenseManager.Core.Enums` | |
| `Aegis.Exceptions` | `LicenseManager.Core.Exceptions` | |
| `Aegis.Validation` | `LicenseManager.Core.Validation` | |
| `Aegis.Utils` | `LicenseManager.Core.Utils` | |
| `Aegis.Server` | `LicenseManager.Server` | 独立项目(可选) |
| `Aegis.Server.Data` | `LicenseManager.Server.Data` | |
| `Aegis.Server.Services` | `LicenseManager.Server.Services` | |
| `Aegis.Tests` | `LicenseManager.Core.Tests` | 测试项目 |
| `Aegis.Sample.*` | `LicenseManager.Sample.*` | 示例项目 |

### 4.2 程序集映射

| 原程序集 | 新程序集 | NuGet 包 ID |
| --- | --- | --- |
| `Aegis.dll` | `LicenseManager.Core.dll` | `LicenseManager.Core` |
| `Aegis.Server.dll` | `LicenseManager.Server.dll` | `LicenseManager.Server` |
| `Aegis.Tests.dll` | `LicenseManager.Core.Tests.dll` | (内部) |

### 4.3 包元数据

| 字段 | 原 Aegis | 新 LMC |
| --- | --- | --- |
| `PackageId` | `Aegis` | `LicenseManager.Core` |
| `Title` | Aegis | LicenseManager.Core |
| `Version` | 1.4.0 | 1.0.0-lmc.1(语义化版本前缀) |
| `Authors` | LSXPrime | [Your Company] |
| `Company` | LSXPrime | [Your Company] |
| `Description` | Aegis 1.4.0 - .NET 8 license library | LicenseManager.Core - License generation & validation core for LicenseManager Platform (based on LSXPrime/Aegis) |
| `PackageTags` | license,licensing | license,key,licensing,protect,license-manager |

### 4.4 文件名映射(可选)

文件名不强制改(命名空间已重命名),但建议同步以保持一致性:

| 原文件名 | 新文件名 |
| --- | --- |
| `Aegis.csproj` | `LicenseManager.Core.csproj` |
| `LicenseGenerator.cs` | `LicenseGenerator.cs`(保持,只是类内 namespace 改) |
| ... | ... |

> **建议**:源代码文件名可保持原样(类名未变),只需修改命名空间声明。

---

## 5. 重构步骤

### 5.1 一次性重构流程

```bash
# 1. 克隆 Aegis
git clone https://github.com/LSXPrime/Aegis.git aegis-tmp
cd aegis-tmp
git checkout v1.4.0

# 2. 创建 LMC 仓库
cd ..
git init license-manager-core
cd license-manager-core

# 3. 复制源代码(剔除 .git, README, LICENSE)
rsync -av --exclude='.git' --exclude='README.md' --exclude='LICENSE' \
  ../aegis-tmp/ .

# 4. 全局替换命名空间
find . -type f \( -name "*.cs" -o -name "*.csproj" -o -name "*.sln" \) \
  -exec sed -i 's/\bnamespace Aegis\b/namespace LicenseManager.Core/g' {} +
find . -type f \( -name "*.cs" -o -name "*.csproj" \) \
  -exec sed -i 's/\busing Aegis\b/using LicenseManager.Core/g' {} +
find . -type f -name "*.cs" \
  -exec sed -i 's/\bAegis\.Models\b/LicenseManager.Core.Models/g' {} +
find . -type f -name "*.cs" \
  -exec sed -i 's/\bAegis\.Enums\b/LicenseManager.Core.Enums/g' {} +
find . -type f -name "*.cs" \
  -exec sed -i 's/\bAegis\.Exceptions\b/LicenseManager.Core.Exceptions/g' {} +
find . -type f -name "*.cs" \
  -exec sed -i 's/\bAegis\.Validation\b/LicenseManager.Core.Validation/g' {} +
find . -type f -name "*.cs" \
  -exec sed -i 's/\bAegis\.Utils\b/LicenseManager.Core.Utils/g' {} +
find . -type f -name "*.cs" \
  -exec sed -i 's/\bAegis\.Server\b/LicenseManager.Server/g' {} +

# 5. 替换 using 引用
find . -type f -name "*.cs" \
  -exec sed -i 's/using Aegis;/using LicenseManager.Core;/g' {} +
find . -type f -name "*.cs" \
  -exec sed -i 's/using Aegis\./using LicenseManager.Core./g' {} +

# 6. 重命名项目文件
mv src/Aegis/Aegis.csproj src/LicenseManager.Core/LicenseManager.Core.csproj
# 调整 csproj 中的 PackageId, Title, Description, Authors, etc.

# 7. 替换根命名空间声明
sed -i 's/namespace Aegis/namespace LicenseManager.Core/g' src/LicenseManager.Core/*.cs

# 8. 替换 AssemblyName / RootNamespace
find . -type f -name "*.csproj" \
  -exec sed -i 's|<AssemblyName>Aegis</AssemblyName>|<AssemblyName>LicenseManager.Core</AssemblyName>|g' {} +
find . -type f -name "*.csproj" \
  -exec sed -i 's|<RootNamespace>Aegis</RootNamespace>|<RootNamespace>LicenseManager.Core</RootNamespace>|g' {} +

# 9. 验证构建
dotnet restore
dotnet build -c Release
dotnet test

# 10. 提交
git add .
git commit -m "refactor(core): rename Aegis to LicenseManager.Core (1:1 rename, zero behavior change)

- Rename namespace Aegis → LicenseManager.Core
- Rename AssemblyName and NuGet PackageId
- Update NOTICE and LICENSE
- Migrate all using directives
- Migrate all tests

Based on LSXPrime/Aegis v1.4.0, MIT licensed.
"

# 11. 打 tag
git tag v1.0.0-lmc.1
```

### 5.2 验证清单(零行为变更)

| 检查项 | 方法 | 通过标准 |
| --- | --- | --- |
| 所有源文件已替换 | `grep -r "namespace Aegis" src/` | 0 个结果 |
| 所有 using 已替换 | `grep -r "using Aegis" src/` | 0 个结果 |
| 程序集名正确 | `dotnet build` 输出 `LicenseManager.Core.dll` | ✅ |
| 所有原测试通过 | `dotnet test` | 100% pass |
| 公共 API 数量一致 | 对比 LMC vs Aegis 反射导出 | 误差 0 |
| NuGet 包元数据正确 | `dotnet pack` 后 `nuget inspect` | 字段正确 |
| 性能无回退 | 跑 benchmark(若有) | ±5% 内 |

### 5.3 自动化脚本(可复用)

> 注：该脚本需自行创建（仓库中未包含）。

`scripts/rename-from-aegis.sh`:

```bash
#!/usr/bin/env bash
set -euo pipefail

SRC_DIR="${1:?Source directory required}"

# 命名空间替换
declare -A MAP=(
  ["Aegis.Models"]="LicenseManager.Core.Models"
  ["Aegis.Enums"]="LicenseManager.Core.Enums"
  ["Aegis.Exceptions"]="LicenseManager.Core.Exceptions"
  ["Aegis.Validation"]="LicenseManager.Core.Validation"
  ["Aegis.Utils"]="LicenseManager.Core.Utils"
  ["Aegis.Server"]="LicenseManager.Server"
  ["Aegis.Server.Data"]="LicenseManager.Server.Data"
  ["Aegis.Server.Services"]="LicenseManager.Server.Services"
)

# 排序长前缀先替换,避免短前缀覆盖
for OLD in $(echo "${!MAP[@]}" | tr ' ' '\n' | awk '{ print length, $0 }' | sort -rn | cut -d' ' -f2-); do
  NEW="${MAP[$OLD]}"
  find "$SRC_DIR" -type f -name "*.cs" -exec sed -i "s/\b${OLD//./\\.}\b/${NEW//./\\.}/g" {} +
done

# 通用 Aegis 命名空间
find "$SRC_DIR" -type f -name "*.cs" \
  -exec sed -i 's/\bnamespace Aegis\b/namespace LicenseManager.Core/g' {} +

# using 指令
find "$SRC_DIR" -type f -name "*.cs" \
  -exec sed -i 's/using Aegis;/using LicenseManager.Core;/g' {} +
find "$SRC_DIR" -type f -name "*.cs" \
  -exec sed -i 's/using Aegis\./using LicenseManager.Core./g' {} +

echo "Renaming complete. Verify with: grep -r 'Aegis' $SRC_DIR"
```

### 5.4 增量同步(2 周一次)

```bash
# 1. 拉取 Aegis 上游
git remote add aegis-upstream https://github.com/LSXPrime/Aegis.git
git fetch aegis-upstream

# 2. 列出新增 commit
git log --oneline v1.4.0..aegis-upstream/master

# 3. cherry-pick 或三方合并
# 推荐:三方合并
git merge aegis-upstream/master -m "merge: sync from Aegis upstream"

# 4. 自动运行重命名脚本
./scripts/rename-from-aegis.sh src/

# 5. 跑测试
dotnet test

# 6. 修复冲突(若有)
# 7. 提交 + 打 tag
```

---

## 6. Abstractions 接口层(平台解耦)

### 6.1 为什么需要 Abstractions

平台业务层(产品/客户/Audit)不应该直接依赖 `LicenseGenerator` 等具体类。理由:

1. **可替换**:核心可升级而不影响业务
2. **可测试**:业务单测可 mock 接口
3. **边界清晰**:接口是契约,实现是细节

### 6.2 核心接口设计

`src/LicenseManager.Core.Abstractions/ILicenseGenerator.cs`:

```csharp
namespace LicenseManager.Core.Abstractions;

/// <summary>
/// Generates and validates signed licenses.
/// </summary>
public interface ILicenseGenerator
{
    /// <summary>
    /// Generates a new signed license for the given context.
    /// </summary>
    Task<SignedLicense> GenerateAsync(
        LicenseContext context,
        CancellationToken ct = default);

    /// <summary>
    /// Validates a signed license and returns the contained payload.
    /// </summary>
    Task<LicenseValidationResult> ValidateAsync(
        SignedLicense signedLicense,
        ValidationContext context,
        CancellationToken ct = default);

    /// <summary>
    /// Rotates the system key. Returns the new key version.
    /// </summary>
    Task<int> RotateSystemKeyAsync(
        RotationPolicy policy,
        CancellationToken ct = default);
}
```

`src/LicenseManager.Core.Abstractions/ILicenseManager.cs`:

```csharp
namespace LicenseManager.Core.Abstractions;

/// <summary>
/// Manages license lifecycle: activation, deactivation, refresh.
/// </summary>
public interface ILicenseManager
{
    Task<ActivationResult> ActivateAsync(
        ActivationRequest request,
        CancellationToken ct = default);

    Task<DeactivationResult> DeactivateAsync(
        DeactivationRequest request,
        CancellationToken ct = default);

    Task<RefreshResult> RefreshAsync(
        RefreshRequest request,
        CancellationToken ct = default);

    Task<VerificationResult> VerifyAsync(
        VerificationRequest request,
        CancellationToken ct = default);

    // 「验证+激活」合一:已激活于当前设备则仅校验,否则执行首次激活(一键激活)
    Task<CheckoutResult> ActivateOrVerifyAsync(
        ActivationRequest request,
        CancellationToken ct = default);
}
```

`src/LicenseManager.Core.Abstractions/IFeatureManager.cs`:

```csharp
namespace LicenseManager.Core.Abstractions;

/// <summary>
/// Feature entitlement and gating.
/// </summary>
public interface IFeatureManager
{
    bool IsFeatureEnabled(LicenseContext context, string featureCode);

    T GetFeatureValue<T>(LicenseContext context, string featureCode, T defaultValue = default);

    void ThrowIfNotAllowed(LicenseContext context, string featureCode);

    Task<UpdateResult> UpdateFeatureConsumptionAsync(
        LicenseContext context,
        string featureCode,
        decimal delta,
        CancellationToken ct = default);
}
```

### 6.3 DTO/Model(放 Abstractions)

```csharp
// LicenseContext.cs
public sealed record LicenseContext(
    string LicenseKey,
    string ProductCode,
    string? HardwareId,
    DateTimeOffset Now,
    IReadOnlyDictionary<string, object> Metadata)
{
    // 按功能代码索引的功能值集合,供 IFeatureManager 使用;
    // 加载实际 License 时由调用方从 License.Features 填充
    public IReadOnlyDictionary<string, FeatureValue> Features { get; init; } =
        new Dictionary<string, FeatureValue>();
}

// ValidationContext.cs
public sealed record ValidationContext(
    string? ActivationKey,
    string HardwareId,
    string? Hostname,
    string? OsVersion,
    string? SdkVersion,
    IReadOnlyDictionary<string, object> Environment);

// SignedLicense.cs
public sealed record SignedLicense(
    string Payload,
    string Signature,
    int KeyVersion,
    DateTimeOffset IssuedAt);

// LicenseValidationResult.cs
public abstract record LicenseValidationResult
{
    public sealed record Valid(LicensePayload Payload) : LicenseValidationResult;
    public sealed record Invalid(ValidationError Error) : LicenseValidationResult;
}

public sealed record ValidationError(
    string Code,
    string Message,
    IReadOnlyDictionary<string, object>? Details = null);
```

### 6.4 LMC 内部实现,对外暴露接口

```csharp
// src/LicenseManager.Core/LicenseGenerator.cs(原 Aegis)
namespace LicenseManager.Core;

public sealed class LicenseGenerator : ILicenseGenerator
{
    // 原 Aegis 实现
    public async Task<SignedLicense> GenerateAsync(LicenseContext context, CancellationToken ct)
    {
        // ... 原有实现
    }

    public async Task<LicenseValidationResult> ValidateAsync(...)
    {
        // ... 原有实现
    }
}

// 在 src/LicenseManager.Core/DependencyInjection.cs 提供扩展
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddLicenseManagerCore(
        this IServiceCollection services,
        Action<LicenseManagerCoreOptions> configure)
    {
        services.Configure(configure);
        services.AddSingleton<ILicenseGenerator, LicenseGenerator>();
        services.AddSingleton<ILicenseManager, LicenseManager>();
        services.AddSingleton<IFeatureManager, FeatureManager>();
        return services;
    }
}
```

### 6.5 平台层使用示例

```csharp
// 平台层(不引用 LicenseManager.Core,只引用 Abstractions)
using LicenseManager.Core.Abstractions;

public class LicenseService
{
    private readonly ILicenseGenerator _generator;
    private readonly ILicenseManager _manager;

    public LicenseService(ILicenseGenerator generator, ILicenseManager manager)
    {
        _generator = generator;
        _manager = manager;
    }

    public async Task<LicenseDto> IssueLicenseAsync(Guid productId, Guid customerId)
    {
        var context = new LicenseContext(
            LicenseKey: Guid.NewGuid().ToString(),
            ProductCode: productId.ToString(),
            HardwareId: null,
            Now: DateTimeOffset.UtcNow,
            Metadata: new Dictionary<string, object>
            {
                ["customer_id"] = customerId,
                ["product_id"] = productId
            });

        var signed = await _generator.GenerateAsync(context);
        return new LicenseDto(signed);
    }
}
```

---

## 7. 构建与测试

### 7.1 构建命令

```bash
# 还原
dotnet restore

# 构建(全平台)
dotnet build -c Release

# 仅构建 Core
dotnet build src/LicenseManager.Core/LicenseManager.Core.csproj -c Release

# 打包
dotnet pack src/LicenseManager.Core/LicenseManager.Core.csproj \
  -c Release \
  -o ./artifacts/ \
  --no-build \
  -p:Version=1.0.0-lmc.1 \
  -p:PackageVersion=1.0.0-lmc.1
```

### 7.2 测试

```bash
# 全部测试
dotnet test -c Release

# 仅 Core
dotnet test tests/LicenseManager.Core.Tests/ -c Release

# 带覆盖率
dotnet test -c Release --collect:"XPlat Code Coverage" --results-directory ./coverage

# 报告(ReportGenerator)
reportgenerator \
  -reports:./coverage/**/coverage.cobertura.xml \
  -targetdir:./coverage-report \
  -reporttypes:Html
```

### 7.3 测试要求

| 类别 | 覆盖率目标 |
| --- | --- |
| LMC Core | ≥ 85% |
| LMC Abstractions | ≥ 90% |
| LMC 扩展(消费型/Bundle/Refresh,以 partial class 内置于 Core) | ≥ 80% |
| 整体 | ≥ 80% |

### 7.4 测试用例迁移

原 Aegis 测试 **完整迁移**(不改测试逻辑,只改 using):

| 原测试命名空间 | 新测试命名空间 |
| --- | --- |
| `Aegis.Tests.LicenseGenerator` | `LicenseManager.Core.Tests.LicenseGenerator` |
| `Aegis.Tests.FeatureManager` | `LicenseManager.Core.Tests.FeatureManager` |
| ... | ... |

---

## 8. NuGet 打包与发布

### 8.1 包元数据(最终)

```xml
<PropertyGroup>
  <PackageId>LicenseManager.Core</PackageId>
  <Version>1.0.0-lmc.1</Version>
  <Title>LicenseManager.Core</Title>
  <Description>
    License generation, validation, and feature management core for LicenseManager Platform.
    Based on LSXPrime/Aegis v1.4.0 (MIT licensed).
  </Description>
  <Authors>[Your Company]</Authors>
  <Company>[Your Company]</Company>
  <PackageLicenseExpression>MIT</PackageLicenseExpression>
  <PackageProjectUrl>https://github.com/license-manager/core</PackageProjectUrl>
  <PackageReadmeFile>README.md</PackageReadmeFile>
  <RepositoryUrl>https://github.com/license-manager/core</RepositoryUrl>
  <RepositoryType>git</RepositoryType>
  <PackageTags>license;key;licensing;protect;license-manager;license-manager-core</PackageTags>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
  <Deterministic>true</Deterministic>
</PropertyGroup>
```

### 8.2 验证包

```bash
# 列出包内容
unzip -l artifacts/LicenseManager.Core.1.0.0-lmc.1.nupkg

# 验证元数据
nuget inspect artifacts/LicenseManager.Core.1.0.0-lmc.1.nupkg

# 验证签名
nuget verify -Signatures artifacts/LicenseManager.Core.1.0.0-lmc.1.nupkg
```

### 8.3 发布

#### 发布到 nuget.org(公开)

```bash
# 需要 API Key
dotnet nuget push artifacts/LicenseManager.Core.1.0.0-lmc.1.nupkg \
  --api-key $NUGET_API_KEY \
  --source https://api.nuget.org/v3/index.json
```

#### 发布到私有 NuGet Feed(私有化客户)

```bash
# 私有 NuGet(在客户内网)
dotnet nuget push artifacts/LicenseManager.Core.1.0.0-lmc.1.nupkg \
  --source https://nuget.internal.customer.com/v3/index.json
```

#### GitHub Packages

```bash
dotnet nuget push artifacts/LicenseManager.Core.1.0.0-lmc.1.nupkg \
  --source https://nuget.pkg.github.com/license-manager/index.json \
  --api-key $GITHUB_TOKEN
```

### 8.4 提交流程

```
feature/* → PR → main → CI → 自动构建 + 测试
  ↓
打 tag(v1.0.0-lmc.1)
  ↓
GitHub Actions: dotnet pack → 上传 artifacts
  ↓
手动或自动 dotnet nuget push
  ↓
GitHub Release(附 changelog + nupkg)
```

### 8.5 版本策略

| 来源 | 版本号 | 说明 |
| --- | --- | --- |
| 上游 Aegis 1.4.0 | `1.0.0-lmc.1` | 首次重命名(基线) |
| 上游 Aegis 1.4.1 | `1.0.1-lmc.1` | 同步上游 patch |
| 上游 Aegis 1.5.0 | `1.1.0-lmc.1` | 同步上游 minor |
| 上游 Aegis 2.0.0 | `2.0.0-lmc.1` | 同步上游 major |
| 平台特定 patch | `1.0.0-lmc.2` | 我们自己的 patch |

格式:`{Aegis-version}-lmc.{lmc-revision}`

---

## 9. API 契约稳定性

### 9.1 兼容性承诺

- LMC v1.x:24 个月内保证 ABI/API 向后兼容
- 公开 API:`LicenseGenerator` / `LicenseManager` / `FeatureManager` 等所有公共类
- 公开 DTO:`SignedLicense` / `LicenseContext` 等 records

### 9.2 破坏性变更(必须升级主版本)

- 移除公开类型
- 改变公开方法签名
- 改变默认行为
- 改变 license 文件格式
- 改变默认算法

### 9.3 非破坏性变更(patch/minor 即可)

- 新增公开类型
- 新增公开方法
- 新增枚举值
- 性能优化
- Bug fix

### 9.4 弃用流程

1. 标记 `[Obsolete("Use X instead, will be removed in v2.0")]`
2. 在 CHANGELOG 标注 `Deprecated`
3. 至少 6 个月后移除
4. 提前 12 个月公告

---

## 10. 现有 Aegis 用户迁移指南

### 10.1 升级步骤

```bash
# 1. 在 csproj 中替换
# 旧:
<PackageReference Include="Aegis" Version="1.4.0" />
# 新:
<PackageReference Include="LicenseManager.Core" Version="1.0.0-lmc.1" />
<PackageReference Include="LicenseManager.Core.Abstractions" Version="1.0.0-lmc.1" />
```

```bash
# 2. 批量替换 using
# 旧: using Aegis;
# 新: using LicenseManager.Core;

# 用工具:
# VS: Edit > Find and Replace > Replace in Files
#   Find:    using Aegis;
#   Replace: using LicenseManager.Core;
#   Scope:   Entire Solution

# 命名空间替换:
#   Aegis.Models    → LicenseManager.Core.Models
#   Aegis.Enums     → LicenseManager.Core.Enums
#   Aegis.Exceptions→ LicenseManager.Core.Exceptions
```

```bash
# 3. (推荐)切换到 Abstractions
# 旧: 直接 new LicenseGenerator(...)
# 新: 依赖注入 ILicenseGenerator

# 在 Program.cs:
services.AddLicenseManagerCore(options =>
{
    options.SystemKeyPath = "/etc/license-manager/system-key.json";
});
```

```bash
# 4. 验证
dotnet build
dotnet test
```

### 10.2 兼容性矩阵

| Aegis 版本 | LMC 版本 | 兼容? | 备注 |
| --- | --- | --- | --- |
| 1.0.x | 不支持 | ❌ | 需先升到 1.4.0 |
| 1.4.0 | 1.0.0-lmc.1 | ✅ | 首次重命名版本(基线) |
| 1.4.1 | 1.0.1-lmc.1 | ✅ | 上游 patch 后 |
| 1.5.0 | 1.1.0-lmc.1 | ✅ | 上游 minor 后 |
| 2.0.0 | 2.0.0-lmc.1 | ⚠️ | 需检查 breaking change |

### 10.3 迁移检查表

- [ ] csproj 替换 PackageReference
- [ ] 全局替换 `using Aegis` → `using LicenseManager.Core`
- [ ] 替换 `Aegis.Models` / `Aegis.Enums` / `Aegis.Exceptions` / `Aegis.Validation` / `Aegis.Utils`
- [ ] 替换 `Aegis.Server` 引用(若使用)
- [ ] (推荐)切换到 Abstractions 注入
- [ ] (推荐)配置 System Key 路径
- [ ] 重新构建
- [ ] 跑测试
- [ ] (生产)轮换 System Key

### 10.4 常见问题

**Q: 我现有的 license 文件还能用吗?**
A: ✅ 可以。license 文件格式未变,公私钥兼容。

**Q: 我现有的 System Key 还能用吗?**
A: ✅ 可以。只需将路径指向新位置。

**Q: 我现有的测试会通过吗?**
A: ✅ 可以。零行为变更,所有原测试通过。

**Q: 我应该切换到 Abstractions 吗?**
A: 推荐,但非强制。Abstractions 提供更好的解耦。

**Q: 性能会变化吗?**
A: 否。重构不改算法,只改命名。

---

## 11. CI/CD

### 11.1 GitHub Actions

`.github/workflows/ci.yml`:

```yaml
name: CI

on:
  push:
    branches: [main, release/*]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        dotnet-version: ['8.0.x']
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ matrix.dotnet-version }}

      - name: Restore
        run: dotnet restore

      - name: Build
        run: dotnet build -c Release --no-restore

      - name: Test
        run: dotnet test -c Release --no-build --verbosity normal --collect:"XPlat Code Coverage"

      - name: Pack
        run: |
          dotnet pack src/LicenseManager.Core/LicenseManager.Core.csproj \
            -c Release --no-build -o ./artifacts \
            -p:PackageVersion=1.0.0-lmc.1
          dotnet pack src/LicenseManager.Core.Abstractions/LicenseManager.Core.Abstractions.csproj \
            -c Release --no-build -o ./artifacts \
            -p:PackageVersion=1.0.0-lmc.1

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: nuget-packages
          path: artifacts/*.nupkg

  publish:
    needs: build
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v4
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: nuget-packages
          path: artifacts

      - name: Publish to NuGet
        run: dotnet nuget push artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
```

### 11.2 质量门

- 构建必须成功
- 所有测试必须通过
- 覆盖率 ≥ 80%
- 0 个高危 CVE(SCA 卡)
- 公共 API 数量与 Aegis 1.4.0 一致(用 `Microsoft.DotNet.ApiCompat` 验证)

---

## 12. 文档

### 12.1 必须产出

| 文档 | 路径 | 状态 |
| --- | --- | --- |
| README.md | `/README.md` | 必需 |
| MIGRATION-FROM-AEGIS.md | `/docs/MIGRATION-FROM-AEGIS.md` | 必需 |
| ARCHITECTURE.md | `/docs/ARCHITECTURE.md` | 必需 |
| API.md | `/docs/API.md` | 必需 |
| CHANGELOG.md | `/CHANGELOG.md` | 必需 |
| LICENSE | `/LICENSE` | 必需 |
| NOTICE | `/NOTICE` | 必需 |
| ADR | `/docs/adr/` | 推荐 |

### 12.2 README.md 模板要点

- 介绍 LMC 与 Aegis 的关系
- 快速开始(5 分钟上手)
- 链接到 MIGRATION-FROM-AEGIS
- 链接到 API.md
- 链接到 Aegis 上游
- 许可证说明

---

## 13. 风险与缓解

| 风险 | 概率 | 影响 | 缓解 |
| --- | --- | --- | --- |
| 重命名脚本漏改 | 中 | 高 | CI 中 grep 检查 |
| 上游大版本合并冲突 | 中 | 中 | 自动化脚本 + 人工 review |
| 用户不切换到 LMC | 低 | 低 | 兼容期 6 个月 + 公告 |
| 性能因 namespace 变化 | 极低 | 中 | Benchmark CI |
| 签名格式变化 | 极低 | 高 | 零行为变更 + 兼容性测试 |

---

## 14. 附录

### 14.1 关联文档

- [RDP v1.0.1](./LicenseManager-Platform-RDP-v1.0.1.md) - 产品需求
- [TAD v1.0](./LicenseManager-Platform-TAD-v1.0.md) - 技术架构
- [Aegis 上游](https://github.com/LSXPrime/Aegis) - 上游项目

### 14.2 检查清单(发布前)

- [ ] 所有源文件命名空间已替换
- [ ] 所有 using 指令已替换
- [ ] 项目文件 PackageId / AssemblyName / RootNamespace 已更新
- [ ] LICENSE 与 NOTICE 已就位
- [ ] 所有测试通过
- [ ] 覆盖率 ≥ 80%
- [ ] 公共 API 与 Aegis 1.4.0 一致
- [ ] NuGet 包元数据正确
- [ ] 签名包(snupkg)生成
- [ ] GitHub Release 就绪
- [ ] 迁移文档就位
- [ ] CHANGELOG 就位
