mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
287 lines
13 KiB
Markdown
287 lines
13 KiB
Markdown
|
|
# Launcher 架构拆分评估报告
|
|||
|
|
|
|||
|
|
## 1. 现状分析
|
|||
|
|
|
|||
|
|
### 1.1 Launcher 当前职责清单
|
|||
|
|
|
|||
|
|
根据代码审查,[LanMountainDesktop.Launcher](file:///d:/github/LanMountainDesktop/LanMountainDesktop.Launcher) 当前承担 **6 个主要职责域**:
|
|||
|
|
|
|||
|
|
| 职责域 | 核心文件 | 代码量 | 复杂度 |
|
|||
|
|
|--------|---------|--------|--------|
|
|||
|
|
| **OOBE 首次体验** | `OobeStateService`, `OobeWindow`, `WelcomeOobeStep`, `DataLocationOobeStep`, `PrivacyAgreementService` | ~28 KB | 中 |
|
|||
|
|
| **Splash / 启动协调** | `LauncherFlowCoordinator`, `SplashWindow`, `LoadingDetailsWindow`, `StartupAttemptRegistry` | ~120 KB | **极高** |
|
|||
|
|
| **更新引擎** | `UpdateEngineService`, `UpdateCheckService` | ~77 KB | **极高** |
|
|||
|
|
| **插件管理** | `PluginInstallerService`, `PluginUpgradeQueueService` | ~13 KB | 低 |
|
|||
|
|
| **部署/版本管理** | `DeploymentLocator`, `FlexibleHostLocator`, `DotNetRuntimeProbe`, `LegacyVersionDetector` | ~70 KB | 高 |
|
|||
|
|
| **Air APP 生命周期** | `AirApp/*`, `LauncherBackgroundService` | ~21 KB | 中 |
|
|||
|
|
|
|||
|
|
**总计:~673 KB 源代码,95 个 .cs/.axaml 文件**
|
|||
|
|
|
|||
|
|
### 1.2 关键耦合热点
|
|||
|
|
|
|||
|
|
#### 热点 1:[LauncherFlowCoordinator.cs](file:///d:/github/LanMountainDesktop/LanMountainDesktop.Launcher/Services/LauncherFlowCoordinator.cs) — **90 KB / 2034 行**
|
|||
|
|
|
|||
|
|
这是整个 Launcher 最大的单文件,负责:
|
|||
|
|
- OOBE → Splash → Update → Plugin → Host Launch 的完整编排
|
|||
|
|
- 多实例检测与协调 (IPC coordinator)
|
|||
|
|
- 主程序启动、进程监控、超时处理
|
|||
|
|
- 激活恢复 (activation recovery)
|
|||
|
|
- 所有 UI 窗口的生命周期管理
|
|||
|
|
|
|||
|
|
> [!WARNING]
|
|||
|
|
> 这个文件是当前最大的架构债务。它同时了解所有职责域,是修改任何启动行为都必须触碰的瓶颈文件。
|
|||
|
|
|
|||
|
|
#### 热点 2:[UpdateEngineService.cs](file:///d:/github/LanMountainDesktop/LanMountainDesktop.Launcher/Services/UpdateEngineService.cs) — **72 KB / 1850 行**
|
|||
|
|
|
|||
|
|
包含两套完整的更新应用流程(Legacy 和 PLONDS),内嵌:
|
|||
|
|
- 签名验证
|
|||
|
|
- 增量文件应用
|
|||
|
|
- SHA-256/SHA-512 校验
|
|||
|
|
- 回滚机制
|
|||
|
|
- 快照管理
|
|||
|
|
- 安装检查点与断点续传
|
|||
|
|
|
|||
|
|
#### 热点 3:[App.axaml.cs](file:///d:/github/LanMountainDesktop/LanMountainDesktop.Launcher/App.axaml.cs) — **34 KB / 850 行**
|
|||
|
|
|
|||
|
|
App 入口承担了过多的运行时编排逻辑,包括:
|
|||
|
|
- Coordinator IPC 服务器的创建和管理
|
|||
|
|
- Air APP IPC broker 模式
|
|||
|
|
- 主程序进程存活监控
|
|||
|
|
- 失败恢复 UI 流程
|
|||
|
|
|
|||
|
|
### 1.3 职责域间的依赖关系
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
A["App.axaml.cs<br/>(入口编排)"] --> B["LauncherFlowCoordinator<br/>(流程协调)"]
|
|||
|
|
A --> C["Air APP Broker"]
|
|||
|
|
A --> D["Coordinator IPC"]
|
|||
|
|
B --> E["OobeStateService"]
|
|||
|
|
B --> F["UpdateEngineService"]
|
|||
|
|
B --> G["PluginInstallerService"]
|
|||
|
|
B --> H["DeploymentLocator"]
|
|||
|
|
B --> I["StartupAttemptRegistry"]
|
|||
|
|
B --> D
|
|||
|
|
F --> H
|
|||
|
|
G --> J["PluginUpgradeQueueService"]
|
|||
|
|
C --> K["LauncherAirAppLifecycleService"]
|
|||
|
|
|
|||
|
|
style A fill:#ff6b6b,color:#fff
|
|||
|
|
style B fill:#ff6b6b,color:#fff
|
|||
|
|
style F fill:#ff9f43,color:#fff
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!IMPORTANT]
|
|||
|
|
> 红色节点是耦合最严重的热点。`LauncherFlowCoordinator` 直接依赖几乎所有其他服务。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 方案评估
|
|||
|
|
|
|||
|
|
### Option A:多项目拆分
|
|||
|
|
|
|||
|
|
将 Launcher 拆分成独立的 .NET 项目/可执行文件:
|
|||
|
|
|
|||
|
|
| 拆分后的项目 | 职责 |
|
|||
|
|
|-------------|------|
|
|||
|
|
| `LanMountainDesktop.Launcher` | 精简入口,仅做 OOBE + Splash + 编排调度 |
|
|||
|
|
| `LanMountainDesktop.UpdateService` | 更新检查、下载、应用、回滚 |
|
|||
|
|
| `LanMountainDesktop.PluginService` | 插件安装、升级队列 |
|
|||
|
|
| `LanMountainDesktop.DeploymentManager` | 版本目录管理、主机发现 |
|
|||
|
|
|
|||
|
|
#### 优点
|
|||
|
|
- 最大化隔离:每个服务可独立部署、独立更新
|
|||
|
|
- 更新引擎可以在 Launcher 自身不运行时被调用(例如计划任务)
|
|||
|
|
- 故障隔离:插件安装崩溃不影响更新流程
|
|||
|
|
|
|||
|
|
#### 缺点
|
|||
|
|
|
|||
|
|
> [!CAUTION]
|
|||
|
|
> **这些缺点在当前阶段是致命的。**
|
|||
|
|
|
|||
|
|
- **进程间通信成本巨大**:当前 `LauncherFlowCoordinator` 的 2034 行编排逻辑严重依赖同进程内的同步/异步调用和共享状态(`TaskCompletionSource`、进程对象引用、UI Dispatcher 调度)。拆成多进程意味着每个交互点都需要 IPC 管道 + 序列化 + 超时处理 + 错误恢复。
|
|||
|
|
- **启动延迟增加**:当前 Launcher 启动到 Host 启动的路径已经很长(OOBE → 更新 → 插件 → 主机发现 → Host 进程启动 → IPC 握手)。多进程会在每个阶段增加进程启动开销。
|
|||
|
|
- **安装包膨胀**:每个独立可执行文件都需要自己的运行时依赖,即使共享 Avalonia SDK。
|
|||
|
|
- **复杂的部署协调**:Launcher 自身不可被拆分更新——它就是更新的入口。如果 `UpdateService` 是独立进程,谁来启动它?又需要一个 meta-launcher。
|
|||
|
|
- **当前代码并未准备好**:`LauncherFlowCoordinator.RunAsync()` 是一个巨大的异步方法,内部有十几个局部变量和闭包在多个 await 之间共享状态。这些状态不可能简单地序列化为 IPC 消息。
|
|||
|
|
|
|||
|
|
#### 改造工程量估算
|
|||
|
|
- **IPC 层**:需新增 ~8-10 个 IPC 契约、每个有请求/响应/通知消息
|
|||
|
|
- **进程管理**:需要为每个子服务编写进程启动、健康检查、重启逻辑
|
|||
|
|
- **状态同步**:`StartupAttemptRegistry` 的锁文件机制需要扩展为跨进程锁
|
|||
|
|
- **估计 3-5 人周**,且引入大量新的故障模式
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Option B:单项目内部解耦(推荐)
|
|||
|
|
|
|||
|
|
保持单一 Launcher 可执行文件,通过以下手段实现内部解耦:
|
|||
|
|
|
|||
|
|
#### 阶段 1:职责分层(重构目录结构)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
LanMountainDesktop.Launcher/
|
|||
|
|
├── Program.cs # 入口(保持精简)
|
|||
|
|
├── App.axaml.cs # Avalonia 应用(精简到 <200 行)
|
|||
|
|
├── Core/ # 核心编排层
|
|||
|
|
│ ├── LauncherOrchestrator.cs # 从 App.axaml.cs 提取的运行时编排
|
|||
|
|
│ ├── StartupPipeline.cs # 从 FlowCoordinator 提取的阶段管道
|
|||
|
|
│ └── StartupPhase.cs # 每个阶段的抽象接口
|
|||
|
|
├── Deployment/ # 版本管理域
|
|||
|
|
│ ├── DeploymentLocator.cs
|
|||
|
|
│ ├── FlexibleHostLocator.cs
|
|||
|
|
│ ├── HostLaunchPlan.cs
|
|||
|
|
│ ├── DotNetRuntimeProbe.cs
|
|||
|
|
│ └── LegacyVersionDetector.cs
|
|||
|
|
├── Update/ # 更新引擎域
|
|||
|
|
│ ├── UpdateEngineService.cs # 重构后 <400 行
|
|||
|
|
│ ├── LegacyUpdateApplier.cs # 从 UpdateEngine 提取
|
|||
|
|
│ ├── PlondsUpdateApplier.cs # 从 UpdateEngine 提取
|
|||
|
|
│ ├── SignatureVerifier.cs # 从 UpdateEngine 提取
|
|||
|
|
│ ├── UpdateCheckService.cs
|
|||
|
|
│ └── UpdateSnapshotManager.cs # 从 UpdateEngine 提取
|
|||
|
|
├── Plugin/ # 插件管理域
|
|||
|
|
│ ├── PluginInstallerService.cs
|
|||
|
|
│ └── PluginUpgradeQueueService.cs
|
|||
|
|
├── Oobe/ # OOBE 域
|
|||
|
|
│ ├── OobeStateService.cs
|
|||
|
|
│ ├── IOobeStep.cs
|
|||
|
|
│ ├── WelcomeOobeStep.cs
|
|||
|
|
│ ├── DataLocationOobeStep.cs
|
|||
|
|
│ └── PrivacyAgreementService.cs
|
|||
|
|
├── AirApp/ # Air APP 域(已部分独立)
|
|||
|
|
│ └── ...
|
|||
|
|
├── Coordination/ # 多实例协调域
|
|||
|
|
│ ├── StartupAttemptRegistry.cs
|
|||
|
|
│ ├── LauncherCoordinatorIpcServer.cs
|
|||
|
|
│ └── LauncherCoordinatorIpcClient.cs
|
|||
|
|
├── Views/ # UI 层
|
|||
|
|
│ └── ...
|
|||
|
|
├── ViewModels/ # ViewModel 层
|
|||
|
|
│ └── ...
|
|||
|
|
└── Models/ # 数据模型
|
|||
|
|
└── ...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 阶段 2:拆分 LauncherFlowCoordinator
|
|||
|
|
|
|||
|
|
将 2034 行的 `RunAsync()` 重构为 Pipeline + Phase 模式:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 启动管道定义(伪代码)
|
|||
|
|
public class StartupPipeline
|
|||
|
|
{
|
|||
|
|
private readonly IReadOnlyList<IStartupPhase> _phases;
|
|||
|
|
|
|||
|
|
public async Task<LauncherResult> ExecuteAsync(StartupContext context)
|
|||
|
|
{
|
|||
|
|
foreach (var phase in _phases)
|
|||
|
|
{
|
|||
|
|
var result = await phase.ExecuteAsync(context);
|
|||
|
|
if (!result.Continue) return result.LauncherResult;
|
|||
|
|
}
|
|||
|
|
return LauncherResult.Success();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 各阶段独立实现
|
|||
|
|
public class CleanupPhase : IStartupPhase { ... }
|
|||
|
|
public class OobePhase : IStartupPhase { ... }
|
|||
|
|
public class UpdatePhase : IStartupPhase { ... }
|
|||
|
|
public class PluginUpgradePhase : IStartupPhase { ... }
|
|||
|
|
public class HostLaunchPhase : IStartupPhase { ... }
|
|||
|
|
public class StartupMonitorPhase : IStartupPhase { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 阶段 3:拆分 UpdateEngineService
|
|||
|
|
|
|||
|
|
将 1850 行的更新引擎拆分为独立的策略类:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 更新引擎成为协调者,不再包含实现
|
|||
|
|
public class UpdateEngineService
|
|||
|
|
{
|
|||
|
|
private readonly IUpdateApplier _legacyApplier;
|
|||
|
|
private readonly IUpdateApplier _plondsApplier;
|
|||
|
|
private readonly ISignatureVerifier _signatureVerifier;
|
|||
|
|
private readonly IUpdateSnapshotManager _snapshotManager;
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 阶段 4:精简 App.axaml.cs
|
|||
|
|
|
|||
|
|
将 850 行的 App 入口精简为纯粹的 Avalonia 应用初始化 + 委托给 `LauncherOrchestrator`:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public override void OnFrameworkInitializationCompleted()
|
|||
|
|
{
|
|||
|
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|||
|
|
{
|
|||
|
|
var orchestrator = new LauncherOrchestrator(desktop, LauncherRuntimeContext.Current);
|
|||
|
|
_ = orchestrator.RunAsync();
|
|||
|
|
}
|
|||
|
|
base.OnFrameworkInitializationCompleted();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 优点
|
|||
|
|
- **零部署风险**:不改变安装包结构、不引入新进程、不改变 IPC 拓扑
|
|||
|
|
- **增量重构**:可以一个职责域一个域地逐步重构,每次重构都可编译验证
|
|||
|
|
- **测试友好**:拆分后的各 Phase 和 Service 可以独立单元测试
|
|||
|
|
- **保持启动性能**:单进程内的函数调用无 IPC 开销
|
|||
|
|
- **为未来多进程做准备**:如果将来真的需要拆分进程,接口已经清晰
|
|||
|
|
|
|||
|
|
#### 缺点
|
|||
|
|
- 仍然是单进程:更新引擎崩溃会影响 Launcher 进程
|
|||
|
|
- 需要自律维持架构边界(没有编译级隔离)
|
|||
|
|
|
|||
|
|
#### 改造工程量估算
|
|||
|
|
- 阶段 1(目录重组):~0.5 人天
|
|||
|
|
- 阶段 2(FlowCoordinator 拆分):~2-3 人天
|
|||
|
|
- 阶段 3(UpdateEngine 拆分):~1-2 人天
|
|||
|
|
- 阶段 4(App.axaml.cs 精简):~0.5-1 人天
|
|||
|
|
- **总计 ~4-7 人天**,且风险可控
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 决策矩阵
|
|||
|
|
|
|||
|
|
| 维度 | Option A (多项目拆分) | Option B (内部解耦) |
|
|||
|
|
|------|---------------------|-------------------|
|
|||
|
|
| **改造风险** | 🔴 高:引入新 IPC、新故障模式 | 🟢 低:纯重构,行为不变 |
|
|||
|
|
| **改造工期** | 🔴 3-5 人周 | 🟢 4-7 人天 |
|
|||
|
|
| **启动性能** | 🔴 多进程启动开销 | 🟢 零额外开销 |
|
|||
|
|
| **故障隔离** | 🟢 进程级隔离 | 🟡 需靠代码纪律 |
|
|||
|
|
| **独立更新** | 🟢 各服务可独立版本 | 🔴 单一二进制 |
|
|||
|
|
| **可测试性** | 🟢 天然隔离 | 🟢 接口拆分后等效 |
|
|||
|
|
| **安装包大小** | 🔴 膨胀(多 EXE) | 🟢 不变 |
|
|||
|
|
| **部署复杂度** | 🔴 谁更新 Launcher? | 🟢 VeloPack 现有流程 |
|
|||
|
|
| **团队人力需求** | 🔴 需长期维护多套 IPC | 🟢 维护成本低 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 推荐方案
|
|||
|
|
|
|||
|
|
> [!IMPORTANT]
|
|||
|
|
> **推荐 Option B:单项目内部解耦**,原因如下:
|
|||
|
|
|
|||
|
|
1. **当前阶段的核心瓶颈不是项目边界,而是文件级别的职责混乱**。`LauncherFlowCoordinator` 一个文件 2034 行,`UpdateEngineService` 一个文件 1850 行,`App.axaml.cs` 一个文件 850 行——这才是真正影响可维护性的问题。
|
|||
|
|
|
|||
|
|
2. **Launcher 的核心约束决定了它必须是单一入口**。根据 [ARCHITECTURE.md](file:///d:/github/LanMountainDesktop/docs/ARCHITECTURE.md) 的定义,Launcher 是 "应用的唯一入口",负责版本选择、原子化更新和安全启动。这个约束使得多进程拆分的价值大打折扣。
|
|||
|
|
|
|||
|
|
3. **已经存在良好的外部进程隔离**。Host 主程序 (`LanMountainDesktop.exe`)、Air APP Host (`LanMountainDesktop.AirAppHost`) 都是独立进程。Launcher 只需要作为协调者存在,它不需要自己也拆成多个进程。
|
|||
|
|
|
|||
|
|
4. **改造投入产出比**。Option A 需要 3-5 人周且引入新风险,Option B 需要 4-7 人天且零风险,效果(可维护性、可测试性)几乎等效。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Open Questions
|
|||
|
|
|
|||
|
|
1. **是否考虑将 Launcher 的 CLI 模式(`update check`、`update apply`、`plugin install`)独立成一个无 UI 的命令行工具?** 这是一个比全面拆分轻量得多的拆分点,可以让 CI/CD 和脚本调用不依赖 Avalonia 运行时。
|
|||
|
|
|
|||
|
|
2. **`UpdateEngineService` 是否打算支持 Launcher 自身的自更新?** 如果是,可能需要一个极简的 "bootstrap updater" 组件,这是唯一可能需要独立进程的场景。
|
|||
|
|
|
|||
|
|
3. **内部解耦后,是否要引入 `Microsoft.Extensions.DependencyInjection`?** 当前所有服务都是手动 `new` 的,引入 DI 容器可以自然地约束依赖方向,但也会增加 Launcher 启动路径的复杂度。
|
|||
|
|
|