feat..去除了冗余的字体文件,又修改了PLONDS系统

This commit is contained in:
lincube
2026-05-30 11:56:50 +08:00
parent d004088601
commit 6a650873bc
61 changed files with 1849 additions and 3147 deletions

View File

@@ -0,0 +1,512 @@
# PLONDS Comparator 改造设计
> 日期2026-05-30
> 状态:待审批
## 1. 背景与动机
PLONDSPenguin Logistics Online Network Distribution System是 LanMountainDesktop 的文件驱动式分布式更新系统。当前 Comparator 工作流存在以下问题:
1. **产出物过于复杂**:生成 `update-{platform}.zip``plonds-filemap-{platform}.json``plonds-filemap-{platform}.json.sig``platform-summary-{platform}.json``plonds-static.zip` 等多个文件,客户端消费困难
2. **模型定义重复**`Plonds.Shared``Plonds.Core`、宿主侧、Launcher 侧各自定义独立的 DTO字段名不一致
3. **签名机制过重**RSA 签名增加了 CI 复杂度(需要管理密钥),且对文件驱动式更新系统而言 SHA256 哈希校验已足够
4. **平台覆盖不当**Linux 平台不需要 PLONDS 支持macOS 尚未接入,但代码中硬编码了三个平台
5. **工作流间 artifact 传递脆弱**Comparator → Publisher 通过 artifact 传递 `plonds-static.zip`,容易断裂
## 2. 设计目标
- 产出物精简为两个文件:`changed.zip` + `PLONDS.json`
- 去掉 RSA 签名,只用 SHA256/MD5 校验
- 只关注 Windows 平台
- 统一模型定义,消除 DTO 重复
- 保持 Comparator 和 Publisher 两个工作流的职责分离
## 3. 新产出物定义
### 3.1 changed.zip
只包含与上一版本有差异的文件action 为 `add``replace` 的文件),目录结构与部署目录一致。
### 3.2 PLONDS.json
```json
{
"formatVersion": "2.0",
"currentVersion": "1.2.0",
"previousVersion": "1.1.0",
"isFullUpdate": false,
"requiresCleanInstall": false,
"channel": "stable",
"platform": "windows-x64",
"updatedAt": "2026-05-30T12:00:00Z",
"filesMap": {
"LanMountainDesktop.exe": {
"action": "replace",
"sha256": "abc123...",
"size": 1024000
},
"LanMountainDesktop.dll": {
"action": "reuse",
"sha256": "def456...",
"size": 512000
},
"OldModule.dll": {
"action": "delete",
"sha256": "",
"size": 0
}
},
"changedFilesMap": {
"LanMountainDesktop.exe": {
"archivePath": "LanMountainDesktop.exe",
"sha256": "abc123...",
"size": 1024000
}
},
"checksums": {
"changed.zip": "md5:9a8b7c6d..."
}
}
```
### 3.3 字段语义
| 字段 | 类型 | 说明 |
|------|------|------|
| `formatVersion` | string | 协议版本,固定 `"2.0"` |
| `currentVersion` | string | 当前发布版本 |
| `previousVersion` | string | 基线版本(全量更新时为 `"0.0.0"` |
| `isFullUpdate` | bool | 是否为全量更新(找不到基线版本时为 true |
| `requiresCleanInstall` | bool | 启动器是否也更新了——如果是,客户端不走增量流程,让用户重新运行安装器 |
| `channel` | string | 更新通道:`stable``preview` |
| `platform` | string | 平台标识:`windows-x64` |
| `updatedAt` | string | ISO 8601 时间戳 |
| `filesMap` | object | 全量文件图:每个文件的 action + sha256 + size |
| `changedFilesMap` | object | 变更文件图:只包含需要从 changed.zip 解压的文件 |
| `checksums` | object | 产出物的 MD5 值 |
### 3.4 filesMap 中 action 的值
| Action | 含义 | changed.zip 中是否包含 |
|--------|------|----------------------|
| `add` | 新增文件 | ✅ |
| `replace` | 替换文件 | ✅ |
| `reuse` | 复用上一版本文件 | ❌ |
| `delete` | 删除文件 | ❌ |
### 3.5 requiresCleanInstall 判断逻辑
比较 `LanMountainDesktop.Launcher.exe` 在当前版本和基线版本中的 SHA256
- 如果 SHA256 不同 → `requiresCleanInstall = true`
- 如果 SHA256 相同或没有基线版本 → `requiresCleanInstall = false`
## 4. Plonds.Tool build-delta 命令改造
### 4.1 新命令签名
```
build-delta --platform <platform>
--current-version <version>
--current-zip <file>
--output-dir <dir>
--channel <channel>
[--baseline-version <version>]
[--baseline-zip <file>]
[--launcher-path <relative-path>]
```
### 4.2 参数说明
| 参数 | 必需 | 说明 |
|------|------|------|
| `--platform` | 是 | 平台标识,如 `windows-x64` |
| `--current-version` | 是 | 当前发布版本号 |
| `--current-zip` | 是 | 当前版本的 payload zip 路径 |
| `--output-dir` | 是 | 输出目录 |
| `--channel` | 是 | 更新通道 |
| `--baseline-version` | 否 | 基线版本号(省略则视为全量更新) |
| `--baseline-zip` | 否 | 基线版本的 payload zip 路径(省略则视为全量更新) |
| `--launcher-path` | 否 | Launcher 可执行文件的相对路径,默认 `LanMountainDesktop.Launcher.exe` |
### 4.3 删除的参数
| 参数 | 原因 |
|------|------|
| `--current-tag` | 不再需要version 就够了 |
| `--private-key` | 去掉签名 |
| `--is-full-payload` | 自动判断:没有 baseline-zip 就是全量 |
| `--static-output-dir` | 不再生成 S3 静态布局 |
| `--update-base-url` | 不再生成 S3 URL |
| `--baseline-tag` | 不再需要 |
### 4.4 内部逻辑
```
1. 解压 current-zip → currentDir
2. 如果有 baseline-zip → 解压 → baselineDir
否则 → baselineDir = 空(全量更新)
3. 扫描 currentDir → 计算 SHA256
4. 扫描 baselineDir → 计算 SHA256如果有
5. 对比生成 filesMap:
- 两个版本都有且 SHA256 相同 → reuse
- 两个版本都有但 SHA256 不同 → replace
- 只在新版本中存在 → add
- 只在旧版本中存在 → delete
6. 从 filesMap 提取 changedFilesMap:
- 只包含 action=add/replace 的条目
- 添加 archivePath在 changed.zip 中的路径)
7. 打包 changed.zip:
- 只包含 add/replace 的文件
- 保持原始目录结构
8. 判断 requiresCleanInstall:
- 比较 Launcher 可执行文件在两个版本中的 SHA256
- 如果不同 → requiresCleanInstall=true
9. 计算 changed.zip 的 MD5
10. 生成 PLONDS.json
11. 输出到 output-dir:
- changed.zip
- PLONDS.json
```
### 4.5 不再生成的产物
| 旧产物 | 处置 |
|--------|------|
| `update-{platform}.zip` | 被 `changed.zip` 替代 |
| `plonds-filemap-{platform}.json` | 被 `PLONDS.json` 替代 |
| `plonds-filemap-{platform}.json.sig` | 去掉签名 |
| `platform-summary-{platform}.json` | 不再需要 |
| `plonds-static.zip` | 不再生成 S3 静态布局 |
| `meta/channels/...` | 不再由 Tool 生成,由 Publisher 负责 |
## 5. Plonds.Shared 模型改造
### 5.1 删除的模型
| 模型 | 原因 |
|------|------|
| `PlondsFileMap` | 被新的 `PlondsManifest` 替代 |
| `PlondsFileEntry` | 被新的 `PlondsFileEntry` 替代 |
| `PlondsComponent` | 不再有组件概念 |
| `PlondsDistributionInfo` | 不再生成分发文档 |
| `PlondsChannelPointer` | 由 Publisher 用脚本生成 |
| `PlondsReleaseManifest` | 不再需要 |
| `PlondsReleasePlatformEntry` | 不再需要 |
| `PlondsSignatureDescriptor` | 去掉签名 |
| `PlondsMirrorAsset` | 由 Publisher 处理 |
| `PlondsMirrorEntry` | 由 Publisher 处理 |
| `PlondsMetadataCatalog` | 不再需要 |
| `PlondsAssetEntry` | 不再需要 |
### 5.2 新模型定义
```csharp
// PlondsManifest — 对应 PLONDS.json
public sealed record PlondsManifest(
string FormatVersion,
string CurrentVersion,
string PreviousVersion,
bool IsFullUpdate,
bool RequiresCleanInstall,
string Channel,
string Platform,
DateTimeOffset UpdatedAt,
IReadOnlyDictionary<string, PlondsFileEntry> FilesMap,
IReadOnlyDictionary<string, PlondsChangedFileEntry> ChangedFilesMap,
IReadOnlyDictionary<string, string> Checksums);
// PlondsFileEntry — filesMap 中的条目
public sealed record PlondsFileEntry(
string Action, // add | replace | reuse | delete
string Sha256,
long Size);
// PlondsChangedFileEntry — changedFilesMap 中的条目
public sealed record PlondsChangedFileEntry(
string ArchivePath, // 在 changed.zip 中的路径
string Sha256,
long Size);
```
### 5.3 设计决策
- `FilesMap``ChangedFilesMap``IReadOnlyDictionary<string, T>` 而非 `IReadOnlyList<T>`key 就是文件相对路径,查找 O(1)
- 去掉 `Component` 概念——当前只有一个 `app` 组件,分层没有实际意义
- `FormatVersion` 固定为 `"2.0"`,与旧格式区分
## 6. Comparator 工作流改造
### 6.1 保留两个工作流
- **Comparator**`plonds-comparator.yml`):比较文件生成器,只负责生成 `changed.zip` + `PLONDS.json`
- **Publisher**`plonds-publisher.yml`,原 `plonds-uploader.yml`):发布器,负责上传到 S3 和生成 channel pointer
### 6.2 Comparator 改造后步骤
```yaml
# plonds-comparator.yml
触发: release.published / release.prereleased / workflow_dispatch
jobs:
compare:
runs-on: ubuntu-latest
steps:
- Checkout
- 解析发布上下文
→ RELEASE_TAG, RELEASE_VERSION, RELEASE_CHANNEL
- Setup .NET
- 构建 PLONDS Tool
- 解析基线版本
→ 查找上一个同频道 Release
→ 如果有 → 记录 baseline_tag, baseline_version
→ 如果没有 → is_full_update=true
- 下载 payload zips
→ 下载当前版本 files-windows-x64.zip
→ 下载基线版本 files-windows-x64.zip (如果有)
- 运行 build-delta
→ dotnet run Plonds.Tool -- build-delta \
--platform windows-x64 \
--current-version $VERSION \
--current-zip files-windows-x64.zip \
--output-dir plonds-output \
--channel $CHANNEL \
[--baseline-version $BASELINE_VERSION] \
[--baseline-zip baseline-files-windows-x64.zip]
- 上传到 GitHub Release
→ gh release upload changed.zip PLONDS.json
- 传递元数据给 Publisher
→ 上传 artifact: plonds-run-metadata (tag.txt)
```
### 6.3 与当前步骤的差异
| 当前步骤 | 改造后 |
|---------|--------|
| 准备签名密钥 | ❌ 删除 |
| 解析基线计划 (pwsh三平台) | ✅ 简化:只找 Windows逻辑简化 |
| 下载 payload zips (pwsh三平台) | ✅ 简化:只下载 Windows |
| 构建增量资产 (pwsh含 build-index + 静态布局验证 + plonds-static.zip 打包) | ✅ 简化:只调用 build-delta |
| 上传 PLONDS assets 到 release | ✅ 简化:只上传 changed.zip + PLONDS.json |
| 传递元数据 | ✅ 保留,但 artifact 内容简化 |
## 7. 双模式差分生成
### 7.1 概述
Comparator 支持两种差分生成方法,通过 `workflow_dispatch``compare_method` 输入项选择:
| 方法 | 标识 | 核心思路 |
|------|------|---------|
| 方法一 | `file-compare` | 下载两个版本的 files zip全量文件哈希对比 |
| 方法二 | `commit-analyze` | 分析两个版本之间的 git commit映射源码变更到产物文件 |
### 7.2 GitHub Actions 触发器新增输入项
```yaml
workflow_dispatch:
inputs:
tag: ...
baseline_tag: ...
channel: ...
compare_method: # 新增
description: '比较方法'
type: choice
default: file-compare
options:
- file-compare
- commit-analyze
hash_algorithm: # 新增(仅方法一)
description: '哈希算法'
type: choice
default: sha256
options:
- sha256
- md5
```
当由 `release` 事件触发时,默认使用 `file-compare` + `sha256`
### 7.3 方法一文件对比模式file-compare
**流程:**
```
1. 下载当前版本 files-windows-x64.zip
2. 下载基线版本 files-windows-x64.zip如果有
3. 解压两个 zip 到临时目录
4. 用指定哈希算法sha256/md5扫描两个目录的所有文件
5. 对比哈希值,生成 filesMapadd/replace/reuse/delete
6. 从当前版本目录中提取 add/replace 的文件 → changed.zip
7. 生成 PLONDS.json
```
**PlondsDeltaBuildOptions 新增参数:**
```csharp
string HashAlgorithm = "sha256" // "sha256" | "md5"
```
**哈希算法对 PLONDS.json 的影响:**
- `sha256``filesMap``changedFilesMap` 中使用 `sha256` 字段
- `md5``filesMap``changedFilesMap` 中使用 `md5` 字段
### 7.4 方法二Commit 分析模式commit-analyze
**流程:**
```
1. 下载当前版本 files-windows-x64.zip
2. 解压到临时目录
3. git log --name-only baseline_tag..current_tag
→ 得到两个版本之间的 commit 列表和涉及的源码文件
4. 过滤:只保留源码目录下的文件
5. 用简单规则映射源码文件到产物文件
6. 从当前版本的解压目录中提取映射到的产物文件 → changed.zip
7. 生成 PLONDS.json
8. 如果没有源码变更 → 自动回退到方法一
```
**源码目录过滤规则:**
只分析以下目录下的文件变更:
| 目录 | 说明 |
|------|------|
| `LanMountainDesktop/` | 主宿主应用 |
| `LanMountainDesktop.Launcher/` | 启动器 |
| `LanMountainDesktop.Shared.Contracts/` | 共享契约 |
| `LanMountainDesktop.PluginSdk/` | 插件 SDK |
| `LanMountainDesktop.Appearance/` | 外观系统 |
| `LanMountainDesktop.Settings.Core/` | 设置核心 |
| `LanMountainDesktop.ComponentSystem/` | 组件系统 |
忽略的目录:`docs/``scripts/``.github/``.trae/``PenguinLogisticsOnlineNetworkDistributionSystem/`
**源码到产物的映射规则:**
| 源码路径模式 | 映射到产物文件 |
|-------------|--------------|
| `LanMountainDesktop/**/*.{cs,axaml,xaml}` | `LanMountainDesktop.dll`, `LanMountainDesktop.exe` |
| `LanMountainDesktop.Launcher/**/*.{cs,axaml,xaml}` | `LanMountainDesktop.Launcher.exe` |
| `LanMountainDesktop.Shared.Contracts/**/*.cs` | `LanMountainDesktop.Shared.Contracts.dll` |
| `LanMountainDesktop.PluginSdk/**/*.cs` | `LanMountainDesktop.PluginSdk.dll` |
| `LanMountainDesktop.Appearance/**/*.cs` | `LanMountainDesktop.Appearance.dll` |
| `LanMountainDesktop.Settings.Core/**/*.cs` | `LanMountainDesktop.Settings.Core.dll` |
| `LanMountainDesktop.ComponentSystem/**/*.cs` | `LanMountainDesktop.ComponentSystem.dll` |
| `**/*.json`(配置文件) | 同路径的 .json |
| 其他无法映射的变更 | 保守标记 → 所有核心 .dll/.exe |
**方法二在 Plonds.Tool 中的新命令:**
```
build-delta-from-commits --platform <platform>
--current-version <version>
--current-zip <file>
--output-dir <dir>
--channel <channel>
--baseline-tag <tag>
--current-tag <tag>
[--source-dirs <dir1,dir2,...>]
[--fallback-zip <file>]
```
| 参数 | 必需 | 说明 |
|------|------|------|
| `--platform` | 是 | 平台标识 |
| `--current-version` | 是 | 当前发布版本号 |
| `--current-zip` | 是 | 当前版本的 payload zip |
| `--output-dir` | 是 | 输出目录 |
| `--channel` | 是 | 更新通道 |
| `--baseline-tag` | 是 | 基线版本的 git tag |
| `--current-tag` | 是 | 当前版本的 git tag |
| `--source-dirs` | 否 | 要分析的源码目录列表(逗号分隔) |
| `--fallback-zip` | 否 | 回退到方法一时使用的基线 zip |
**回退逻辑:**
如果 `git log` 分析后发现没有源码目录下的文件变更(比如只有 docs/ 变更),则自动回退到方法一:
1. 如果提供了 `--fallback-zip` → 用方法一对比两个 zip
2. 如果没有提供 → 生成全量更新(`isFullUpdate=true`
### 7.5 方法二的 PLONDS.json 特殊处理
方法二无法像方法一那样生成完整的 `filesMap`(因为不知道哪些文件是 reuse 的),因此:
- `filesMap` 只包含映射到的变更文件(标记为 `add``replace`
- 不包含 `reuse``delete` 条目
- `isFullUpdate` 始终为 `false`(除非回退到方法一且无基线)
- `requiresCleanInstall` 根据 Launcher.exe 是否在映射到的变更文件列表中判断
### 7.6 工作流中的条件分支
```yaml
- name: Run build-delta
shell: bash
run: |
if [[ "$COMPARE_METHOD" == "commit-analyze" ]]; then
# 方法二
dotnet run --project ... -- build-delta-from-commits \
--platform windows-x64 \
--current-version $RELEASE_VERSION \
--current-zip $PWD/plonds-input/current-files-windows-x64.zip \
--output-dir $PWD/plonds-output \
--channel $RELEASE_CHANNEL \
--baseline-tag $BASELINE_TAG \
--current-tag $RELEASE_TAG \
--fallback-zip $PWD/plonds-input/baseline-files-windows-x64.zip
else
# 方法一
dotnet run --project ... -- build-delta \
--platform windows-x64 \
--current-version $RELEASE_VERSION \
--current-zip $PWD/plonds-input/current-files-windows-x64.zip \
--output-dir $PWD/plonds-output \
--channel $RELEASE_CHANNEL \
--hash-algorithm $HASH_ALGORITHM \
--baseline-version $BASELINE_VERSION \
--baseline-zip $PWD/plonds-input/baseline-files-windows-x64.zip
fi
```
方法二时,基线 zip 仍然需要下载(用于回退),但不需要解压(除非回退)。
### 7.7 两种方法的步骤差异
| 步骤 | 方法一 (file-compare) | 方法二 (commit-analyze) |
|------|----------------------|------------------------|
| 下载基线 zip | ✅ 需要 | ✅ 需要(用于回退) |
| 下载当前 zip | ✅ | ✅ |
| 解压两个 zip | ✅ | ✅ 只解压当前(回退时解压基线) |
| git diff/log | ❌ | ✅ 需要 fetch-depth:0 |
| 哈希对比 | ✅ 两个目录全量扫描 | ❌ 不做(除非回退) |
| 源码→产物映射 | ❌ | ✅ |
| 回退逻辑 | ❌ | ✅ 无源码变更时回退方法一 |
## 8. 不在本次改造范围内的事项
- Publisher 工作流改造(后续单独设计)
- Rollback 工作流改造(后续单独设计)
- 宿主侧客户端代码改造PlondsUpdateApplier 等,后续单独设计)
- Launcher 侧客户端代码改造(后续单独设计)
- Plonds.Api 项目处置(后续决定是否保留)
- `build-index``build-plonds``generate``publish``sign``pack-payload` 等 Tool 命令的清理(后续处理)