From 37e883ef95ec7dff68ab83ff4053dee0d57f5d01 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Thu, 4 Jun 2026 11:20:27 +0800 Subject: [PATCH 1/4] docs: update Core & Differential docs and sync i18n across all locales - Expand SetSource signature to include all 9 parameters (authScheme, basicUsername, basicPassword, installPath) - Add AuthScheme, BasicUsername, BasicPassword fields to UpdateRequest config table - Add SetAuthScheme, SetBasicUsername, SetBasicPassword methods to UpdateRequestBuilder - Update BrotliCompressionProvider description to reflect conditional compilation implementation - Sync all changes to English (i18n/en) and Chinese (i18n/zh-Hans) translations - Replace outdated zh-Hans i18n overrides with current structured API reference format Co-Authored-By: Claude Opus 4.8 --- website/docs/doc/GeneralUpdate.Core.md | 5 +- .../docs/doc/GeneralUpdate.Differential.md | 6 +- .../current/doc/GeneralUpdate.Core.md | 7 +- .../current/doc/GeneralUpdate.Differential.md | 4 +- .../current/doc/GeneralUpdate.Core.md | 1714 ++++++++--------- .../current/doc/GeneralUpdate.Differential.md | 583 +++--- 6 files changed, 1029 insertions(+), 1290 deletions(-) diff --git a/website/docs/doc/GeneralUpdate.Core.md b/website/docs/doc/GeneralUpdate.Core.md index bf08797..fd3f600 100644 --- a/website/docs/doc/GeneralUpdate.Core.md +++ b/website/docs/doc/GeneralUpdate.Core.md @@ -143,7 +143,7 @@ sidebar_position: 5 | `Cancel()` | 无 | `void` | UI 中提供"取消更新"按钮 | 触发内部 `CancellationTokenSource` | | `SetConfig(UpdateRequest)` | `configInfo` — 更新配置对象 | `GeneralUpdateBootstrap` | 主程序内显式配置更新参数 | 会调用 `Validate()` 检查关键字段 | | `SetConfig(string)` | `filePath` — JSON 配置文件路径 | `GeneralUpdateBootstrap` | 从文件读取更新配置 | 支持相对路径和绝对路径;UTF-8 JSON 格式 | -| `SetSource(...)` | `updateUrl`, `appSecretKey`, `reportUrl?`, `scheme?`, `token?` | `GeneralUpdateBootstrap` | 轻配置入口,配合 manifest 使用 | 只提供服务端入口和密钥,身份信息由 manifest 补齐 | +| `SetSource(...)` | `updateUrl`(更新检查 API 地址)
`appSecretKey`(应用密钥)
`reportUrl?`(可选上报地址)
`scheme?`(可选认证方案,如 `"Bearer"`)
`token?`(可选认证令牌)
`authScheme?`(可选认证方案枚举,默认 `Hmac`)
`basicUsername?`(可选 Basic 认证用户名)
`basicPassword?`(可选 Basic 认证密码)
`installPath?`(可选安装路径) | `GeneralUpdateBootstrap` | 轻配置入口,配合 manifest 使用 | 只提供服务端入口和密钥,身份信息由 manifest 补齐;支持 HMAC / Bearer / Basic 多种认证方式 | | `SetOption(Option, T)` | `option` — 选项键, `value` — 选项值 | `GeneralUpdateBootstrap` | 设置运行时选项 | 传入 `null` 给可空选项会移除当前设置 | | `UseDiffPipeline(Action)` | `configure` — 差分管道配置委托 | `GeneralUpdateBootstrap` | 替换或调整差分补丁管道 | 未调用时使用默认配置 | | `AddListenerUpdateInfo(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 接收服务端版本信息 | 无更新时也会触发(`Info.Code = 404`) | @@ -197,6 +197,9 @@ sidebar_position: 5 | `SetDriverDirectory(string)` | `path` | `UpdateRequestBuilder` | 设置驱动目录 | — | | `SetScheme(string)` | `scheme` | `UpdateRequestBuilder` | 设置认证方案 | — | | `SetToken(string)` | `token` | `UpdateRequestBuilder` | 设置认证令牌 | — | +| `SetAuthScheme(AuthScheme)` | `authScheme` — 认证方案枚举 | `UpdateRequestBuilder` | 设置认证方案(Hmac / Bearer / ApiKey / Basic) | 默认 `Hmac` | +| `SetBasicUsername(string)` | `username` | `UpdateRequestBuilder` | 设置 Basic 认证用户名 | 需配合 `SetAuthScheme(AuthScheme.Basic)` | +| `SetBasicPassword(string)` | `password` | `UpdateRequestBuilder` | 设置 Basic 认证密码 | 需配合 `SetAuthScheme(AuthScheme.Basic)` | | `SetFiles(List)` | `files` | `UpdateRequestBuilder` | 设置跳过文件列表 | — | | `SetFormats(List)` | `formats` | `UpdateRequestBuilder` | 设置跳过扩展名列表 | — | | `SetDirectories(List)` | `dirs` | `UpdateRequestBuilder` | 设置跳过目录列表 | — | diff --git a/website/docs/doc/GeneralUpdate.Differential.md b/website/docs/doc/GeneralUpdate.Differential.md index df623d3..c78d73f 100644 --- a/website/docs/doc/GeneralUpdate.Differential.md +++ b/website/docs/doc/GeneralUpdate.Differential.md @@ -10,7 +10,7 @@ sidebar_position: 6 ### 1.1 组件概述 -**GeneralUpdate.Differential** 是 GeneralUpdate 的二进制差分组件,专注解决"一个旧文件 + 一个补丁文件 = 一个新文件"的问题。它提供可替换的文件级差分算法(BSDIFF 4.0 / Streaming HDiff)、补丁压缩抽象(BZip2 / Deflate / Brotli 预留)和 BSDIFF 兼容补丁读写能力。 +**GeneralUpdate.Differential** 是 GeneralUpdate 的二进制差分组件,专注解决"一个旧文件 + 一个补丁文件 = 一个新文件"的问题。它提供可替换的文件级差分算法(BSDIFF 4.0 / Streaming HDiff)、补丁压缩抽象(BZip2 / Deflate,源码中预留 .NET 6+ Brotli)和 BSDIFF 兼容补丁读写能力。 目录级对比、批量补丁生成、并行调度、删除文件处理和更新流程编排由 `GeneralUpdate.Core` 的 `DiffPipeline` 或 `GeneralUpdate.Tools` 承担。 @@ -21,7 +21,7 @@ sidebar_position: 6 | 文件级差分生成 | `CleanAsync(oldFile, newFile, patchFile)` — 对比新旧文件生成 `.patch` 补丁 | | 文件级差分应用 | `DirtyAsync(oldFile, newFile, patchFile)` — 旧文件 + 补丁 → 新文件 | | 可替换差分算法 | `BsdiffDiffer`(BSDIFF 4.0,后缀排序)和 `StreamingHdiffDiffer`(块哈希索引) | -| 可替换压缩格式 | BZip2 (0x00)、Deflate (0x01),源码中预留 .NET 6+ Brotli (0x02) | +| 可替换压缩格式 | BZip2 (0x00)、Deflate (0x01),源码中通过 `#if NET6_0_OR_GREATER` 条件编译预留 Brotli (0x02) | | BSDIFF 兼容格式 | 写入 33 字节扩展头(32 字节 BSDIFF40 + 1 字节压缩格式),兼容 32 字节旧头 | | 线程安全 | 内置 differ 和压缩提供器均支持并发调用 | @@ -93,7 +93,7 @@ Differential 本身是底层库,不提供配置类。所有参数通过构造 | --- | --- | --- | --- | | `BZip2CompressionProvider` | `0x00` | 完全可用 | BSDIFF 旧补丁兼容,解压成本较高 | | `DeflateCompressionProvider` | `0x01` | 完全可用 | 解压速度更友好,适合客户端批量应用 | -| `BrotliCompressionProvider` | `0x02` | 仅 .NET 6+ 编译(源码预留) | 生产不建议使用 | +| `BrotliCompressionProvider` | `0x02` | 仅 .NET 6+ 编译(源码中为完整实现,通过 `#if NET6_0_OR_GREATER` 条件编译) | 当前 `netstandard2.0` 包中不包含,生产不建议使用 | ### 3.2 实例方法 diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md index b79af76..12964f4 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md @@ -98,8 +98,11 @@ sidebar_position: 5 | `ProductId` | `string` | — | Optional | — | Product identifier for multi-product servers | | `UpdatePath` | `string` | `InstallPath` | Optional | Valid directory path | Upgrade process location | | `Bowl` | `string` | `null` | Optional | Valid filename | Auxiliary process name to close before update | -| `Scheme` | `string` | `null` | Optional | `"Bearer"` etc. | Auth scheme | +| `Scheme` | `string` | `null` | Optional | `"Bearer"` etc. | Auth scheme (used with `Token`) | | `Token` | `string` | `null` | Optional | — | Auth token | +| `AuthScheme` | `AuthScheme` | `Hmac` | Optional | `Hmac`, `Bearer`, `Basic` | Auth scheme enum; `Hmac` for GeneralSpacestation server-side signed auth | +| `BasicUsername` | `string` | `null` | Optional | — | HTTP Basic auth username (requires `AuthScheme = Basic`) | +| `BasicPassword` | `string` | `null` | Optional | — | HTTP Basic auth password (requires `AuthScheme = Basic`) | | `Files` | `List` | `null` | Optional | — | Files to skip during update | | `Formats` | `List` | `null` | Optional | — | Extensions to skip during update | | `Directories` | `List` | `null` | Optional | — | Directories to skip during update | @@ -135,7 +138,7 @@ sidebar_position: 5 | `Cancel()` | None | `void` | UI "Cancel Update" button | Triggers internal `CancellationTokenSource` | | `SetConfig(UpdateRequest)` | `configInfo` | `GeneralUpdateBootstrap` | Explicit update configuration | Calls `Validate()` on key fields | | `SetConfig(string)` | `filePath` — JSON config file path | `GeneralUpdateBootstrap` | Read config from file | Supports relative/absolute paths; UTF-8 JSON | -| `SetSource(...)` | `updateUrl`, `appSecretKey`, `reportUrl?`, `scheme?`, `token?` | `GeneralUpdateBootstrap` | Lightweight entry with manifest | Identity info filled by manifest | +| `SetSource(...)` | `updateUrl` (version check API URL)
`appSecretKey` (app secret key)
`reportUrl?` (optional report URL)
`scheme?` (optional auth scheme, e.g., `"Bearer"`)
`token?` (optional auth token)
`authScheme?` (optional auth scheme enum, default `Hmac`)
`basicUsername?` (optional Basic auth username)
`basicPassword?` (optional Basic auth password)
`installPath?` (optional install path) | `GeneralUpdateBootstrap` | Lightweight entry with manifest | Identity info filled by manifest; supports HMAC / Bearer / Basic auth methods | | `SetOption(Option, T)` | `option` — key, `value` — value | `GeneralUpdateBootstrap` | Set runtime options | Pass `null` to reset nullable options | | `UseDiffPipeline(Action)` | `configure` — delegate | `GeneralUpdateBootstrap` | Replace or tune diff pipeline | Default used if not called | | `AddListenerUpdateInfo(...)` | `EventHandler` | `GeneralUpdateBootstrap` | Receive server version info | Also fires when no update available | diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md index ae1d454..ab1b32f 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md @@ -21,7 +21,7 @@ Directory-level comparison, batch patch generation, parallel scheduling, deleted | File-Level Diff Generation | `CleanAsync(oldFile, newFile, patchFile)` — compare old & new files to generate `.patch` | | File-Level Diff Application | `DirtyAsync(oldFile, newFile, patchFile)` — old file + patch → new file | | Pluggable Diff Algorithms | `BsdiffDiffer` (BSDIFF 4.0, suffix sort) and `StreamingHdiffDiffer` (block hash indexing) | -| Pluggable Compression | BZip2 (0x00), Deflate (0x01), .NET 6+ Brotli (0x02) reserved in source | +| Pluggable Compression | BZip2 (0x00), Deflate (0x01), .NET 6+ Brotli (0x02) available via conditional compilation | | BSDIFF Compatible Format | 33-byte extended header (32-byte BSDIFF40 + 1-byte compression format), 32-byte legacy compatible | | Thread Safety | Built-in differ and compression providers support concurrent calls | @@ -87,7 +87,7 @@ Differential is a low-level library with no configuration classes. All parameter | --- | --- | --- | --- | | `BZip2CompressionProvider` | `0x00` | Fully available | Legacy BSDIFF compatible, higher decompression cost | | `DeflateCompressionProvider` | `0x01` | Fully available | Faster decompression, better for client batch apply | -| `BrotliCompressionProvider` | `0x02` | .NET 6+ only (source reserved) | Not recommended for production | +| `BrotliCompressionProvider` | `0x02` | .NET 6+ only (full implementation in source behind `#if NET6_0_OR_GREATER` conditional compilation) | Not included in current `netstandard2.0` package; not recommended for production | ### 3.2 Instance Methods diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md index 556adc1..fd3f600 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md @@ -6,548 +6,699 @@ sidebar_position: 5 **命名空间:** `GeneralUpdate.Core` | **主要入口:** `GeneralUpdateBootstrap` | **NuGet 包:** `GeneralUpdate.Core` -`GeneralUpdate.Core` 是 GeneralUpdate 的更新执行核心,重点提供可编程的启动器、配置模型、事件模型、下载子系统扩展点、生命周期钩子、状态上报、差分管道和平台策略扩展。本页聚焦组件 API、属性和扩展方式;完整端到端上手流程会放到 cookbook 中。 +## 1. 组件简介 -## 文档大纲与知识点导航 {#knowledge-map} +### 1.1 组件概述 -如果你是第一次阅读 Core 文档,可以先看这个导航,再跳到对应知识点。本文按照“入口与配置 -> 执行策略 -> 差分/下载/并发 -> 扩展点 -> 工具链关系”的顺序组织。 +**GeneralUpdate.Core** 是 GeneralUpdate 生态的更新执行核心引擎,负责客户端应用的全生命周期更新管理。它提供可编程的启动器、配置模型、事件通知系统、下载子系统(支持并发、断点续传、重试、校验、后处理管道)、差分补丁管道、版本回写、IPC 进程通信以及平台策略扩展。 -| 你想了解什么 | 推荐阅读 | -| --- | --- | -| Core 到底负责什么、不负责什么 | [组件能力边界](#组件能力边界) | -| 如何启动一次标准更新 | [入口类:GeneralUpdateBootstrap](#入口类generalupdatebootstrap)、[标准更新策略](#standard-update-strategy) | -| 如何用极简配置接入更新 | [generalupdate.manifest.json](#应用身份清单generalupdatemanifestjson)、[极简配置理念](#极简配置理念)、[配合引导类使用](#配合引导类使用) | -| `Client`、`Upgrade`、`OssClient`、`OssUpgrade` 有什么区别 | [执行策略总览](#execution-strategies) | -| 静默更新到底什么时候下载、什么时候替换 | [静默更新策略](#silent-update-strategy) | -| 差分算法有哪些,如何选择 | [差分算法与补丁管道](#differential-pipeline) | -| 下载和差分如何并发、多线程 | [下载并发与差分并行](#download-diff-concurrency) | -| 如何接收更新过程事件通知 | [事件 API](#事件-api) | -| 如何关闭日志降低性能损耗 | [日志与性能](#logging-performance) | -| 如何扩展下载、校验、认证、Hook 或平台策略 | [扩展点总览](#扩展点总览) | -| Tools 生成的产物在 Core 中怎么消费 | [与 GeneralUpdate.Tools 的关系](#与-generalupdatetools-的关系) | - -## 组件能力边界 - -Core 负责“执行更新”,不负责生成更新包,也不直接管理服务端后台。 - -| 能力 | Core 是否负责 | 说明 | -| --- | --- | --- | -| 读取更新配置 | 是 | 通过 `UpdateRequest`、配置文件、`SetSource` 或 IPC 恢复运行参数。 | -| 检查服务端版本 | 是 | `Client` / `OssClient` 角色会读取版本清单并生成下载计划。 | -| 下载更新包 | 是 | 可替换下载来源、执行器、重试策略、后处理管道或完整编排器。 | -| 校验与应用补丁 | 是 | 支持 Hash 校验、压缩包处理、差分补丁管道。 | -| 文件替换与重启应用 | 是 | `Upgrade` / `OssUpgrade` 角色用于独立升级程序。 | -| 生成差分包 | 否 | 推荐使用 `GeneralUpdate.Tools`。 | - -## 入口类:GeneralUpdateBootstrap - -`GeneralUpdateBootstrap` 是 Core 的主要门面类。它继承 `AbstractBootstrap`,因此同时拥有自身方法和基类提供的扩展注册方法。 +**核心能力:** -```csharp -using GeneralUpdate.Core; - -var bootstrap = new GeneralUpdateBootstrap(); -``` +| 能力 | 说明 | +| --- | --- | +| 多策略更新执行 | 内置标准 Client/Upgrade 更新、OSS 对象存储更新、静默后台轮询更新三种执行策略 | +| 配置驱动 | 通过 `UpdateRequest` 强类型配置或 `SetSource` 轻配置入口,配合 `generalupdate.manifest.json` 实现极简接入 | +| 下载子系统 | 可替换的下载来源、执行器、重试策略、后处理管道和批量编排器,默认支持并发下载、断点续传、SHA256 校验 | +| 差分补丁管道 | 文件级二进制差分(BSDIFF 4.0 / Streaming HDiff),目录级对比与批量补丁分发,支持并行处理 | +| 事件通知 | 7 种事件回调(版本发现、下载进度、完成、错误、异常等),支持批量事件监听器注册 | +| 扩展点体系 | 10 个可替换接口:生命周期钩子、状态上报、SSL 证书策略、HTTP 认证、下载来源/策略/执行器/管道/编排器、平台策略 | +| 版本清单体系 | `generalupdate.manifest.json` 自动发现应用身份,更新后自动回写本地版本,无需业务代码维护版本号 | +| IPC 进程通信 | 主程序与升级程序之间通过加密文件传递更新上下文,保障升级流程的原子性和安全性 | +| SignalR 实时推送 | 基于 SignalR 的版本更新实时推送(`UpgradeHubService`),支持点对点和广播推送、自动重连、多事件订阅 | + +**解决的业务痛点:** +- 桌面应用需要可靠的自动更新能力,但手写更新逻辑涉及版本对比、下载、校验、解压、文件替换、进程重启等多个复杂环节 +- 大型应用的分发包体积大,全量更新带宽成本高,需要差分更新降低下载量 +- 需要灵活的更新策略(静默后台、用户手动触发、OSS/CDN 分发) +- 升级程序版本独立演进,需要主程序和升级程序的版本协调 +- 多产品线需要统一的更新框架,减少重复开发 + +**业务使用场景:** +- WPF / WinForms / Avalonia / WinUI 桌面应用的自动更新 +- 企业内部工具的统一版本管理 +- 通过 CDN / OSS 分发更新包的客户端应用 +- 需要差分更新降低带宽消耗的大型客户端 + +### 1.2 环境与依赖 + +| 项目 | 说明 | +| --- | --- | +| **版本** | `10.5.0-beta.2` | +| **目标框架** | `netstandard2.0`; `net8.0`; `net10.0`(兼容 .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+;`net8.0`+ 支持 AOT/Trim) | +| **依赖包** | `GeneralUpdate.Differential`(差分算法)、`System.Text.Json`、`Microsoft.AspNetCore.SignalR.Client` | +| **兼容性** | Windows / Linux / macOS;支持 x86 / x64 / ARM64 | -### 方法总览 +--- -| 方法 | 用途 | 常用场景 | -| --- | --- | --- | -| `LaunchAsync()` | 按当前 `Option.AppType` 启动更新流程。 | 所有 Core 使用场景最终都会调用。 | -| `Cancel()` | 请求取消当前更新操作。 | UI 中提供“取消更新”按钮。 | -| `SetConfig(UpdateRequest)` | 使用强类型对象配置更新。 | 主程序内显式配置更新参数。 | -| `SetConfig(string)` | 从 JSON 文件读取 `UpdateRequest`。 | 将更新参数放到 `update_config.json` 或自定义配置文件。 | -| `SetSource(...)` | 只提供更新地址、密钥、报告地址等基础参数。 | 零配置/轻配置入口。 | -| `SetOption(Option, T)` | 设置运行时选项。 | 设置角色、超时、并发、差分、静默更新等。 | -| `UseDiffPipeline(Action)` | 自定义差分补丁管道。 | 替换 differ、调整并行度、接入补丁进度。 | -| `AddListenerUpdatePrecheck(Func)` | 下载前预检查。 | 检查磁盘空间、网络状态或弹窗确认。 | -| `AddListener...` | 注册单个事件回调。 | 更新 UI、写日志、上报监控。 | -| `AddEventListener()` | 批量注册事件监听器。 | 将事件处理封装成类。 | - -### LaunchAsync +## 2. 组件功能列表 -```csharp -public Task LaunchAsync() -``` +| 功能名称 | 功能描述 | 类型 | 是否必填 | 备注限制 | +| --- | --- | --- | --- | --- | +| 标准 Client 更新 | 主程序检查版本、下载更新包、启动升级程序替换文件 | 基础 | 必选 | 需要服务端版本检查 API | +| 标准 Upgrade 更新 | 独立升级程序读取 IPC 上下文并执行文件替换、差分补丁、版本回写 | 基础 | 必选 | 由主程序启动,通过加密 IPC 传递上下文 | +| OSS Client 更新 | 从 OSS/CDN 下载版本配置,对比后启动升级程序 | 基础 | 可选 | 版本配置文件托管在对象存储,不依赖服务端 API | +| OSS Upgrade 更新 | OSS 模式下的升级程序,下载并解压资源包 | 基础 | 可选 | 配合 OssClient 使用 | +| 静默后台更新 | 后台轮询版本、静默下载、进程退出时触发升级 | 基础 | 可选 | 需设置 `Option.Silent = true` | +| 差分补丁管道 | 文件级二进制差分生成与应用,目录级批量补丁分发 | 基础 | 可选 | 需要 `Option.PatchEnabled = true` | +| 并发下载 | 多资源包并发下载,支持断点续传和 SHA256 校验 | 基础 | 可选 | 通过 `Option.MaxConcurrency` 控制 | +| 事件通知回调 | 版本发现、下载进度、完成、异常等 7 种事件 | 基础 | 可选 | 通过 `AddListener*` 方法注册 | +| 应用身份清单 | `generalupdate.manifest.json` 自动发现与版本回写 | 拓展 | 推荐 | 由 `GeneralUpdate.Tools` 生成 | +| 下载来源扩展 | 自定义版本清单和下载资源来源 | 拓展 | 可选 | 实现 `IDownloadSource` | +| 下载执行器扩展 | 自定义单文件下载实现(HTTP/FTP/SFTP 等) | 拓展 | 可选 | 实现 `IDownloadExecutor` | +| 下载重试策略扩展 | 自定义重试、超时、熔断策略 | 拓展 | 可选 | 实现 `IDownloadPolicy` | +| 下载后处理管道扩展 | 下载完成后自定义校验、解密、扫描等 | 拓展 | 可选 | 实现 `IDownloadPipeline` | +| 批量下载编排扩展 | 完全替换批量下载并发控制逻辑 | 拓展 | 可选 | 实现 `IDownloadOrchestrator` | +| 生命周期钩子 | 更新前、下载后、更新后、异常、启动前的业务逻辑注入 | 拓展 | 可选 | 实现 `IUpdateHooks` | +| 状态上报扩展 | 更新状态上报到自有服务端 | 拓展 | 可选 | 实现 `IUpdateReporter` | +| HTTP 认证扩展 | 自定义 HTTP 请求认证头 | 拓展 | 可选 | 实现 `IHttpAuthProvider` | +| SSL 证书策略扩展 | 自定义 HTTPS 证书校验逻辑 | 拓展 | 可选 | 实现 `ISslValidationPolicy` | +| 平台策略扩展 | 替换平台级文件操作或启动逻辑 | 拓展 | 可选 | 实现 `IStrategy` | +| SignalR 实时推送 | 服务端主动推送版本更新通知,客户端订阅接收,支持点对点和广播推送 | 拓展 | 可选 | `UpgradeHubService`,命名空间 `GeneralUpdate.Core.Hubs` | +| 推送重连机制 | 断线自动重连(随机退避策略),连接生命周期管理 | 拓展 | 可选 | `RandomRetryPolicy` | +| 推送事件订阅 | 接收消息、在线状态、重连通知、关闭通知四种事件 | 拓展 | 可选 | 通过 `AddListener*` 方法注册 | +| 多协议认证 | 支持 HMAC-SHA256、Bearer Token、API Key、HTTP Basic 四种认证方案 | 基础 | 可选 | 通过 `AuthScheme` 枚举或 `HttpAuth()` 自定义 | +| 跨平台策略 | 内置 Windows / Linux / macOS 三种 OS 级更新策略,自动选择 | 基础 | 自动 | 根据运行时平台自动选择对应策略 | +| 文件树比对 | 新旧版本目录结构级差异对比,生成增量文件清单 | 基础 | 可选 | `FileTree` / `FileTreeDiffer` / `FileTreeComparer` | +| AOT/Trim 兼容 | `net8.0`+ 目标框架支持 AOT 发布和裁剪,含源生成 JSON 序列化上下文 | 基础 | 可选 | `JsonContext` 命名空间下 9 个序列化上下文 | -`LaunchAsync` 会读取 `Option.AppType` 并选择对应策略: +--- -| `Option.AppType` | 策略 | 说明 | -| --- | --- | --- | -| `AppType.Client` | `ClientStrategy` | 主程序侧:检查版本、下载包、准备升级上下文、启动升级程序。 | -| `AppType.Upgrade` | `UpdateStrategy` | 升级程序侧:读取 IPC 上下文并执行文件替换。 | -| `AppType.OssClient` | `OssStrategy` | OSS 主程序侧更新流程。 | -| `AppType.OssUpgrade` | `OssStrategy` | OSS 升级程序侧更新流程。 | +## 3. API 配置说明 + +### 3.1 配置字段(属性 Props) + +**UpdateRequest 配置属性:** + +| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | +| --- | --- | --- | --- | --- | --- | +| `UpdateUrl` | `string` | — | 是 | 有效绝对 URL | 更新检查 API 地址 | +| `UpdateAppName` | `string` | `"Update.exe"` | 推荐 | 有效文件名 | 升级程序文件名,如与实际不同必须显式设置 | +| `MainAppName` | `string` | — | 推荐 | 有效文件名 | 主程序文件名,用于重启和识别 | +| `ClientVersion` | `string` | — | 推荐 | SemVer 格式 | 当前主程序版本 | +| `AppSecretKey` | `string` | — | 推荐 | — | 应用密钥,用于服务端认证 | +| `InstallPath` | `string` | `AppDomain.CurrentDomain.BaseDirectory` | 可选 | 有效目录路径 | 应用安装根目录 | +| `ReportUrl` | `string` | `null` | 可选 | 有效绝对 URL | 更新状态上报 API | +| `UpdateLogUrl` | `string` | `null` | 可选 | 有效绝对 URL | 更新日志页面地址 | +| `UpgradeClientVersion` | `string` | — | 可选 | SemVer 格式 | 升级程序自身版本 | +| `ProductId` | `string` | — | 可选 | — | 产品标识,多产品时用于区分 | +| `UpdatePath` | `string` | `InstallPath` | 可选 | 有效目录路径 | 升级程序所在目录 | +| `Bowl` | `string` | `null` | 可选 | 有效文件名 | 更新前需关闭的辅助进程名 | +| `Scheme` | `string` | `null` | 可选 | `"Bearer"` 等 | 认证方案(已废弃,推荐使用 `AuthScheme`) | +| `Token` | `string` | `null` | 可选 | — | 认证令牌 | +| `AuthScheme` | `AuthScheme?` | `null` | 可选 | `Hmac`, `Bearer`, `ApiKey`, `Basic` | 认证方案枚举,设置后自动选择对应 Provider | +| `BasicUsername` | `string` | `null` | 可选 | — | HTTP Basic 认证用户名(`AuthScheme = Basic` 时必填) | +| `BasicPassword` | `string` | `null` | 可选 | — | HTTP Basic 认证密码(`AuthScheme = Basic` 时必填) | +| `Files` | `List` | `null` | 可选 | — | 更新时跳过的指定文件列表 | +| `Formats` | `List` | `null` | 可选 | — | 更新时跳过的扩展名列表 | +| `Directories` | `List` | `null` | 可选 | — | 更新时跳过的目录列表 | +| `DriverDirectory` | `string` | `null` | 可选 | 有效目录路径 | 驱动更新目录 | + +**Option 运行时选项:** + +| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | +| --- | --- | --- | --- | --- | --- | +| `Option.AppType` | `AppType` | `Client` | 是 | `Client(1)`, `Upgrade(2)`, `OssClient(3)`, `OssUpgrade(4)` | 当前进程角色 | +| `Option.DiffMode` | `DiffMode` | `Serial` | 可选 | `Serial`, `Parallel` | 下载执行模式 | +| `Option.Encoding` | `Encoding` | `UTF8` | 可选 | `Encoding` 实例 | 压缩包处理编码 | +| `Option.Format` | `Format` | `Zip` | 可选 | `Zip` | 更新包格式 | +| `Option.DownloadTimeout` | `int?` | `30` | 可选 | 正整数(秒) | 下载超时时间 | +| `Option.PatchEnabled` | `bool?` | `true` | 可选 | `true` / `false` | 是否启用差分补丁 | +| `Option.BackupEnabled` | `bool?` | `true` | 可选 | `true` / `false` | 更新前是否备份被替换文件 | +| `Option.Silent` | `bool` | `false` | 可选 | `true` / `false` | 是否启用静默轮询更新 | +| `Option.SilentPollIntervalMinutes` | `int` | `60` | 可选 | 正整数 | 静默模式轮询间隔(分钟) | +| `Option.LaunchClientAfterUpdate` | `bool` | `true` | 可选 | `true` / `false` | 升级后是否启动主程序 | +| `Option.MaxConcurrency` | `int` | `3` | 可选 | `1` ~ `ProcessorCount × 2` | 下载最大并发数 | +| `Option.EnableResume` | `bool` | `true` | 可选 | `true` / `false` | 是否启用断点续传 | +| `Option.RetryCount` | `int` | `3` | 可选 | 非负整数 | 下载重试次数 | +| `Option.VerifyChecksum` | `bool` | `true` | 可选 | `true` / `false` | 是否校验下载文件 Hash | +| `Option.RetryInterval` | `TimeSpan` | `1s` | 可选 | `TimeSpan` 正值 | 下载重试间隔 | + +### 3.2 实例方法 + +**GeneralUpdateBootstrap:** + +| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | +| --- | --- | --- | --- | --- | +| `LaunchAsync()` | 无(读取已配置的 Option 和 Config) | `Task` | 所有 Core 使用场景的最终入口 | 会根据 `Option.AppType` 自动选择执行策略 | +| `Cancel()` | 无 | `void` | UI 中提供"取消更新"按钮 | 触发内部 `CancellationTokenSource` | +| `SetConfig(UpdateRequest)` | `configInfo` — 更新配置对象 | `GeneralUpdateBootstrap` | 主程序内显式配置更新参数 | 会调用 `Validate()` 检查关键字段 | +| `SetConfig(string)` | `filePath` — JSON 配置文件路径 | `GeneralUpdateBootstrap` | 从文件读取更新配置 | 支持相对路径和绝对路径;UTF-8 JSON 格式 | +| `SetSource(...)` | `updateUrl`(更新检查 API 地址)
`appSecretKey`(应用密钥)
`reportUrl?`(可选上报地址)
`scheme?`(可选认证方案,如 `"Bearer"`)
`token?`(可选认证令牌)
`authScheme?`(可选认证方案枚举,默认 `Hmac`)
`basicUsername?`(可选 Basic 认证用户名)
`basicPassword?`(可选 Basic 认证密码)
`installPath?`(可选安装路径) | `GeneralUpdateBootstrap` | 轻配置入口,配合 manifest 使用 | 只提供服务端入口和密钥,身份信息由 manifest 补齐;支持 HMAC / Bearer / Basic 多种认证方式 | +| `SetOption(Option, T)` | `option` — 选项键, `value` — 选项值 | `GeneralUpdateBootstrap` | 设置运行时选项 | 传入 `null` 给可空选项会移除当前设置 | +| `UseDiffPipeline(Action)` | `configure` — 差分管道配置委托 | `GeneralUpdateBootstrap` | 替换或调整差分补丁管道 | 未调用时使用默认配置 | +| `AddListenerUpdateInfo(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 接收服务端版本信息 | 无更新时也会触发(`Info.Code = 404`) | +| `AddListenerUpdatePrecheck(...)` | `Func` | `GeneralUpdateBootstrap` | 下载前预检查 | 返回 `true` 表示跳过非强制更新 | +| `AddListenerProgress(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 更新进度条、状态文本 | 同时包含下载进度和差分进度 | +| `AddListenerMultiDownloadCompleted(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 标记单个资源下载完成 | 不要当做"全部下载完成" | +| `AddListenerMultiAllDownloadCompleted(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 全部下载完成后的后续处理 | 包含失败汇总 `FailedVersions` | +| `AddListenerMultiDownloadError(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 记录单个资源下载失败 | 整体成功仍以 `MultiAllDownloadCompleted` 为准 | +| `AddListenerMultiDownloadStatistics(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 展示下载速度和剩余时间 | 新代码优先使用 `AddListenerProgress` | +| `AddListenerException(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 上报异常、展示错误信息 | 仅通知,不自动重试 | +| `AddEventListener()` | 泛型参数 — 监听器类型 | `GeneralUpdateBootstrap` | 批量注册事件监听器 | `T` 必须实现 `IUpdateEventListener`,推荐继承 `UpdateEventListenerBase` | +| `Hooks()` | 泛型参数 — Hook 类型 | `GeneralUpdateBootstrap` | 注册生命周期钩子 | `T` 必须实现 `IUpdateHooks` 且有无参构造函数 | +| `UpdateReporter()` | 泛型参数 — Reporter 类型 | `GeneralUpdateBootstrap` | 注册状态上报器 | `T` 必须实现 `IUpdateReporter` | +| `SslPolicy()` | 泛型参数 — SSL 策略类型 | `GeneralUpdateBootstrap` | 自定义 HTTPS 证书校验 | 生产环境不建议无条件返回 `true` | +| `HttpAuth()` | 泛型参数 — 认证提供器类型 | `GeneralUpdateBootstrap` | 自定义 HTTP 请求认证 | `T` 必须实现 `IHttpAuthProvider` | +| `DownloadSource()` | 泛型参数 — 下载来源类型 | `GeneralUpdateBootstrap` | 自定义版本清单来源 | `T` 必须实现 `IDownloadSource` | +| `DownloadPolicy()` | 泛型参数 — 下载策略类型 | `GeneralUpdateBootstrap` | 自定义下载重试/超时策略 | `T` 必须实现 `IDownloadPolicy` | +| `DownloadExecutor()` | 泛型参数 — 下载执行器类型 | `GeneralUpdateBootstrap` | 自定义单文件下载实现 | `T` 必须实现 `IDownloadExecutor` | +| `DownloadPipeline()` | 泛型参数 — 下载管道类型 | `GeneralUpdateBootstrap` | 自定义下载后处理 | `T` 必须实现 `IDownloadPipeline` | +| `DownloadOrchestrator()` | 泛型参数 — 下载编排器类型 | `GeneralUpdateBootstrap` | 完全替换批量下载逻辑 | 只有需要完整替换下载行为时才建议实现 | +| `Strategy()` | 泛型参数 — 策略类型 | `GeneralUpdateBootstrap` | 自定义平台级更新策略 | `T` 必须实现 `IStrategy` | + +**DiffPipelineBuilder:** + +| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | +| --- | --- | --- | --- | --- | +| `UseDiffer(IBinaryDiffer)` | `differ` — 差分算法实例 | `DiffPipelineBuilder` | 替换文件级差分算法 | 可选 `BsdiffDiffer` / `StreamingHdiffDiffer` / 自定义 | +| `UseCleanMatcher(ICleanMatcher)` | `matcher` — Clean 匹配器 | `DiffPipelineBuilder` | 自定义 Clean 阶段的文件匹配逻辑 | 默认 `DefaultCleanMatcher` | +| `UseDirtyMatcher(IDirtyMatcher)` | `matcher` — Dirty 匹配器 | `DiffPipelineBuilder` | 自定义 Dirty 阶段的补丁匹配逻辑 | 默认 `DefaultDirtyMatcher` | +| `WithParallelism(int)` | `degree` — 并行度 | `DiffPipelineBuilder` | 设置差分文件并行处理数 | 默认 2;建议 1-8 | +| `WithStopOnFirstError(bool)` | `stop` — 是否首次错误即停止 | `DiffPipelineBuilder` | 错误策略控制 | 默认 `false` | +| `WithProgress(IProgress)` | `progress` — 进度报告器 | `DiffPipelineBuilder` | 接入差分进度回调 | 可配合 `Progress` 使用 | +| `Build()` | 无 | `DiffPipeline` | 构建差分管道实例 | 一般在 `UseDiffPipeline` 回调内部调用 | -示例:独立升级程序入口。 +**UpdateRequestBuilder:** -```csharp -await new GeneralUpdateBootstrap() - .SetOption(Option.AppType, AppType.Upgrade) - .AddListenerException((_, e) => Console.WriteLine(e.Exception)) - .LaunchAsync(); -``` +| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | +| --- | --- | --- | --- | --- | +| `Create()` | 无 | `UpdateRequestBuilder` | 从 `update_config.json` 读取配置 | 文件不存在会抛出 `FileNotFoundException` | +| `SetUpdateUrl(string)` | `url` | `UpdateRequestBuilder` | 设置更新地址 | 必须为绝对 URL | +| `SetUpgradeAppName(string)` | `name` | `UpdateRequestBuilder` | 设置升级程序文件名 | — | +| `SetMainAppName(string)` | `name` | `UpdateRequestBuilder` | 设置主程序文件名 | — | +| `SetClientVersion(string)` | `version` | `UpdateRequestBuilder` | 设置客户端版本 | SemVer 格式 | +| `SetAppSecretKey(string)` | `key` | `UpdateRequestBuilder` | 设置应用密钥 | — | +| `SetInstallPath(string)` | `path` | `UpdateRequestBuilder` | 设置安装目录 | — | +| `SetProductId(string)` | `id` | `UpdateRequestBuilder` | 设置产品标识 | — | +| `SetReportUrl(string)` | `url` | `UpdateRequestBuilder` | 设置上报地址 | — | +| `SetUpdateLogUrl(string)` | `url` | `UpdateRequestBuilder` | 设置更新日志地址 | — | +| `SetUpgradeClientVersion(string)` | `version` | `UpdateRequestBuilder` | 设置升级程序版本 | — | +| `SetBowl(string)` | `bowl` | `UpdateRequestBuilder` | 设置 Bowl 进程名 | — | +| `SetDriverDirectory(string)` | `path` | `UpdateRequestBuilder` | 设置驱动目录 | — | +| `SetScheme(string)` | `scheme` | `UpdateRequestBuilder` | 设置认证方案 | — | +| `SetToken(string)` | `token` | `UpdateRequestBuilder` | 设置认证令牌 | — | +| `SetAuthScheme(AuthScheme)` | `authScheme` — 认证方案枚举 | `UpdateRequestBuilder` | 设置认证方案(Hmac / Bearer / ApiKey / Basic) | 默认 `Hmac` | +| `SetBasicUsername(string)` | `username` | `UpdateRequestBuilder` | 设置 Basic 认证用户名 | 需配合 `SetAuthScheme(AuthScheme.Basic)` | +| `SetBasicPassword(string)` | `password` | `UpdateRequestBuilder` | 设置 Basic 认证密码 | 需配合 `SetAuthScheme(AuthScheme.Basic)` | +| `SetFiles(List)` | `files` | `UpdateRequestBuilder` | 设置跳过文件列表 | — | +| `SetFormats(List)` | `formats` | `UpdateRequestBuilder` | 设置跳过扩展名列表 | — | +| `SetDirectories(List)` | `dirs` | `UpdateRequestBuilder` | 设置跳过目录列表 | — | +| `Build()` | 无 | `UpdateRequest` | 构建并校验配置对象 | 会执行 `Validate()` | + +**UpgradeHubService:** + +| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | +| --- | --- | --- | --- | --- | +| `UpgradeHubService(string, string?, string?)` | `url` — SignalR Hub 地址;`token` — 可选 ID4 认证令牌;`appkey` — 可选客户端唯一标识 | —(构造函数) | 创建推送服务实例 | `appkey` 用于服务端定向推送,推荐使用固定 GUID | +| `StartAsync()` | 无 | `Task` | 建立 SignalR 长连接,开始接收推送 | 可重复调用(先 `StopAsync` 后重新 `StartAsync`) | +| `StopAsync()` | 无 | `Task` | 优雅停止连接,保留重连能力 | 适合应用进入后台时调用 | +| `DisposeAsync()` | 无 | `Task` | 彻底释放 Hub 及所有资源 | 释放后不可再复用 | +| `AddListenerReceive(Action)` | `receiveMessageCallback` — 接收消息回调 | `void` | 订阅服务端推送的版本更新消息 | 消息内容为服务端推送的 JSON 字符串 | +| `AddListenerOnline(Action)` | `onlineMessageCallback` — 状态回调 | `void` | 订阅在线/离线状态变化通知 | — | +| `AddListenerReconnected(Func?)` | `reconnectedCallback` — 重连回调 | `void` | 订阅断线重连成功通知 | 参数为新的 connectionId(可能为 null) | +| `AddListenerClosed(Func)` | `closeCallback` — 关闭回调 | `void` | 订阅连接关闭通知 | 正常关闭时异常参数为 null | + +### 3.3 回调事件 + +| 事件名称 | 回调参数 | 触发时机 | 使用说明 | +| --- | --- | --- | --- | +| `AddListenerUpdateInfo` | `UpdateInfoEventArgs` — `Info.Code`, `Info.Body`(`VersionEntry` 列表) | 标准 Client 策略完成版本对比后触发 | 无更新时 `Code = 404`;有更新时 `Body` 包含待下载的 `VersionEntry` 列表 | +| `AddListenerUpdatePrecheck` | `Func` — 返回 `true` 跳过(非强制),`false` 继续 | `UpdateInfo` 事件之后、下载之前 | 用于磁盘空间检查、网络检测、用户确认弹窗;强制更新不进入跳过逻辑 | +| `AddListenerProgress` | `ProgressEventArgs` — `Progress`(下载)或 `DiffProgress`(差分) | 下载进度或差分进度更新时 | 同一参数中 `Progress` 和 `DiffProgress` 只有一个非空 | +| `AddListenerMultiDownloadCompleted` | `MultiDownloadCompletedEventArgs` — `Version`, `IsCompleted` | 单个资源包下载完成时 | 不要当作"全部资源下载完成"的判断依据 | +| `AddListenerMultiAllDownloadCompleted` | `MultiAllDownloadCompletedEventArgs` — `IsAllDownloadCompleted`, `FailedVersions` | 所有下载任务结束后触发一次 | 失败明细在 `FailedVersions` 中 | +| `AddListenerMultiDownloadError` | `MultiDownloadErrorEventArgs` — `Exception`, `Version` | 单个资源下载失败时 | 记录失败项用于展示或监控 | +| `AddListenerMultiDownloadStatistics` | `MultiDownloadStatisticsEventArgs` — `Speed`, `Remaining`, `BytesReceived` | 兼容旧下载统计/自定义下载实现 | 新代码建议使用 `AddListenerProgress` | +| `AddListenerException` | `ExceptionEventArgs` — `Exception`, `Message` | 各策略捕获异常时 | 仅通知,不等同于自动重试 | + +**UpgradeHubService 推送事件:** + +| 事件名称 | 回调参数 | 触发时机 | 使用说明 | +| --- | --- | --- | --- | +| `AddListenerReceive` | `Action` — 消息内容(JSON 字符串) | 服务端推送版本更新消息时 | 消息格式由服务端决定,建议 JSON 格式 | +| `AddListenerOnline` | `Action` — 状态描述 | 在线/离线状态变化时 | 用于 UI 状态展示 | +| `AddListenerReconnected` | `Func?` — 新的 connectionId | 断线重连成功后 | 可用于刷新客户端状态 | +| `AddListenerClosed` | `Func` — 关闭原因(null 为正常关闭) | 连接关闭时 | 用于记录日志和清理资源 | -> 当升级程序由主程序启动时,Core 会通过加密文件 IPC 自动恢复更新上下文,通常不需要在升级程序里再次调用 `SetConfig`。 +--- -## 执行策略总览 {#execution-strategies} +## 4. 扩展示例(高阶用法) -Core 内置三类上层执行策略:标准更新策略、OSS 更新策略和静默更新策略。它们不是互相独立的 API,而是由 `LaunchAsync()` 根据 `Option.AppType`、`Option.Silent` 和当前配置自动选择。 +### 4.1 组件可扩展能力总览 -| 策略 | 触发条件 | 主要角色 | 适用场景 | -| --- | --- | --- | --- | -| [标准更新策略](#standard-update-strategy) | `Option.AppType = AppType.Client` 或 `AppType.Upgrade`,且 `Option.Silent = false` | 主程序检查/下载,升级程序替换文件 | 有服务端版本检查 API、需要标准更新和回写版本的桌面应用 | -| [OSS 更新策略](#oss-update-strategy) | `Option.AppType = AppType.OssClient` 或 `AppType.OssUpgrade` | 主程序下载 OSS 版本配置,升级程序下载并解压资源 | 版本配置和包都托管在对象存储/CDN 的应用 | -| [静默更新策略](#silent-update-strategy) | `Option.AppType = AppType.Client` 且 `Option.Silent = true` | 主程序后台轮询,退出时启动升级程序 | 希望用户使用期间无打扰下载,退出或合适时机再替换 | +Core 通过 `AbstractBootstrap` 基类提供 10 个扩展注册方法,全部返回当前 bootstrap 实例,支持链式调用。所有注册的类型必须具有无参构造函数。 -### 标准更新策略 {#standard-update-strategy} +| 扩展接口 | 注册方法 | 影响范围 | +| --- | --- | --- | +| `IUpdateHooks` | `Hooks()` | 更新生命周期前后置逻辑 | +| `IUpdateReporter` | `UpdateReporter()` | 更新状态上报 | +| `ISslValidationPolicy` | `SslPolicy()` | HTTPS 证书校验 | +| `IHttpAuthProvider` | `HttpAuth()` | HTTP 请求认证 | +| `IDownloadSource` | `DownloadSource()` | 版本清单和下载资源来源 | +| `IDownloadPolicy` | `DownloadPolicy()` | 下载重试、超时、熔断策略 | +| `IDownloadExecutor` | `DownloadExecutor()` | 单文件下载实现 | +| `IDownloadPipeline` | `DownloadPipeline()` | 下载后处理(校验、解密、扫描) | +| `IDownloadOrchestrator` | `DownloadOrchestrator()` | 批量下载完整编排 | +| `IStrategy` | `Strategy()` | 自定义平台级更新策略 | -标准更新由 `ClientStrategy` 和 `UpdateStrategy` 配合完成。`ClientStrategy` 运行在主程序中,负责发现本地清单、请求服务端版本、生成下载计划、下载更新包、准备 IPC 上下文并启动升级程序;`UpdateStrategy` 运行在独立升级程序中,负责读取 IPC 上下文、解压、应用差分补丁、替换文件、回写版本并按需启动主程序。 +### 4.2 分场景示例 -标准流程的核心顺序如下: +#### 场景 1:自定义差分算法与并行度 -1. 主程序调用 `SetConfig(...)` 或 `SetSource(...)` 后执行 `LaunchAsync()`。 -2. Core 自动读取安装目录下的 `generalupdate.manifest.json`,补齐未显式提供的 `MainAppName`、`UpdateAppName`、`ClientVersion`、`UpgradeClientVersion`、`ProductId`、`InstallPath` 等身份字段。 -3. `ClientStrategy` 使用 `DownloadSource` 获取服务端资源列表,并分别比较主程序版本和升级程序版本。 -4. `DownloadPlanBuilder` 过滤冻结包、按版本排序、检查 `MinClientVersion`,形成下载计划。 -5. 下载阶段通过 `IDownloadOrchestrator` 批量下载资源,默认支持并发、断点续传、重试和 SHA256 校验。 -6. Core 按资源 `AppType` 拆分为升级程序包和主程序包,再根据场景分派。 -7. 升级程序包可以在主程序侧先应用并回写 `UpgradeClientVersion`;主程序包会通过加密 IPC 交给升级程序替换。 -8. 升级程序完成替换后,Core 基于清单体系自动回写 `ClientVersion`,开发者不需要在业务代码中维护本地版本号。 +【场景说明】大型项目希望使用 `StreamingHdiffDiffer` 获得更快的客户端补丁应用速度,并设置并行度为 4。 -| 场景 | 判断结果 | Core 行为 | -| --- | --- | --- | -| `None` | 主程序和升级程序都无需更新 | 分发“无更新”事件并结束。 | -| `UpgradeOnly` | 只有升级程序需要更新 | 主程序下载升级程序包,直接应用到升级程序目录,回写 `UpgradeClientVersion`,主程序继续运行。 | -| `MainOnly` | 只有主程序需要更新 | 主程序下载主程序包,写入 IPC 上下文,启动升级程序替换主程序文件。 | -| `Both` | 主程序和升级程序都需要更新 | 先更新升级程序并回写 `UpgradeClientVersion`,再把主程序包交给新的升级程序处理。 | +【示例代码】 ```csharp +using GeneralUpdate.Core; +using GeneralUpdate.Core.Differential; +using GeneralUpdate.Core.Pipeline; +using GeneralUpdate.Differential.Differ; + await new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret", - reportUrl: "https://update.example.com/api/upgrade/report") - .SetOption(Option.AppType, AppType.Client) + .SetConfig(request) + .UseDiffPipeline(builder => + { + builder + .UseDiffer(new StreamingHdiffDiffer()) + .UseCleanMatcher(new DefaultCleanMatcher()) + .UseDirtyMatcher(new DefaultDirtyMatcher()) + .WithParallelism(4) + .WithStopOnFirstError(true); + }) .SetOption(Option.PatchEnabled, true) - .SetOption(Option.DiffMode, DiffMode.Parallel) - .SetOption(Option.MaxConcurrency, 4) + .SetOption(Option.AppType, AppType.Client) .LaunchAsync(); ``` -### OSS 更新策略 {#oss-update-strategy} +【效果&注意事项】 +- `StreamingHdiffDiffer` 默认使用 Deflate 压缩,客户端应用补丁更快 +- `WithStopOnFirstError(true)` 表示任意补丁失败立即停止所有并行任务 +- 并行度 4 适合多核 CPU + SSD 环境 -OSS 更新由同一个 `OssStrategy` 根据角色分成 `OssClient` 和 `OssUpgrade` 两段。它适合把版本配置 JSON 和更新包放在 OSS、S3、MinIO、CDN 或静态文件服务器上,不依赖标准服务端版本检查 API。 +#### 场景 2:自定义生命周期钩子 -| 角色 | 本地行为 | 关键配置 | -| --- | --- | --- | -| `AppType.OssClient` | 从 `UpdateUrl` 下载 OSS 版本配置到安装目录,比较远端最新版本和本地 `ClientVersion`,需要更新时启动升级程序并退出。 | `UpdateUrl` 指向版本配置文件地址;`MainAppName` / `UpdateAppName` 可由 manifest 提供。 | -| `AppType.OssUpgrade` | 读取本地版本配置或自定义 `DownloadSource`,筛选高于本地版本的资源,下载到安装目录,解压 ZIP,删除压缩包,启动主程序并退出。 | 安装目录可写;资源列表中的版本号必须可比较。 | +【场景说明】在更新前检查磁盘空间、更新后写日志、Linux/macOS 下启动前赋予执行权限。 -OSS 版本配置文件会保存为 `{MainAppName}_versions.json` 或 `{UpdateAppName}_versions.json`。如果注册了 `DownloadSource()`,OSS 升级侧可以跳过默认文件读取逻辑,改为由你的下载源返回资源列表;如果注册了 `DownloadOrchestrator()`,下载过程也可以完全替换。 +【示例代码】 ```csharp -await new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://cdn.example.com/myapp_versions.json", - appSecretKey: "oss-mode-secret") - .SetOption(Option.AppType, AppType.OssClient) - .LaunchAsync(); -``` +using GeneralUpdate.Core.Hooks; -```csharp -await new GeneralUpdateBootstrap() - .SetOption(Option.AppType, AppType.OssUpgrade) - .LaunchAsync(); -``` +public sealed class ProductUpdateHooks : IUpdateHooks +{ + public Task OnBeforeUpdateAsync(HookContext ctx) + { + var drive = new DriveInfo(Path.GetPathRoot(ctx.InstallPath)!); + if (drive.AvailableFreeSpace < 500L * 1024 * 1024) + { + Console.WriteLine("Insufficient disk space for update."); + return Task.FromResult(false); // 拒绝更新 + } + return Task.FromResult(true); + } -### 静默更新策略 {#silent-update-strategy} + public Task OnDownloadCompletedAsync(DownloadContext ctx) + { + Console.WriteLine($"Downloaded {ctx.AssetName}: {ctx.Success}"); + return Task.CompletedTask; + } -静默更新只在 `AppType.Client` 下生效。启用后,`LaunchAsync()` 会进入静默启动分支,创建和标准更新相同的 `ClientStrategy`,但把 `LaunchAfterPrepare` 设为 `false`,再交给 `SilentPollOrchestrator` 做后台轮询。 + public Task OnAfterUpdateAsync(HookContext ctx) + { + File.AppendAllText( + Path.Combine(ctx.InstallPath, "update-history.log"), + $"{DateTimeOffset.Now:O} {ctx.CurrentVersion} -> {ctx.TargetVersion}{Environment.NewLine}"); + return Task.CompletedTask; + } -静默模式不会重新实现更新逻辑;它只是把“检查和下载”放到后台,把“启动升级程序替换文件”延后到进程退出时。这样用户可以继续使用当前进程,更新包先准备好,真正替换发生在应用退出之后。 + public Task OnUpdateErrorAsync(HookContext ctx, Exception ex) + { + File.AppendAllText( + Path.Combine(ctx.InstallPath, "update-error.log"), + $"{ex}{Environment.NewLine}"); + return Task.CompletedTask; + } -| 阶段 | 标准更新 | 静默更新 | -| --- | --- | --- | -| 版本检查 | 用户触发后立即执行一次 | 后台按 `Option.SilentPollIntervalMinutes` 周期执行 | -| 下载 | 发现更新后立即下载 | 发现更新后后台下载 | -| 启动升级程序 | 主程序准备完成后立即启动 | 主程序退出时由 `ProcessExit` 处理启动 | -| 用户体验 | 适合显式“检查更新/立即更新” | 适合无打扰准备更新 | + public Task OnBeforeStartAppAsync(HookContext ctx) + { + // Linux/macOS 下可用 UnixPermissionHooks + return Task.CompletedTask; + } +} -```csharp await new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret") + .SetConfig(request) + .Hooks() .SetOption(Option.AppType, AppType.Client) - .SetOption(Option.Silent, true) - .SetOption(Option.SilentPollIntervalMinutes, 30) - .SetOption(Option.LaunchClientAfterUpdate, true) .LaunchAsync(); ``` -静默更新仍然会使用你注册的 `IUpdateHooks`、`IUpdateReporter`、下载扩展、证书策略、认证策略和差分管道。需要注意的是,静默模式适合“下载准备无感知”,不等于“文件替换无感知”;主程序文件仍应由独立升级程序在主程序退出后替换。 +【效果&注意事项】 +- `OnBeforeUpdateAsync` 返回 `false` 会中断本次更新 +- `OnBeforeStartAppAsync` 适合 Linux/macOS 的 `chmod +x` 操作 +- 内置 `UnixPermissionHooks` 可直接使用 -### Cancel +#### 场景 3:自定义下载来源(私有服务/配置中心) -```csharp -public void Cancel() -``` +【场景说明】从企业内部配置中心获取下载资源列表,而不是调用标准版本检查 API。 -`Cancel` 会触发内部 `CancellationTokenSource`,更新策略会在安全检查点观察取消请求。适合 UI 应用把 bootstrap 保存为字段后绑定取消按钮。 +【示例代码】 ```csharp -private GeneralUpdateBootstrap? _bootstrap; +using GeneralUpdate.Core.Download.Abstractions; +using GeneralUpdate.Core.Download.Models; -async Task StartUpdateAsync(UpdateRequest request) +public sealed class ConfigCenterDownloadSource : IDownloadSource { - _bootstrap = new GeneralUpdateBootstrap() - .SetConfig(request) - .AddListenerException((_, e) => Console.WriteLine(e.Exception)); + public async Task ListAsync(CancellationToken token = default) + { + // 从配置中心拉取资源列表... + var assets = new[] + { + new DownloadAsset( + Name: "MyApp-2.0.0.zip", + Url: "https://cdn.internal.example.com/releases/MyApp-2.0.0.zip", + Size: 50_000_000, + SHA256: "abc123...", + Version: "2.0.0") + }; - await _bootstrap.LaunchAsync(); + return new DownloadSourceResult + { + Assets = assets, + HasMainUpdate = true, + HasUpgradeUpdate = false + }; + } } -void CancelUpdate() -{ - _bootstrap?.Cancel(); -} +await new GeneralUpdateBootstrap() + .SetConfig(request) + .DownloadSource() + .SetOption(Option.AppType, AppType.Client) + .LaunchAsync(); ``` -### SetConfig(UpdateRequest) +【效果&注意事项】 +- 自定义 `IDownloadSource` 会完全替换默认的 HTTP 版本检查逻辑 +- 需要同时注册 `DownloadOrchestrator()` 时,orchestrator 会接管完整下载流程 -```csharp -public GeneralUpdateBootstrap SetConfig(UpdateRequest configInfo) -``` +#### 场景 4:多协议 HTTP 认证 + +【场景说明】Core 内置四种认证方案,通过 `AuthScheme` 枚举一键切换,也可通过 `IHttpAuthProvider` 完全自定义。 -`SetConfig(UpdateRequest)` 会调用 `UpdateRequest.Validate()`,并把外部配置映射为内部 `UpdateContext`。当角色不是 `AppType.Upgrade` 时,它还会初始化临时目录和黑名单匹配器。 +**方式一:使用内置 `AuthScheme` 枚举(推荐)** ```csharp -using GeneralUpdate.Core; using GeneralUpdate.Core.Configuration; +// HMAC-SHA256 签名认证(默认) var request = new UpdateRequest { UpdateUrl = "https://update.example.com/api/upgrade/verification", - ReportUrl = "https://update.example.com/api/upgrade/report", - UpdateAppName = "UpgradeSample.exe", - MainAppName = "ClientSample.exe", - InstallPath = AppDomain.CurrentDomain.BaseDirectory, - ClientVersion = "1.0.0", AppSecretKey = "your-app-secret", - ProductId = "your-product-id", - Files = new List { "appsettings.json" }, - Formats = new List { ".log", ".tmp" }, - Directories = new List { "logs", "cache" } + AuthScheme = AuthScheme.Hmac }; -await new GeneralUpdateBootstrap() - .SetConfig(request) - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` +// Bearer Token 认证 +var bearerRequest = new UpdateRequest +{ + UpdateUrl = "https://update.example.com/api/upgrade/verification", + AppSecretKey = "your-app-secret", + AuthScheme = AuthScheme.Bearer, + Token = "your-jwt-token" +}; -### SetConfig(string) +// API Key 认证 +var apiKeyRequest = new UpdateRequest +{ + UpdateUrl = "https://update.example.com/api/upgrade/verification", + AppSecretKey = "your-app-secret", + AuthScheme = AuthScheme.ApiKey, + Token = "your-api-key" +}; -```csharp -public GeneralUpdateBootstrap SetConfig(string filePath) +// HTTP Basic 认证 +var basicRequest = new UpdateRequest +{ + UpdateUrl = "https://update.example.com/api/upgrade/verification", + AppSecretKey = "your-app-secret", + AuthScheme = AuthScheme.Basic, + BasicUsername = "admin", + BasicPassword = "password123" +}; ``` -`SetConfig(string)` 从 UTF-8 JSON 文件读取 `UpdateRequest`。如果只传文件名,会从当前应用基目录解析;如果传相对或绝对路径,会按路径解析。 +**方式二:自定义 `IHttpAuthProvider`(高级)** -```json +【场景说明】为 Core 发出的 HTTP 请求追加自定义认证逻辑(如从配置中心动态获取 Token)。 + +【示例代码】 + +```csharp +using GeneralUpdate.Core.Security; + +public sealed class JwtAuthProvider : IHttpAuthProvider { - "updateUrl": "https://update.example.com/api/upgrade/verification", - "reportUrl": "https://update.example.com/api/upgrade/report", - "updateAppName": "UpgradeSample.exe", - "mainAppName": "ClientSample.exe", - "installPath": "C:\\Program Files\\MyApp", - "clientVersion": "1.0.0", - "appSecretKey": "your-app-secret", - "productId": "your-product-id" + private readonly string _token; + + public JwtAuthProvider() + { + // 从配置或环境变量读取 token + _token = Environment.GetEnvironmentVariable("UPDATE_JWT_TOKEN") ?? ""; + } + + public Task ApplyAuthAsync(HttpRequestMessage request, CancellationToken token = default) + { + request.Headers.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token); + return Task.CompletedTask; + } } -``` -```csharp await new GeneralUpdateBootstrap() - .SetConfig("update_config.json") + .SetConfig(request) + .HttpAuth() .SetOption(Option.AppType, AppType.Client) .LaunchAsync(); ``` -### SetSource +【效果&注意事项】 +- 内置 Provider:`HmacAuthProvider`(默认)、`BearerTokenAuthProvider`、`ApiKeyAuthProvider`、`BasicAuthProvider` +- `HttpAuthProviderFactory` 根据 `AuthScheme` 自动选择对应 Provider +- 自定义 Provider 需要在无参构造函数中自行读取配置 +- HMAC 签名算法:`HMAC-SHA256(body|timestamp)`,请求头 `X-Update-Timestamp` + `X-Update-Signature` -```csharp -public GeneralUpdateBootstrap SetSource( - string updateUrl, - string appSecretKey, - string? reportUrl = null, - string? scheme = null, - string? token = null) -``` +#### 场景 5:静默更新 + 进程退出触发升级 + +【场景说明】主程序启动后后台轮询更新,下载完成后不打扰用户,待进程退出时启动升级程序。 -`SetSource` 是轻配置入口,适合把应用身份信息放到 `generalupdate.manifest.json`,只在代码中指定服务端入口和密钥。 +【示例代码】 ```csharp -await new GeneralUpdateBootstrap() +using GeneralUpdate.Core; + +// 主程序启动时 +var bootstrap = new GeneralUpdateBootstrap() .SetSource( updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret", - reportUrl: "https://update.example.com/api/upgrade/report", - scheme: "Bearer", - token: "access-token") + appSecretKey: "your-app-secret") .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -### UseDiffPipeline - -```csharp -public GeneralUpdateBootstrap UseDiffPipeline(Action? configure) -``` - -`UseDiffPipeline` 用于替换或调整差分补丁管道。未调用时,引导类会创建默认管道:`BsdiffDiffer`、`DefaultCleanMatcher`、`DefaultDirtyMatcher`、并行度 `2`,并接入 Core 的差分进度事件。关于算法差异、补丁阶段和并发设置,请看 [差分算法与补丁管道](#differential-pipeline)。 + .SetOption(Option.Silent, true) + .SetOption(Option.SilentPollIntervalMinutes, 30) + .SetOption(Option.LaunchClientAfterUpdate, true) + .AddListenerException((_, e) => + { + Console.WriteLine($"Update error: {e.Message}"); + }); -```csharp -using GeneralUpdate.Core.Differential; -using GeneralUpdate.Core.Models; -using GeneralUpdate.Core.Pipeline; -using GeneralUpdate.Differential.Differ; +await bootstrap.LaunchAsync(); -await new GeneralUpdateBootstrap() - .SetConfig(request) - .UseDiffPipeline(builder => +// 应用退出时检查是否有准备好的更新 +AppDomain.CurrentDomain.ProcessExit += (_, _) => +{ + if (bootstrap.SilentOrchestrator?.HasPreparedUpdate == true) { - builder - .UseDiffer(new StreamingHdiffDiffer()) - .UseCleanMatcher(new DefaultCleanMatcher()) - .UseDirtyMatcher(new DefaultDirtyMatcher()) - .WithParallelism(4) - .WithStopOnFirstError(true) - .WithProgress(new Progress(p => - { - Console.WriteLine($"{p.Completed}/{p.Total}: {p.FileName}"); - })); - }) - .SetOption(Option.PatchEnabled, true) - .LaunchAsync(); + bootstrap.SilentOrchestrator.TryLaunchUpgrade(); + } +}; ``` -## 差分算法与补丁管道 {#differential-pipeline} +【效果&注意事项】 +- 静默模式在 `AppType.Client` 下生效 +- 文件替换发生在主程序退出之后 +- 轮询间隔建议不低于 30 分钟 -Core 的差分能力分两层:`IBinaryDiffer` 负责“单个文件如何生成/应用补丁”,`DiffPipeline` 负责“目录中哪些文件需要补丁、哪些文件是新增/删除、如何并行处理多个文件”。普通使用者只需要打开 `Option.PatchEnabled`;需要调优性能或兼容性时,再通过 `UseDiffPipeline(...)` 调整。 +#### 场景 6:SignalR 实时推送 + 常规更新联动 -### 差分算法类型 +【场景说明】客户端同时使用 `UpgradeHubService` 接收服务端实时推送和 `GeneralUpdateBootstrap` 执行常规更新。服务端有新版时可立即推送通知,客户端无需等待轮询即可触发更新。 -| 算法/实现 | 默认位置 | 特点 | 适合场景 | -| --- | --- | --- | --- | -| `BsdiffDiffer` | `GeneralUpdateBootstrap` 默认使用 | 经典 BSDIFF 4.0 算法,默认使用 BZip2 压缩,补丁格式兼容性强。 | 追求稳定兼容、已有包体系基于 BSDIFF 的项目。 | -| `StreamingHdiffDiffer` | `DiffPipeline` 直接构造时的默认 differ;也可通过 `UseDiffPipeline` 显式选择 | 使用块级 Hash 索引做候选匹配,典型复杂度更低,默认使用 Deflate,并生成可由 Dirty 阶段读取的 BSDIFF 兼容补丁格式。 | 大文件较多、希望降低生成补丁时内存和 CPU 压力的项目。 | -| 自定义 `IBinaryDiffer` | 通过 `UseDiffPipeline(builder => builder.UseDiffer(...))` 接入 | 完全替换单文件差分算法。 | 企业内部已有补丁格式、加密补丁或专用二进制差分算法。 | - -`BsdiffDiffer` 还支持替换压缩提供器:`BZip2CompressionProvider` 是兼容默认值,`DeflateCompressionProvider` 更偏向速度,`.NET 6+` 可使用 `BrotliCompressionProvider` 在压缩率和解压速度之间取得更好平衡。选择压缩提供器时要确保生成补丁和应用补丁的运行时都能识别对应格式版本。 +【示例代码】 ```csharp -using GeneralUpdate.Core.Pipeline; -using GeneralUpdate.Differential.Abstractions; -using GeneralUpdate.Differential.Differ; - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .UseDiffPipeline(builder => - { - builder - .UseDiffer(new BsdiffDiffer(new DeflateCompressionProvider())) - .WithParallelism(4); - }) - .SetOption(Option.PatchEnabled, true) - .LaunchAsync(); -``` +using GeneralUpdate.Core; +using GeneralUpdate.Core.Configuration; +using GeneralUpdate.Core.Hubs; -### Clean 和 Dirty 两个阶段 +// 1. 启动 SignalR 推送监听 +var hub = new UpgradeHubService( + "http://localhost:5000/UpgradeHub", + token: null, + appkey: "dfeb5833-975e-4afb-88f1-6278ee9aeff6"); -| 阶段 | 方法 | 运行位置 | 作用 | -| --- | --- | --- | --- | -| Clean | `DiffPipeline.CleanAsync(oldDir, newDir, patchDir)` | 发布侧/工具侧 | 对比旧版本和新版本目录,生成 `.patch` 文件,复制新增文件,并写入 `generalupdate.delete.json` 删除清单。 | -| Dirty | `DiffPipeline.DirtyAsync(appDir, patchDir)` | 客户端升级侧 | 读取补丁目录,把 `.patch` 应用到旧文件,复制新增文件,按删除清单移除旧文件。 | +hub.AddListenerReceive(async (message) => +{ + Console.WriteLine($"收到实时推送: {message}"); + // 收到推送后可以立即触发更新检查 + // 或在 UI 中提示用户有新版本可用 +}); -Core 主要消费 Dirty 阶段;差分包生成建议交给 `GeneralUpdate.Tools` 或发布流水线完成。标准更新中,下载完成后升级程序会先解压包,再在 `PatchEnabled = true` 时通过 `PatchMiddleware` 调用 `DiffPipeline.DirtyAsync(...)` 应用补丁。 +hub.AddListenerOnline((info) => + Console.WriteLine($"在线状态: {info}")); -### 下载并发与差分并行 {#download-diff-concurrency} +hub.AddListenerReconnected((connectionId) => +{ + Console.WriteLine($"已重连,connectionId={connectionId}"); + return Task.CompletedTask; +}); -Core 支持两个层面的多线程能力:下载阶段可以并发下载多个更新资源,差分阶段可以并行处理多个文件补丁。内置标准流程是“先完成当前下载计划,再进入解压/差分/替换阶段”;如果你需要把下载和应用做成更细粒度的流水线,可以通过自定义 `IDownloadOrchestrator` 或自定义 `IStrategy` 接管。 +hub.AddListenerClosed((exception) => +{ + if (exception is not null) + Console.WriteLine($"连接异常关闭: {exception.Message}"); + else + Console.WriteLine("连接已正常关闭"); + return Task.CompletedTask; +}); -| 层级 | 控制 API | 默认行为 | 说明 | -| --- | --- | --- | --- | -| 批量下载并发 | `Option.DiffMode` + `Option.MaxConcurrency` | `DiffMode.Serial` 会强制下载并发为 `1`;`DiffMode.Parallel` 使用 `MaxConcurrency`,并被限制在 `1` 到 `Environment.ProcessorCount * 2` 之间。 | 由 `DefaultDownloadOrchestrator` 使用 `SemaphoreSlim` 控制,支持重试、断点续传和校验。 | -| 差分文件并行 | `UseDiffPipeline(...WithParallelism(n))` | 引导类默认 `2`。 | `DiffPipeline` 对多个文件创建任务,并用 `SemaphoreSlim` 限制同时生成/应用补丁的文件数。 | -| 下载后处理 | `DownloadPipeline()` | 默认 SHA256 校验。 | 每个资源下载成功后执行,可替换为解密、扫描、二次校验等。 | +await hub.StartAsync(); -```csharp +// 2. 常规更新流程 await new GeneralUpdateBootstrap() - .SetConfig(request) - .SetOption(Option.DiffMode, DiffMode.Parallel) - .SetOption(Option.MaxConcurrency, 6) - .UseDiffPipeline(builder => - { - builder - .UseDiffer(new StreamingHdiffDiffer()) - .WithParallelism(4); - }) + .SetSource( + updateUrl: "https://update.example.com/api/upgrade/verification", + appSecretKey: "your-app-secret") + .SetOption(Option.AppType, AppType.Client) + .AddListenerException((_, e) => Console.WriteLine(e.Exception)) .LaunchAsync(); + +// 3. 应用退出时清理 +// await hub.StopAsync(); +// await hub.DisposeAsync(); ``` -并发值不是越大越好。网络慢但磁盘快时可以提高 `Option.MaxConcurrency`;补丁文件很多且磁盘是 SSD 时可以提高 `WithParallelism`;机械硬盘、低配终端或后台静默更新建议降低并发,避免影响主程序响应。 +【效果&注意事项】 +- `UpgradeHubService` 与 `GeneralUpdateBootstrap` 互补:推送做通知,Bootstrap 做实际更新 +- `appkey` 推荐与 `AppSecretKey` 保持一致,便于服务端定向推送 +- `StopAsync` 保留重连能力,适合应用进入后台时调用 +- `DisposeAsync` 彻底释放,适合应用退出时调用 -## 配置模型:UpdateRequest +#### 场景 7:DI 容器中注册 UpgradeHubService -`UpdateRequest` 是外部调用者最常用的配置对象。它继承 `UpdateConfiguration`,并在 `Validate()` 中检查关键字段。 +【场景说明】在 Prism / Generic Host / ASP.NET Core 等 DI 容器中注册 `IUpgradeHubService`,管理推送服务生命周期。 -### 必填或强烈建议配置的属性 +【示例代码】 -| 属性 | 说明 | -| --- | --- | -| `UpdateUrl` | 更新检查 API 地址。必须是绝对 URL。 | -| `UpdateAppName` | 升级程序文件名,默认 `Update.exe`。如果你的升级程序叫 `UpgradeSample.exe`,必须显式设置。 | -| `MainAppName` | 主程序文件名。用于升级后重新启动,也用于识别要更新的应用。 | -| `ClientVersion` | 当前主程序版本。 | -| `AppSecretKey` | 应用密钥,用于和服务端约定认证。 | -| `InstallPath` | 应用安装目录,默认当前应用基目录。生产环境建议显式设置。 | +```csharp +using GeneralUpdate.Core.Hubs; -### 可选属性 +// Prism 示例 +protected override void RegisterTypes(IContainerRegistry containerRegistry) +{ + containerRegistry.Register(); +} -| 属性 | 说明 | -| --- | --- | -| `ReportUrl` | 更新状态上报 API。 | -| `UpdateLogUrl` | 更新日志页面地址。 | -| `UpgradeClientVersion` | 升级程序自身版本。 | -| `ProductId` | 产品标识,同一服务端管理多个产品时使用。 | -| `UpdatePath` | 升级程序所在目录;为空时使用 `InstallPath`。 | -| `Bowl` | 更新前需要关闭的辅助进程名。 | -| `Scheme` / `Token` | 请求认证信息,可与内置认证提供器配合。 | -| `Files` | 更新时跳过的指定文件。 | -| `Formats` | 更新时跳过的扩展名,例如 `.log`。 | -| `Directories` | 更新时跳过的目录。 | +// 在 ViewModel 中使用 +public MainWindowViewModel(IUpgradeHubService hubService) +{ + hubService.AddListenerReceive((message) => + { + Console.WriteLine($"收到推送: {message}"); + }); + _ = hubService.StartAsync(); +} -### 使用 UpdateRequestBuilder +// Generic Host / ASP.NET Core 示例 +builder.Services.AddSingleton(sp => +{ + var config = sp.GetRequiredService(); + return new UpgradeHubService( + config["HubUrl"]!, + appkey: config["AppSecretKey"]); +}); +``` -`UpdateRequestBuilder` 提供链式构建 API,并在 `Build()` 时执行校验。 +【效果&注意事项】 +- DI 容器管理生命周期,避免手动 Dispose +- 可将配置从 `appsettings.json` 注入 -```csharp -using GeneralUpdate.Core.Configuration; +#### 场景 8:跨平台自适应策略 -var request = new UpdateRequestBuilder() - .SetUpdateUrl("https://update.example.com/api/upgrade/verification") - .SetReportUrl("https://update.example.com/api/upgrade/report") - .SetUpgradeAppName("UpgradeSample.exe") - .SetMainAppName("ClientSample.exe") - .SetClientVersion("1.0.0") - .SetAppSecretKey("your-app-secret") - .SetProductId("your-product-id") - .SetInstallPath(AppDomain.CurrentDomain.BaseDirectory) - .SetFiles(new List { "appsettings.json" }) - .SetFormats(new List { ".log", ".tmp" }) - .SetDirectories(new List { "logs" }) - .Build(); -``` +【场景说明】Core 根据运行时平台自动选择 `WindowsStrategy` / `LinuxStrategy` / `MacStrategy`,无需手动指定。也可通过 `PlatformType` 显式控制或自定义平台策略。 -`UpdateRequestBuilder.Create()` 会尝试从应用运行目录的 `update_config.json` 读取配置。如果文件不存在,会抛出 `FileNotFoundException`。 +【示例代码】 ```csharp -var request = UpdateRequestBuilder.Create().Build(); -``` - -## 应用身份清单:generalupdate.manifest.json - -`generalupdate.manifest.json` 是由 `GeneralUpdate.Tools` 生成、由 Core 消费的应用身份清单。它的核心价值是**帮开发者节约接入和维护时间**:Tools 把“主程序叫什么、当前版本是多少、升级程序叫什么、产品标识是什么、升级程序放在哪个目录”等稳定元数据生成到清单里,Core 在运行时自动消费这些信息,业务代码只需要补充服务端地址、密钥、令牌等运行时或敏感参数。 - -换句话说,使用 manifest 后,接入 GeneralUpdate 不再需要手写一大段完整 `UpdateRequest`。发布时让 Tools 生成 `generalupdate.manifest.json`,运行时再配少量敏感信息,就可以直接启动更新流程。这是 Core 推荐的极简配置方式。 - -推荐把它放在应用安装目录,也就是 `UpdateRequest.InstallPath` 指向的目录。默认情况下 `InstallPath` 是 `AppDomain.CurrentDomain.BaseDirectory`,因此普通桌面应用通常把清单放在主程序输出目录根部。 - -```text -MyProduct/ -├─ ClientSample.exe -├─ generalupdate.manifest.json -└─ update/ - └─ UpgradeSample.exe -``` - -### 清单结构 +using GeneralUpdate.Core; +using GeneralUpdate.Core.Configuration; +using GeneralUpdate.Core.Strategy; -Tools 生成的 JSON 使用小驼峰字段名,Core 中对应类型是 `ManifestInfo`。 +// 方式一:自动检测(推荐) +// Core 会根据 RuntimeInformation 自动选择对应平台策略 +await new GeneralUpdateBootstrap() + .SetSource( + updateUrl: "https://update.example.com/api/upgrade/verification", + appSecretKey: "your-app-secret") + .SetOption(Option.AppType, AppType.Client) + .LaunchAsync(); +// Windows → WindowsStrategy +// Linux → LinuxStrategy(无 Bowl 支持) +// macOS → MacStrategy -```json -{ - "mainAppName": "ClientSample.exe", - "clientVersion": "1.0.0", - "appType": "Client", - "updateAppName": "UpgradeSample.exe", - "upgradeClientVersion": "1.0.0", - "productId": "sample-product", - "updatePath": "update/" -} +// 方式二:显式指定平台策略(高级) +await new GeneralUpdateBootstrap() + .SetConfig(request) + .Strategy() + .SetOption(Option.AppType, AppType.Client) + .LaunchAsync(); ``` -| JSON 字段 | Core 字段 | 说明 | -| --- | --- | --- | -| `mainAppName` | `MainAppName` | 主程序可执行文件名。升级完成后用于重新启动主程序,也用于识别当前产品。 | -| `clientVersion` | `ClientVersion` | 当前主程序版本。Core 用它向服务端询问是否有主程序更新。 | -| `appType` | `AppType` | 当前进程角色字符串,例如 `Client`、`Upgrade`、`OssClient`、`OssUpgrade`。 | -| `updateAppName` | `UpdateAppName` | 升级程序文件名,默认 `Update.exe`。 | -| `upgradeClientVersion` | `UpgradeClientVersion` | 升级程序自身版本。Core 用它判断是否需要先更新升级程序。 | -| `productId` | `ProductId` | 产品标识。一个更新服务管理多个产品时用于区分产品。 | -| `updatePath` | `UpdatePath` | 升级程序所在目录;可以是相对 `InstallPath` 的目录,例如 `update/`。 | - -清单刻意不包含 `UpdateUrl`、`ReportUrl`、`AppSecretKey`、`Scheme`、`Token` 等服务端和认证信息。这样可以让 Tools 负责构建可发布的身份元数据,而密钥仍由应用代码、配置中心或部署环境提供。 - -### 极简配置理念 {#极简配置理念} +【效果&注意事项】 +- 三种平台策略均继承 `AbstractStrategy`,共享 Hash → Compress → Patch 管道 +- `WindowsStrategy` 额外支持 Bowl 辅助进程管理 +- `LinuxStrategy` 建议配合 `UnixPermissionHooks` 使用以自动赋予执行权限 +- 平台策略可被自定义 `IStrategy` 完全替换 -manifest 体系把更新配置拆成两部分: - -| 配置类型 | 由谁提供 | 为什么这样拆 | -| --- | --- | --- | -| 稳定身份信息 | `GeneralUpdate.Tools` 生成到 `generalupdate.manifest.json` | 这些字段来自项目、版本和发布目录,重复手写容易出错,也会增加每个应用接入更新的时间。 | -| 运行时/敏感信息 | 应用代码、配置中心、环境变量或部署系统提供 | 服务端地址、密钥、Token 可能因环境变化,也不应该由 Tools 固化到可发布清单里。 | - -因此最常见的接入路径是: - -1. 用 `GeneralUpdate.Tools` 生成并随应用发布 `generalupdate.manifest.json`。 -2. 在应用启动更新时只配置 `UpdateUrl`、`AppSecretKey`、`ReportUrl`,以及必要的认证信息。 -3. 让 `GeneralUpdateBootstrap` 在内部读取 manifest,自动补齐应用身份、版本、升级程序位置,并在更新成功后回写本地版本。 - -这种方式把开发者需要关心的配置压缩到“敏感信息 + 少量运行选项”,既减少样板代码,也避免多个应用重复维护主程序名、升级程序名、本地版本号和产品标识。 - -### Tools 如何生成清单 - -`GeneralUpdate.Tools` 的配置生成流程会解析主程序和升级程序的 `.csproj`,校验版本号,然后写出 `generalupdate.manifest.json`。 - -| Tools 阶段 | 作用 | -| --- | --- | -| `CsprojParseStep` | 解析主程序 `.csproj`;如果提供升级程序 `.csproj`,也会一起解析。 | -| `SemverValidateStep` | 校验 `ClientVersion` 和 `UpgradeClientVersion` 必须符合 semver,例如 `1.0.0`。 | -| `ManifestBuildStep` | 如果 UI 中没有手动填写 `MainAppName` / `UpdateAppName`,使用 `.csproj` 的 `AssemblyName` 补齐。 | -| `FileEmitStep` | 把清单写到输出目录,文件名固定为 `generalupdate.manifest.json`。 | - -配置界面的发布样例流程还会调用 `SamplePublisherService.PublishAsync(...)`,把主程序输出、升级程序输出和清单一起组织到可运行样例目录中。因此新手不需要从零手写完整 `UpdateRequest`,可以先用 Tools 生成清单,再在应用代码中补充服务端入口和密钥。 +--- -### 配合引导类使用 +## 5. 常规使用示例 -使用清单后,业务代码不需要关心 `MainAppName`、`ClientVersion`、`UpdateAppName`、`UpgradeClientVersion`、`ProductId`、`UpdatePath` 这些身份字段,也不需要手动读取 `generalupdate.manifest.json`。引导类启动更新流程时会在内部读取 `InstallPath/generalupdate.manifest.json`,并把清单中的应用身份信息带入后续的版本检查、下载、启动升级程序和版本回写流程。 +### 5.1 快速入门示例(最简 demo) -默认安装目录就是当前应用目录时,只需要把服务端入口和密钥传给 `SetSource`: +最简配置:使用 manifest 自动发现身份信息,只需要配置服务端入口和密钥。 ```csharp +using GeneralUpdate.Core; + await new GeneralUpdateBootstrap() .SetSource( updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret", - reportUrl: "https://update.example.com/api/upgrade/report") + appSecretKey: "your-app-secret") .SetOption(Option.AppType, AppType.Client) .LaunchAsync(); ``` -如果应用的实际安装目录不是当前进程基目录,只需要在 `UpdateRequest` 中补充 `InstallPath`,仍然不需要把清单中的身份字段重复写进代码: +升级程序入口(`Update.exe`): + +```csharp +await new GeneralUpdateBootstrap() + .SetOption(Option.AppType, AppType.Upgrade) + .AddListenerException((_, e) => Console.WriteLine(e.Exception)) + .LaunchAsync(); +``` + +### 5.2 基础参数组合示例 ```csharp using GeneralUpdate.Core; @@ -557,650 +708,307 @@ var request = new UpdateRequest { UpdateUrl = "https://update.example.com/api/upgrade/verification", ReportUrl = "https://update.example.com/api/upgrade/report", + UpdateAppName = "UpgradeSample.exe", + MainAppName = "ClientSample.exe", + InstallPath = AppDomain.CurrentDomain.BaseDirectory, + ClientVersion = "1.0.0", AppSecretKey = "your-app-secret", - InstallPath = @"C:\Program Files\MyProduct" + ProductId = "your-product-id", + Files = new List { "appsettings.json" }, + Formats = new List { ".log", ".tmp" }, + Directories = new List { "logs", "cache" } }; await new GeneralUpdateBootstrap() .SetConfig(request) .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -推荐的职责拆分是: - -| 由 manifest 提供 | 由代码或环境提供 | -| --- | --- | -| `MainAppName`、`ClientVersion`、`UpdateAppName`、`UpgradeClientVersion`、`ProductId`、`UpdatePath` | `UpdateUrl`、`ReportUrl`、`AppSecretKey`、`Scheme`、`Token`、事件、扩展点、运行选项 | - -### 版本回写 - -在 `generalupdate.manifest.json` 体系下,清单同时也是本地版本状态文件。开发者只需要在首次发布时通过 Tools 生成清单,不需要在每次更新完成后再写业务代码去修改本地版本号。更新成功后,Core 会把已应用的新版本自动写回安装目录下的同一个 `generalupdate.manifest.json`: - -| 场景 | 回写字段 | -| --- | --- | -| 主程序更新完成 | `ClientVersion` | -| 升级程序自身更新完成 | `UpgradeClientVersion` | - -这样下一次轮询或启动时,引导类会基于清单中的最新本地版本继续向服务端验证,而不是继续使用打包时的旧版本。回写的意义是把“本地版本号维护”收进 Core 的更新流程里,避免开发者在应用代码中额外维护 `ClientVersion` 或 `UpgradeClientVersion`。这个行为依赖安装目录可写;如果应用安装在受限目录,需要确保升级程序拥有写入清单的权限。 - -## 运行选项:Option - -Core 使用强类型 `Option` 注册运行时选项,并通过 `SetOption` 设置值。 - -```csharp -await new GeneralUpdateBootstrap() - .SetConfig(request) - .SetOption(Option.AppType, AppType.Client) + .SetOption(Option.DiffMode, DiffMode.Parallel) .SetOption(Option.MaxConcurrency, 4) - .SetOption(Option.VerifyChecksum, true) - .LaunchAsync(); -``` - -| 选项 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| `Option.AppType` | `AppType` | `Client` | 当前进程角色。 | -| `Option.DiffMode` | `DiffMode` | `Serial` | 执行模式。`Serial` 会让默认下载编排器串行下载;`Parallel` 允许按 `Option.MaxConcurrency` 并发下载。 | -| `Option.Encoding` | `Encoding` | `UTF8` | 压缩包处理编码。 | -| `Option.Format` | `Format` | `Zip` | 更新包格式。 | -| `Option.DownloadTimeout` | `int?` | `30` | 下载超时时间,单位秒。 | -| `Option.PatchEnabled` | `bool?` | `true` | 是否启用差分补丁处理。 | -| `Option.BackupEnabled` | `bool?` | `true` | 更新前是否备份被替换文件。 | -| `Option.Silent` | `bool` | `false` | 是否启用静默轮询更新。 | -| `Option.SilentPollIntervalMinutes` | `int` | `60` | 静默模式轮询间隔。 | -| `Option.LaunchClientAfterUpdate` | `bool` | `true` | 升级后是否启动主程序。 | -| `Option.MaxConcurrency` | `int` | `3` | 默认下载编排器最大并发数,实际值会被限制到合理范围。 | -| `Option.EnableResume` | `bool` | `true` | 是否启用断点续传。 | -| `Option.RetryCount` | `int` | `3` | 下载重试次数。 | -| `Option.VerifyChecksum` | `bool` | `true` | 是否校验下载文件 Hash。 | -| `Option.RetryInterval` | `TimeSpan` | `1s` | 下载重试间隔。 | - -如果传入 `null` 给可空选项,`SetOption` 会移除当前设置,后续读取回到默认值。 - -## 事件 API {#事件-api} - -事件适合观察更新过程,不应该承载复杂业务流程。复杂流程建议封装成 `IUpdateHooks` 或 cookbook 中的完整方案。 - -### 单个事件回调 - -单个事件回调适合在启动器链式配置中直接订阅某一个通知。Core 内部通过全局 `EventManager` 按 `EventArgs` 类型分发事件;同一类型可以注册多个回调,某个回调抛出的异常会被记录到 `GeneralTracer`,不会阻断其他回调。 - -这些回调通常可能在更新流程线程、下载任务线程或差分任务线程中触发,不会自动切回 UI 线程。WPF、Avalonia、WinUI、MAUI 等客户端需要在回调里把 UI 更新切回 Dispatcher / SynchronizationContext;耗时业务也建议投递到队列或后台任务,避免阻塞下载和差分并行度。 - -| 方法 | 参数类型 | 当前代码中的触发时机 | 关键字段 | 推荐用途 | -| --- | --- | --- | --- | --- | -| `AddListenerUpdateInfo` | `UpdateInfoEventArgs` | 标准 `Client` 策略完成版本对比后触发。没有可更新内容时也会触发一次,`Info.Code` 为 `404`、`Info.Body` 为空列表;有更新时 `Info.Body` 是需要下载的 `VersionEntry` 列表。 | `Info.Code`、`Info.Message`、`Info.Body`;`VersionEntry` 包含 `RecordId`、`Name`、`Version`、`Url`、`Hash`、`AppType`、`IsForcibly`、`UpgradeMode`、`FromVersion`、`ToVersion` 等。 | 展示更新说明、版本数量、强制更新提示,或者记录服务端返回的版本元数据。不要在这里做文件替换。 | -| `AddListenerUpdatePrecheck` | `Func` | `UpdateInfo` 事件之后、Hook 和下载之前执行。按当前 `ClientStrategy.CanSkip` 实现:非强制更新下返回 `true` 表示跳过本次更新,返回 `false` 表示继续;强制更新不会进入跳过判断。 | 入参同 `UpdateInfoEventArgs`,可以读取本次更新涉及的所有 `VersionEntry`,包括版本号、更新说明、Hash、包地址、升级模式、跨版本范围等。 | 做下载前的轻量决策,也可以整理版本信息弹窗给用户,让用户阅读更新内容后决定是否继续。需要异步、可取消或有副作用的流程请使用 `IUpdateHooks.OnBeforeUpdateAsync`。 | -| `AddListenerProgress` | `ProgressEventArgs` | 默认下载通道报告 `DownloadProgress` 时触发;差分 Clean / Dirty 管道报告 `DiffProgress` 时也会触发。同一个事件参数中 `Progress` 和 `DiffProgress` 只会有一个非空。 | 下载:`Progress.AssetName`、`BytesDownloaded`、`TotalBytes`、`Percentage`、`Status`。差分:`DiffProgress.Completed`、`Total`、`CurrentFile`、`Percentage`、`IsComplete`、`Error`。 | 更新进度条、状态文本、下载速度/大小展示、差分补丁进度展示。默认下载进度应优先使用这个事件。 | -| `AddListenerMultiDownloadCompleted` | `MultiDownloadCompletedEventArgs` | `DownloadProgressReporter` 收到 `DownloadStatus.Completed` 时触发。当前默认桥接中 `Version` 实际携带 `AssetName`,自定义下载器也可以放入自己的对象。 | `Version`、`IsCompleted`。 | 标记某个资源包下载完成、追加下载日志。不要把它当作“全部资源下载完成”。 | -| `AddListenerMultiAllDownloadCompleted` | `MultiAllDownloadCompletedEventArgs` | `DefaultDownloadOrchestrator` 等待所有下载任务结束后触发一次。并发下载时它在所有任务都完成、失败结果收集完之后触发。 | `IsAllDownloadCompleted`;`FailedVersions` 是失败明细列表,元素为 `(asset, errorMessage)`。 | 在所有资源下载结束后刷新整体 UI、输出失败汇总、决定是否展示重试入口。 | -| `AddListenerMultiDownloadError` | `MultiDownloadErrorEventArgs` | `DownloadProgressReporter` 收到 `DownloadStatus.Failed` 时触发。当前默认桥接中的 `Version` 可能是 `AssetName`。 | `Exception`、`Version`。 | 记录单个资源下载失败、展示失败项、触发外部监控。整体是否成功仍以 `MultiAllDownloadCompleted` 为准。 | -| `AddListenerMultiDownloadStatistics` | `MultiDownloadStatisticsEventArgs` | 兼容旧下载统计或自定义下载实现的事件。当前默认下载编排主要通过 `AddListenerProgress` 分发下载进度,不会额外合成统计事件。 | `Version`、`Remaining`、`Speed`、`TotalBytesToReceive`、`BytesReceived`、`ProgressPercentage`。 | 如果你接入了仍会分发该事件的下载器,可以用它展示剩余时间和速度;新代码建议优先监听 `AddListenerProgress`。 | -| `AddListenerException` | `ExceptionEventArgs` | `GeneralUpdateBootstrap`、各平台策略、标准策略、OSS 策略和更新策略捕获异常时触发。 | `Exception`、`Message`。 | 上报异常、展示错误信息、写入业务日志。这个事件表示异常已经被 Core 捕获并通知,不等同于自动重试。 | - -`UpdateInfoEventArgs.Info.Body` 中的元素是 Core 经过版本对比、应用类型筛选和下载计划构建后需要处理的版本包,不是简单的原始 HTTP 响应透传。需要关注下载 URL、Hash、强制更新、跨版本差分范围时,可以直接读取 `VersionEntry` 上的属性。 - -`AddListenerUpdatePrecheck` 的返回值容易误解:以当前代码为准,返回 `true` 是“可以跳过”,不是“继续下载”。它适合放在“下载前确认”这个场景里:先从 `UpdateInfoEventArgs.Info.Body` 整理本次更新涉及的版本号、更新日志、包大小、升级类型等内容,弹窗给用户阅读;用户确认更新时返回 `false` 继续,用户选择稍后、磁盘不足或当前网络不允许时返回 `true` 跳过非强制更新。如果只是展示服务端版本信息、不需要决定是否跳过,可以只监听 `AddListenerUpdateInfo`。 - -```csharp -await new GeneralUpdateBootstrap() - .SetConfig(request) - .AddListenerUpdateInfo((_, e) => - { - Console.WriteLine($"Versions from server: {e.Info?.Body?.Count ?? 0}"); - }) - .AddListenerUpdatePrecheck(e => - { - var versions = e.Info?.Body; - var hasUpdate = (versions?.Count ?? 0) > 0; - var enoughDisk = DriveInfo.GetDrives() - .Where(d => d.IsReady) - .Any(d => d.AvailableFreeSpace > 1024L * 1024 * 1024); - var userRejected = versions != null && !ShowUpdateDialog(versions); - - // 当前实现中返回 true 表示跳过非强制更新,返回 false 表示继续。 - return !hasUpdate || !enoughDisk || userRejected; - }) - .AddListenerMultiDownloadCompleted((_, e) => - { - Console.WriteLine($"{e.Version}: {(e.IsCompleted ? "completed" : "failed")}"); - }) - .AddListenerMultiAllDownloadCompleted((_, e) => - { - Console.WriteLine(e.IsAllDownloadCompleted - ? "All downloads completed." - : $"Failed downloads: {e.FailedVersions.Count}"); - }) - .AddListenerMultiDownloadError((_, e) => - { - Console.WriteLine($"Download failed: {e.Version}"); - Console.WriteLine(e.Exception); - }) + .SetOption(Option.PatchEnabled, true) .AddListenerProgress((_, e) => { if (e.Progress != null) - Console.WriteLine($"Download {e.Progress.AssetName}: {e.Progress.Percentage:F1}% {e.Progress.Status}"); - - if (e.DiffProgress != null) - Console.WriteLine($"Patch: {e.DiffProgress.Completed}/{e.DiffProgress.Total} {e.DiffProgress.CurrentFile}"); - }) - .AddListenerException((_, e) => - { - Console.WriteLine(e.Message); - Console.WriteLine(e.Exception); + Console.WriteLine($"{e.Progress.AssetName}: {e.Progress.Percentage:F1}%"); }) + .AddListenerException((_, e) => Console.WriteLine(e.Exception)) .LaunchAsync(); ``` -### 批量事件监听器 - -实现 `IUpdateEventListener` 可以把事件处理集中到一个类。若只关心部分事件,继承 `UpdateEventListenerBase` 更简单。 +### 5.2.1 SignalR 实时推送快速入门 ```csharp -using GeneralUpdate.Core.Download; -using GeneralUpdate.Core.Event; - -public sealed class ConsoleUpdateListener : UpdateEventListenerBase -{ - public override void OnUpdateInfo(UpdateInfoEventArgs args) - { - Console.WriteLine($"Update count: {args.Info?.Body?.Count ?? 0}"); - } - - public override void OnProgress(ProgressEventArgs args) - { - if (args.Progress != null) - Console.WriteLine($"{args.Progress.AssetName}: {args.Progress.Percentage:F1}%"); - } - - public override void OnException(ExceptionEventArgs args) - { - Console.WriteLine(args.Exception); - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .AddEventListener() - .LaunchAsync(); -``` - -## 日志与性能 {#logging-performance} - -Core 内置 `GeneralTracer`,默认开启。它基于 `System.Diagnostics.Trace` 输出日志:Windows 下会写入调试输出窗口,同时写到控制台,并在应用基目录创建 `Logs/generalupdate-trace yyyy-MM-dd.log` 文件。文件监听器使用后台队列写入,但每条日志仍会做开关检查、时间格式化、调用栈定位和入队/输出,因此在性能敏感场景可以关闭。 - -| API | 作用 | -| --- | --- | -| `GeneralTracer.SetTracingEnabled(false)` | 关闭 Core 日志输出。关闭后 `Debug` / `Info` / `Warn` / `Error` / `Fatal` 会快速返回,同时 Trace listener 被过滤。 | -| `GeneralTracer.SetTracingEnabled(true)` | 重新开启日志输出。适合诊断版本、灰度排查或用户反馈问题时启用。 | -| `GeneralTracer.IsTracingEnabled()` | 查询当前日志开关状态。 | -| `GeneralTracer.Dispose()` | 释放文件监听器并清空 Trace listeners,通常只在测试、工具进程退出或你明确接管 Trace listeners 时使用。 | +using GeneralUpdate.Core.Hubs; -建议在应用启动早期设置日志开关,确保更新流程中的下载、校验、差分和替换日志都遵循同一个策略。 +// 创建推送客户端 +var hub = new UpgradeHubService( + "http://localhost:5000/UpgradeHub", + appkey: Guid.NewGuid().ToString()); -```csharp -using GeneralUpdate.Core; - -if (performanceMode) +// 订阅推送消息 +hub.AddListenerReceive((message) => { - GeneralTracer.SetTracingEnabled(false); -} - -await new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret") - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -关闭日志适合低功耗设备、I/O 较慢的终端、静默后台轮询、批量自动化更新或对启动耗时极敏感的产品形态。排查线上问题时建议临时开启,因为 Core 的日志会记录策略分派、下载编排、校验、差分、Hook 和异常路径。 + Console.WriteLine($"收到更新推送: {message}"); +}); -## 扩展点总览 +// 建立连接 +await hub.StartAsync(); -扩展点由 `AbstractBootstrap` 提供,所有注册方法都返回当前 bootstrap,可链式调用。 +Console.WriteLine("已连接,等待服务端推送..."); +Console.ReadLine(); -| 注册方法 | 接口 | 影响范围 | -| --- | --- | --- | -| `Hooks()` | `IUpdateHooks` | 更新生命周期前后置逻辑。 | -| `UpdateReporter()` | `IUpdateReporter` | 更新状态上报。 | -| `SslPolicy()` | `ISslValidationPolicy` | HTTPS 证书校验。 | -| `HttpAuth()` | `IHttpAuthProvider` | HTTP 请求认证。 | -| `DownloadSource()` | `IDownloadSource` | 版本清单和下载资源来源。 | -| `DownloadPolicy()` | `IDownloadPolicy` | 下载重试、超时、熔断等策略。 | -| `DownloadExecutor()` | `IDownloadExecutor` | 单文件下载实现。 | -| `DownloadPipeline()` | `IDownloadPipeline` | 下载后处理,例如校验、解密、扫描。 | -| `DownloadOrchestrator()` | `IDownloadOrchestrator` | 批量下载完整编排。 | -| `Strategy()` | `IStrategy` | 自定义平台级更新策略。 | +// 停止连接(保留重连能力) +await hub.StopAsync(); -> 通过这些方法注册的类型必须有无参构造函数,因为 Core 使用 `new()` 或反射创建实例。需要复杂依赖时,建议在自定义类型内部读取配置,或在应用层封装一个无参适配器。 +// 释放资源(不可再复用) +await hub.DisposeAsync(); +``` -## 生命周期钩子:IUpdateHooks +### 5.3 真实业务落地示例(多参数联动) -`IUpdateHooks` 适合处理“更新前检查、下载完成后处理、更新完成后清理、启动应用前准备、异常处理”等业务逻辑。它也是一个非常灵活的开放点:在 Linux 或 macOS 上,更新后的可执行文件可能需要重新赋予执行权限,或者需要先执行企业内部的授权脚本、签名校验脚本、权限修复脚本,再启动主程序;这些操作都可以放在 `OnBeforeStartAppAsync` 中完成。 +完整 Client 端更新流程,包含事件监听、差分管道、并发控制和状态上报: ```csharp +using GeneralUpdate.Core; +using GeneralUpdate.Core.Configuration; +using GeneralUpdate.Core.Download; +using GeneralUpdate.Core.Download.Reporting; +using GeneralUpdate.Core.Event; using GeneralUpdate.Core.Hooks; +using GeneralUpdate.Core.Pipeline; +using GeneralUpdate.Differential.Differ; -public sealed class ProductUpdateHooks : IUpdateHooks +// 1. 构建配置 +var request = new UpdateRequestBuilder() + .SetUpdateUrl("https://update.mycompany.com/api/upgrade/verification") + .SetReportUrl("https://update.mycompany.com/api/upgrade/report") + .SetUpgradeAppName("MyApp.Upgrade.exe") + .SetMainAppName("MyApp.exe") + .SetClientVersion("1.0.0") + .SetUpgradeClientVersion("1.0.0") + .SetAppSecretKey("prod-secret-key") + .SetProductId("my-product") + .SetInstallPath(AppDomain.CurrentDomain.BaseDirectory) + .SetScheme("Bearer") + .SetToken(Environment.GetEnvironmentVariable("UPDATE_TOKEN") ?? "") + .SetFiles(new List { "appsettings.Development.json" }) + .SetFormats(new List { ".log", ".tmp", ".pdb" }) + .SetDirectories(new List { "logs", "cache", "temp" }) + .Build(); + +// 2. 注入业务钩子 +public sealed class BusinessUpdateHooks : IUpdateHooks { public Task OnBeforeUpdateAsync(HookContext ctx) { - Console.WriteLine($"Before update: {ctx.CurrentVersion} -> {ctx.TargetVersion}"); + // 检查磁盘空间 + var drive = new DriveInfo(Path.GetPathRoot(ctx.InstallPath)!); + if (drive.AvailableFreeSpace < 1024L * 1024 * 1024) // < 1GB + return Task.FromResult(false); return Task.FromResult(true); } public Task OnDownloadCompletedAsync(DownloadContext ctx) - { - Console.WriteLine($"Downloaded {ctx.AssetName}, success={ctx.Success}, path={ctx.LocalPath}"); - return Task.CompletedTask; - } + => Task.CompletedTask; public Task OnAfterUpdateAsync(HookContext ctx) { - File.WriteAllText(Path.Combine(ctx.InstallPath, "last-update.txt"), DateTimeOffset.Now.ToString("O")); + // 写更新成功日志 + File.AppendAllText( + Path.Combine(ctx.InstallPath, "update.log"), + $"{DateTimeOffset.Now:O} Updated to {ctx.TargetVersion}{Environment.NewLine}"); return Task.CompletedTask; } public Task OnUpdateErrorAsync(HookContext ctx, Exception ex) { - File.AppendAllText(Path.Combine(ctx.InstallPath, "update-error.log"), ex + Environment.NewLine); + // 上报到监控系统 return Task.CompletedTask; } public Task OnBeforeStartAppAsync(HookContext ctx) - { - Console.WriteLine($"Starting app from {ctx.InstallPath}"); - return Task.CompletedTask; - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .Hooks() - .LaunchAsync(); -``` - -Linux/macOS 场景可以直接注册内置的 `UnixPermissionHooks`,让 Core 在启动应用前执行 `chmod +x`: - -```csharp -await new GeneralUpdateBootstrap() - .SetConfig(request) - .Hooks() - .LaunchAsync(); -``` - -如果需要执行自己的赋权脚本,可以封装一个无参 hook 适配器,再通过 `Hooks()` 注册: - -```csharp -using GeneralUpdate.Core.Hooks; - -public sealed class ProductPermissionHooks : IUpdateHooks -{ - private readonly CustomPermissionHooks _inner = - new("/opt/my-product/scripts/fix-permissions.sh"); - - public Task OnBeforeStartAppAsync(HookContext ctx) - => _inner.OnBeforeStartAppAsync(ctx); - - public Task OnBeforeUpdateAsync(HookContext ctx) => Task.FromResult(true); - public Task OnDownloadCompletedAsync(DownloadContext ctx) => Task.CompletedTask; - public Task OnAfterUpdateAsync(HookContext ctx) => Task.CompletedTask; - public Task OnUpdateErrorAsync(HookContext ctx, Exception ex) => Task.CompletedTask; + => Task.CompletedTask; } -await new GeneralUpdateBootstrap() - .SetConfig(request) - .Hooks() - .LaunchAsync(); -``` - -内置实现包括: - -| 类型 | 说明 | -| --- | --- | -| `NoOpUpdateHooks` | 默认空实现。 | -| `UnixPermissionHooks` | 在 Unix-like 系统启动前执行 `chmod +x`。 | -| `CustomPermissionHooks` | 执行自定义权限脚本;该类型构造函数需要参数,不适合直接用 `Hooks()` 注册,可自行包装无参适配器。 | - -## 状态上报:IUpdateReporter - -`IUpdateReporter` 用于把更新状态上报到服务端。 - -```csharp -using GeneralUpdate.Core.Download.Reporting; - -public sealed class ConsoleUpdateReporter : IUpdateReporter +// 3. 配置差分管道 +Action configurePipeline = builder => { - public Task ReportAsync(UpdateReport report, CancellationToken token = default) - { - Console.WriteLine($"Report: record={report.RecordId}, status={report.Status}, type={report.Type}"); - return Task.CompletedTask; - } -} + builder + .UseDiffer(new StreamingHdiffDiffer()) + .WithParallelism(4) + .WithStopOnFirstError(true) + .WithProgress(new Progress(p => + { + Console.WriteLine($"Patch: {p.Completed}/{p.Total} {p.CurrentFile} {p.Percentage}%"); + })); +}; -await new GeneralUpdateBootstrap() +// 4. 启动更新 +var bootstrap = new GeneralUpdateBootstrap() .SetConfig(request) - .UpdateReporter() - .LaunchAsync(); -``` - -内置 `HttpUpdateReporter` 会向 `ReportUrl` 发送 JSON: - -```json -{ - "recordId": 123, - "status": 1, - "type": 1 -} -``` - -状态值: - -| 枚举 | 值 | 说明 | -| --- | --- | --- | -| `UpdateStatus.Updating` | `1` | 更新中。 | -| `UpdateStatus.Success` | `2` | 更新成功。 | -| `UpdateStatus.Failure` | `3` | 更新失败。 | - -## HTTP 认证:IHttpAuthProvider - -`IHttpAuthProvider` 可以为 Core 发出的 HTTP 请求追加认证头。 - -```csharp -using GeneralUpdate.Core.Security; - -public sealed class StaticBearerAuthProvider : IHttpAuthProvider -{ - public Task ApplyAuthAsync(HttpRequestMessage request, CancellationToken token = default) + .SetOption(Option.AppType, AppType.Client) + .SetOption(Option.DiffMode, DiffMode.Parallel) + .SetOption(Option.MaxConcurrency, 4) + .SetOption(Option.DownloadTimeout, 120) + .SetOption(Option.PatchEnabled, true) + .SetOption(Option.BackupEnabled, true) + .SetOption(Option.VerifyChecksum, true) + .SetOption(Option.RetryCount, 5) + .SetOption(Option.RetryInterval, TimeSpan.FromSeconds(2)) + .Hooks() + .UseDiffPipeline(configurePipeline) + .AddListenerUpdateInfo((_, e) => { - request.Headers.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "access-token"); - - return Task.CompletedTask; - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .HttpAuth() - .LaunchAsync(); -``` - -Core 内置的认证类型包括 `NoOpAuthProvider`、`BearerTokenAuthProvider`、`ApiKeyAuthProvider` 和 `HmacAuthProvider`。这些类型中部分构造函数需要参数,因此如果要通过 `HttpAuth()` 注册,通常需要写一个无参包装类。 - -## HTTPS 证书策略:ISslValidationPolicy - -`ISslValidationPolicy` 用于控制 HTTPS 证书校验。默认 `StrictSslValidationPolicy` 只接受没有 SSL policy errors 的证书。 - -```csharp -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using GeneralUpdate.Core.Security; - -public sealed class DevelopmentSslPolicy : ISslValidationPolicy -{ - public bool ValidateCertificate( - X509Certificate2? certificate, - X509Chain? chain, - SslPolicyErrors sslPolicyErrors) + if (e.Info?.Code == "404") + { + Console.WriteLine("Already up to date."); + return; + } + Console.WriteLine($"Found {e.Info?.Body?.Count ?? 0} version(s) to download."); + }) + .AddListenerUpdatePrecheck(e => { - return sslPolicyErrors == SslPolicyErrors.None - || certificate?.Issuer.Contains("CN=Local Dev Root") == true; - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .SslPolicy() - .LaunchAsync(); -``` - -生产环境不建议无条件返回 `true`,否则会绕过 HTTPS 的安全保证。 - -## 下载来源:IDownloadSource - -`IDownloadSource` 负责返回待下载资源列表。适合接入私有服务、文件服务器、配置中心或自定义云存储。 - -```csharp -using GeneralUpdate.Core.Download.Abstractions; -using GeneralUpdate.Core.Download.Models; - -public sealed class StaticDownloadSource : IDownloadSource -{ - public Task ListAsync(CancellationToken token = default) + var hasUpdate = (e.Info?.Body?.Count ?? 0) > 0; + return !hasUpdate; // 有更新则继续(返回 false = 不跳过) + }) + .AddListenerProgress((_, e) => { - var assets = new[] - { - new DownloadAsset( - Name: "app-1.0.1.zip", - Url: "https://cdn.example.com/releases/app-1.0.1.zip", - Size: 25_000_000, - SHA256: "expected-sha256", - Version: "1.0.1") - }; - - return Task.FromResult(new DownloadSourceResult + if (e.Progress != null) { - Assets = assets, - HasMainUpdate = true, - HasUpgradeUpdate = false - }); - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .DownloadSource() - .LaunchAsync(); -``` - -## 下载重试策略:IDownloadPolicy - -`IDownloadPolicy` 包装单次下载动作,适合实现重试、超时、熔断或限流。 - -```csharp -using GeneralUpdate.Core.Download.Abstractions; - -public sealed class TwoAttemptDownloadPolicy : IDownloadPolicy -{ - public async Task ExecuteAsync( - Func> action, - CancellationToken token = default) - { - try + var pct = e.Progress.Percentage; + var status = e.Progress.Status; + var name = e.Progress.AssetName; + Console.WriteLine($"[Download] {name}: {pct:F1}% ({status})"); + } + if (e.DiffProgress != null) { - return await action(token); + var d = e.DiffProgress; + Console.WriteLine($"[Patch] {d.CurrentFile}: {d.Completed}/{d.Total} {d.Percentage}%"); } - catch when (!token.IsCancellationRequested) + }) + .AddListenerMultiAllDownloadCompleted((_, e) => + { + if (e.IsAllDownloadCompleted) + Console.WriteLine("All downloads completed successfully."); + else { - await Task.Delay(TimeSpan.FromSeconds(2), token); - return await action(token); + Console.WriteLine($"Download completed with {e.FailedVersions.Count} failure(s):"); + foreach (var (asset, error) in e.FailedVersions) + Console.WriteLine($" - {asset.Name}: {error}"); } - } -} + }) + .AddListenerException((_, e) => + { + Console.WriteLine($"Update error: {e.Message}"); + Console.WriteLine(e.Exception); + }); -await new GeneralUpdateBootstrap() - .SetConfig(request) - .DownloadPolicy() - .LaunchAsync(); +await bootstrap.LaunchAsync(); ``` -如果同时注册 `DownloadOrchestrator()`,自定义 orchestrator 会接管完整下载流程,`DownloadPolicy()` 是否生效取决于 orchestrator 自己是否使用该策略。 +--- -## 单文件下载:IDownloadExecutor +## 6. 全局配置 -`IDownloadExecutor` 负责把一个 `DownloadAsset` 下载到目标路径。适合支持 FTP、SFTP、私有协议或自定义 HTTP 客户端。 +Core 不依赖全局配置文件,而是通过 `generalupdate.manifest.json` 提供应用身份信息。下面是 manifest 的配置语法和优先级规则。 -```csharp -using GeneralUpdate.Core.Download.Abstractions; -using GeneralUpdate.Core.Download.Models; +### Manifest 配置语法 -public sealed class MirrorDownloadExecutor : IDownloadExecutor +```json { - private readonly HttpClient _client = new(); - - public async Task ExecuteAsync( - DownloadAsset asset, - string destPath, - IProgress? progress = null, - CancellationToken token = default) - { - var started = DateTimeOffset.Now; - await using var input = await _client.GetStreamAsync(asset.Url, token); - await using var output = File.Create(destPath); - await input.CopyToAsync(output, token); - - var fileInfo = new FileInfo(destPath); - return new DownloadResult(asset, destPath, fileInfo.Length, DateTimeOffset.Now - started, 0, true, null); - } + "mainAppName": "ClientSample.exe", + "clientVersion": "1.0.0", + "appType": "Client", + "updateAppName": "UpgradeSample.exe", + "upgradeClientVersion": "1.0.0", + "productId": "sample-product", + "updatePath": "update/" } - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .DownloadExecutor() - .LaunchAsync(); ``` -## 下载后处理:IDownloadPipeline - -`IDownloadPipeline` 在文件下载完成后运行。适合做 Hash 校验、解密、病毒扫描、格式转换等。 +### 配置优先级规则 -```csharp -using GeneralUpdate.Core.Download.Abstractions; +| 优先级 | 配置来源 | 说明 | +| --- | --- | --- | +| 1(最高) | 代码中 `SetConfig(UpdateRequest)` 或 `SetSource(...)` 显式设置的值 | 覆盖所有其他来源 | +| 2 | `generalupdate.manifest.json` 中的字段 | 自动补齐代码中未显式设置的字段 | +| 3(最低) | 组件内部默认值 | `UpdateAppName = "Update.exe"`, `InstallPath = BaseDirectory` 等 | -public sealed class AntivirusPipeline : IDownloadPipeline -{ - public Task ProcessAsync(string downloadedPath, CancellationToken token = default) - { - if (!File.Exists(downloadedPath)) - throw new FileNotFoundException("Downloaded file not found.", downloadedPath); +### Manifest 字段映射 - Console.WriteLine($"Scanning {downloadedPath}"); - return Task.FromResult(downloadedPath); - } -} +| JSON 字段 | Core 字段 | 说明 | +| --- | --- | --- | +| `mainAppName` | `MainAppName` | 主程序可执行文件名 | +| `clientVersion` | `ClientVersion` | 当前主程序版本 | +| `appType` | `AppType` | 当前进程角色(`Client`, `Upgrade`, `OssClient`, `OssUpgrade`) | +| `updateAppName` | `UpdateAppName` | 升级程序文件名 | +| `upgradeClientVersion` | `UpgradeClientVersion` | 升级程序自身版本 | +| `productId` | `ProductId` | 产品标识 | +| `updatePath` | `UpdatePath` | 升级程序所在目录(相对 `InstallPath`) | -await new GeneralUpdateBootstrap() - .SetConfig(request) - .DownloadPipeline() - .LaunchAsync(); -``` +### 版本回写机制 -Core 在创建下载管道时会优先尝试使用 `string` 构造函数传入期望 Hash;如果没有该构造函数,则使用无参构造函数。 +更新成功后,Core 自动回写客户端版本到 `generalupdate.manifest.json`: -## 批量下载编排:IDownloadOrchestrator +| 场景 | 回写字段 | +| --- | --- | +| 主程序更新完成 | `ClientVersion` | +| 升级程序自身更新完成 | `UpgradeClientVersion` | -`IDownloadOrchestrator` 是下载子系统的最高层扩展点。注册后,它会接管批量下载、并发控制、重试、进度和结果汇总。 +### 日志配置 ```csharp -using GeneralUpdate.Core.Download.Abstractions; -using GeneralUpdate.Core.Download.Executors; -using GeneralUpdate.Core.Download.Models; +using GeneralUpdate.Core; -public sealed class SerialDownloadOrchestrator : IDownloadOrchestrator -{ - private readonly IDownloadExecutor _executor = new HttpDownloadExecutor(new HttpClient()); - - public async Task ExecuteAsync( - DownloadPlan plan, - string destDir, - int maxConcurrency = 3, - IProgress? progress = null, - CancellationToken token = default) - { - var results = new List(); - var started = DateTimeOffset.Now; +// 关闭日志(性能敏感场景) +GeneralTracer.SetTracingEnabled(false); - foreach (var asset in plan.Assets) - { - var destPath = Path.Combine(destDir, asset.Name); - results.Add(await _executor.ExecuteAsync(asset, destPath, progress, token)); - } +// 重新开启(排查问题) +GeneralTracer.SetTracingEnabled(true); - return new DownloadReport( - results, - results.Where(r => r.Success).Sum(r => r.DownloadedBytes), - DateTimeOffset.Now - started, - results.Count(r => r.Success), - results.Count(r => !r.Success)); - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .DownloadOrchestrator() - .LaunchAsync(); +// 释放日志资源 +GeneralTracer.Dispose(); ``` -只有当你需要完整替换下载行为时才建议实现 orchestrator。多数情况下替换 `IDownloadExecutor`、`IDownloadPolicy` 或 `IDownloadPipeline` 就够了。 +### AOT / Trim 兼容性 -## 平台策略:IStrategy +`net8.0` 和 `net10.0` 目标框架支持 AOT 发布和裁剪。Core 在 `JsonContext` 命名空间下提供了 9 个源生成 JSON 序列化上下文,覆盖所有配置和模型类型的序列化需求,避免反射导致的裁剪问题: -`IStrategy` 是最高级别的更新策略接口。Core 内置 `ClientStrategy`、`UpdateStrategy`、`OssStrategy` 以及 Windows/Linux/macOS 平台策略。只有在你需要替换平台级文件操作或启动逻辑时,才应实现它。 +| 上下文类 | 覆盖类型 | +| --- | --- | +| `BaseResponseJsonContext` | `BaseResponseDTO`、`VersionRespDTO` | +| `VersionEntryJsonContext` | `VersionEntry`、`VersionIdentity` | +| `UpdateRequestJsonContext` | `UpdateRequest`、`UpdateConfiguration` | +| `ProcessContractJsonContext` | `ProcessContract` | +| `ManifestInfoJsonContext` | `ManifestInfo` | +| `OssVersionRecordJsonContext` | `OssVersionRecord` | +| `DownloadAssetJsonContext` | `DownloadAsset` | +| `BlackPolicyJsonContext` | `BlackPolicy` | +| `PushPayloadJsonContext` | `PushPayload` | -```csharp -using GeneralUpdate.Core.Configuration; -using GeneralUpdate.Core.Download.Reporting; -using GeneralUpdate.Core.Hooks; -using GeneralUpdate.Core.Strategy; +### 进程环境变量(IPC 辅助) -public sealed class LoggingStrategy : IStrategy -{ - private UpdateContext? _context; +`Environments` 静态类通过 AES 加密临时文件在 Client 与 Upgrade 进程之间传递键值对环境变量,用于升级失败追踪等场景: - public IUpdateHooks Hooks { get; set; } = new NoOpUpdateHooks(); - public IUpdateReporter Reporter { get; set; } = new HttpUpdateReporter(); - - public void Create(UpdateContext parameter) - { - _context = parameter; - } +| 环境变量 Key | 说明 | +| --- | --- | +| `UpgradeFail` | 记录上次升级失败的版本号,避免重复尝试失败版本 | - public async Task ExecuteAsync() - { - if (_context == null) - throw new InvalidOperationException("Strategy was not initialized."); - - Console.WriteLine($"Custom strategy executing in {_context.InstallPath}"); - await Hooks.OnBeforeUpdateAsync(new HookContext( - _context.UpdateAppName, - _context.InstallPath, - _context.ClientVersion, - _context.LastVersion, - _context.AppType ?? AppType.Client)); - } +```csharp +using GeneralUpdate.Core.Configuration; - public Task StartAppAsync() - { - Console.WriteLine("Custom start app logic."); - return Task.CompletedTask; - } -} +// 设置环境变量(Client 端写入) +Environments.Set("UpgradeFail", "2.0.0"); -await new GeneralUpdateBootstrap() - .SetConfig(request) - .Strategy() - .LaunchAsync(); +// 读取环境变量(Upgrade 端读取) +var failedVersion = Environments.Get("UpgradeFail"); ``` -> 静默更新不是单独的扩展接口,而是内置执行策略。配置方式和生命周期见 [静默更新策略](#silent-update-strategy)。 - -## 与 GeneralUpdate.Tools 的关系 +--- -Core 消费更新清单和更新包;`GeneralUpdate.Tools` 负责辅助生成和验证这些产物。 +## 相关资源 -| Tools 能力 | Core 中对应消费点 | -| --- | --- | -| Patch Package | `Option.PatchEnabled`、`UseDiffPipeline`、差分补丁处理。 | -| Manifest Generator | `ManifestInfo`、`AppMetadataDiscoverer`、版本回写。 | -| Extension Package | 作为更新包内容或扩展包分发,由下载和部署流程消费。 | -| OSS Config | `OssClient` / `OssUpgrade` 角色读取 OSS 配置并下载。 | -| Hash / Simulation / Report | 对应 `Option.VerifyChecksum`、下载后校验和状态上报。 | - -## 相关示例 - -- [Upgrade sample](https://github.com/GeneralLibrary/GeneralUpdate-Samples/blob/main/src/Upgrade/Program.cs) -- [OSS upgrade sample](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/OSS/OSSUpgradeSample) -- [GeneralUpdate repository](https://github.com/GeneralLibrary/GeneralUpdate) -- [GeneralUpdate.Tools repository](https://github.com/GeneralLibrary/GeneralUpdate.Tools) +- [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) +- [Samples 示例代码](https://github.com/GeneralLibrary/GeneralUpdate-Samples) +- [GeneralUpdate.Tools](https://github.com/GeneralLibrary/GeneralUpdate.Tools) +- [入门实战手册](../quickstart/Beginner%20cookbook.md) +- [组件文档](./GeneralUpdate.Core.md) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md index 60c72b8..c78d73f 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md @@ -4,439 +4,364 @@ sidebar_position: 6 # GeneralUpdate.Differential -**命名空间:** `GeneralUpdate.Differential`、`GeneralUpdate.Differential.Differ`、`GeneralUpdate.Differential.Abstractions` | **主要入口:** `IBinaryDiffer`、`BsdiffDiffer`、`StreamingHdiffDiffer` | **NuGet 包:** `GeneralUpdate.Differential` +**命名空间:** `GeneralUpdate.Differential` | **主要入口:** `IBinaryDiffer`、`BsdiffDiffer`、`StreamingHdiffDiffer` | **NuGet 包:** `GeneralUpdate.Differential` -`GeneralUpdate.Differential` 是 GeneralUpdate 的二进制差分组件,专注解决”一个旧文件 + 一个补丁文件 = 一个新文件”的问题。它提供可替换的文件级差分算法、补丁压缩抽象和 BSDIFF 兼容补丁读写能力;目录级对比、批量补丁生成、并行调度、删除文件处理和更新流程编排由 `GeneralUpdate.Core` 的 `DiffPipeline` 或 `GeneralUpdate.Tools` 承担。 +## 1. 组件简介 -## 文档大纲与知识点导航 {#knowledge-map} +### 1.1 组件概述 -如果你第一次阅读 Differential 文档,可以先看这个导航,再跳到对应知识点。本文按照“能力边界 -> 文件级 API -> 算法选择 -> 压缩格式 -> 与 Core/Tools 集成 -> 性能与扩展”的顺序组织。 +**GeneralUpdate.Differential** 是 GeneralUpdate 的二进制差分组件,专注解决"一个旧文件 + 一个补丁文件 = 一个新文件"的问题。它提供可替换的文件级差分算法(BSDIFF 4.0 / Streaming HDiff)、补丁压缩抽象(BZip2 / Deflate,源码中预留 .NET 6+ Brotli)和 BSDIFF 兼容补丁读写能力。 -| 你想了解什么 | 推荐阅读 | -| --- | --- | -| Differential 到底负责什么、不负责什么 | [组件能力边界](#组件能力边界) | -| `Clean` / `Dirty` 是什么含义 | [Clean 与 Dirty 语义](#clean-与-dirty-语义) | -| 如何给单个文件生成并应用补丁 | [单文件快速开始](#单文件快速开始) | -| 使用 Core 时是否还要手动集成 Differential | [与 GeneralUpdate.Core 的关系](#与-generalupdatecore-的关系) | -| 当前有哪些差分算法,如何选择 | [差分算法选择](#差分算法选择) | -| BSDIFF 补丁格式和压缩字节怎么工作 | [补丁格式与压缩 Provider](#补丁格式与压缩-provider) | -| 如何在 Core 更新流程里启用目录级差分 | [与 GeneralUpdate.Core 的关系](#与-generalupdatecore-的关系) | -| Tools 构建差分包时用了什么能力 | [与 GeneralUpdate.Tools 的关系](#与-generalupdatetools-的关系) | -| 下载和差分是否可以多线程并行 | [并发模型与性能建议](#并发模型与性能建议) | -| 大型项目如何提升差分构建效率 | [大型项目并行差分](#大型项目并行差分) | -| 如何接入自定义差分算法或压缩方式 | [扩展点](#扩展点) | - -## 组件能力边界 - -Differential 是底层文件补丁库,不是完整的更新编排器。理解这个边界可以避免把旧文档里的 `DifferentialCore`、黑名单、目录批量处理等概念误认为当前组件 API。 - -| 能力 | Differential 是否负责 | 说明 | -| --- | --- | --- | -| 单文件二进制补丁生成 | 是 | 通过 `IBinaryDiffer.CleanAsync(oldFile, newFile, patchFile)` 完成。 | -| 单文件二进制补丁应用 | 是 | 通过 `IBinaryDiffer.DirtyAsync(oldFile, outputNewFile, patchFile)` 完成。 | -| 差分算法实现 | 是 | 当前主要实现为 `BsdiffDiffer` 和 `StreamingHdiffDiffer`。 | -| 补丁数据压缩/解压 | 是 | 通过 `ICompressionProvider` 抽象,内置 BZip2、Deflate,源码中预留 .NET 6+ Brotli。 | -| 目录级新旧版本对比 | 否 | 由 `GeneralUpdate.Core.Pipeline.DiffPipeline` 的 matcher 负责。 | -| 新增文件复制、删除清单、批量 patch 命名 | 否 | 由 `DiffPipeline` 负责生成 `.patch` 文件、复制新增文件和写入 `generalupdate.delete.json`。 | -| 更新包生成工具 | 否 | 推荐由 `GeneralUpdate.Tools` 调用 Core 差分管道生成发布产物。 | -| 下载、校验、解压、版本回写、重启 | 否 | 这些属于 `GeneralUpdate.Core` 更新流程。 | +目录级对比、批量补丁生成、并行调度、删除文件处理和更新流程编排由 `GeneralUpdate.Core` 的 `DiffPipeline` 或 `GeneralUpdate.Tools` 承担。 -> 当前源码中没有旧文档提到的 `DifferentialCore` 单例。直接使用 Differential 组件时,请面向 `IBinaryDiffer` 和具体 differ 实现编程;需要目录级能力时使用 Core 的 `DiffPipeline`。 +**核心能力:** -## Clean 与 Dirty 语义 {#clean-与-dirty-语义} +| 能力 | 说明 | +| --- | --- | +| 文件级差分生成 | `CleanAsync(oldFile, newFile, patchFile)` — 对比新旧文件生成 `.patch` 补丁 | +| 文件级差分应用 | `DirtyAsync(oldFile, newFile, patchFile)` — 旧文件 + 补丁 → 新文件 | +| 可替换差分算法 | `BsdiffDiffer`(BSDIFF 4.0,后缀排序)和 `StreamingHdiffDiffer`(块哈希索引) | +| 可替换压缩格式 | BZip2 (0x00)、Deflate (0x01),源码中通过 `#if NET6_0_OR_GREATER` 条件编译预留 Brotli (0x02) | +| BSDIFF 兼容格式 | 写入 33 字节扩展头(32 字节 BSDIFF40 + 1 字节压缩格式),兼容 32 字节旧头 | +| 线程安全 | 内置 differ 和压缩提供器均支持并发调用 | + +**解决的业务痛点:** +- 全量更新带宽成本高,差分更新可将更新包从 GB 级降低到 MB 甚至 KB 级 +- 不同文件类型和变化模式需要不同的差分策略(细粒度匹配 vs 快速块匹配) +- 压缩算法的选择影响客户端解压速度和补丁体积的平衡 + +**业务使用场景:** +- 大型桌面应用(多 DLL、资源文件)的增量更新 +- 固件/驱动包的二进制差分分发 +- 游戏资源热更新 +- CI/CD 发布流水线中自动生成增量补丁包 + +### 1.2 环境与依赖 + +| 项目 | 说明 | +| --- | --- | +| **版本** | `10.5.0-beta.2` | +| **目标框架** | `netstandard2.0`(兼容 .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+) | +| **依赖包** | 无外部依赖(纯 .NET BCL) | +| **兼容性** | 所有支持 .NET Standard 2.0 的平台 | -Differential 沿用了 GeneralUpdate 差分流程中的两个术语: +--- -| 术语 | 方法 | 输入 | 输出 | 常用位置 | +## 2. 组件功能列表 + +| 功能名称 | 功能描述 | 类型 | 是否必填 | 备注限制 | | --- | --- | --- | --- | --- | -| `Clean` | `CleanAsync` | 旧文件、新文件、补丁路径 | `.patch` 补丁文件 | 构建/发布阶段 | -| `Dirty` | `DirtyAsync` | 旧文件、输出新文件路径、补丁路径 | 还原后的新文件 | 客户端升级阶段 | +| BSDIFF 4.0 差分生成 | 基于后缀排序的经典差分算法,补丁体积稳定 | 基础 | 可选 | `BsdiffDiffer`,默认 BZip2 压缩 | +| BSDIFF 4.0 补丁应用 | 将 BSDIFF 格式补丁应用到旧文件 | 基础 | 可选 | 支持 32/33 字节两种头部格式 | +| Streaming HDiff 差分生成 | 基于 FNV-1a 块哈希索引的快速差分 | 基础 | 可选 | `StreamingHdiffDiffer`,默认 Deflate 压缩 | +| BZip2 压缩 | 补丁控制段/差异段/额外段的 BZip2 压缩 | 基础 | 可选 | 格式字节 `0x00`,`BsdiffDiffer` 默认 | +| Deflate 压缩 | 补丁段的 Deflate 压缩,解压更快 | 基础 | 可选 | 格式字节 `0x01`,`StreamingHdiffDiffer` 默认 | +| 自定义差分算法 | 实现 `IBinaryDiffer` 接入自研算法 | 拓展 | 可选 | 需保证 Clean/Dirty 一致性 | +| 自定义压缩提供器 | 实现 `ICompressionProvider` 替换压缩方式 | 拓展 | 可选 | 新格式字节需配合扩展补丁读取逻辑 | -文件级补丁应用不会直接覆盖旧文件,而是把还原结果写到你传入的 `newFilePath`。Core 的 `DiffPipeline` 在目录级更新时会先写临时文件,成功后再替换原文件,从而避免补丁应用失败时破坏原文件。 +--- -## 单文件快速开始 +## 3. API 配置说明 -下面示例只演示 Differential 的底层单文件能力。如果你已经在使用 `GeneralUpdate.Core`,Core 默认已经集成 Differential,不需要为了正常更新流程再手动集成或直接调用本组件。如果你要比较两个目录、生成一批 `.patch`、复制新增文件或处理删除文件,请直接看 [与 GeneralUpdate.Core 的关系](#与-generalupdatecore-的关系)。 +### 3.1 配置字段(属性 Props) -```csharp -using GeneralUpdate.Differential.Abstractions; -using GeneralUpdate.Differential.Differ; +Differential 本身是底层库,不提供配置类。所有参数通过构造函数传入。 -IBinaryDiffer differ = new BsdiffDiffer(); +**BsdiffDiffer 构造参数:** -var oldFile = @"D:\releases\1.0.0\app.dll"; -var newFile = @"D:\releases\1.0.1\app.dll"; -var patchFile = @"D:\patches\app.dll.patch"; -var outputFile = @"D:\restore\app.dll"; +| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | +| --- | --- | --- | --- | --- | --- | +| `compressionProvider` | `ICompressionProvider` | `BZip2CompressionProvider` | 可选 | `BZip2CompressionProvider` / `DeflateCompressionProvider` | 补丁压缩提供器 | -// 生成补丁:oldFile + newFile -> patchFile -await differ.CleanAsync(oldFile, newFile, patchFile); +**StreamingHdiffDiffer 构造参数:** -// 应用补丁:oldFile + patchFile -> outputFile -await differ.DirtyAsync(oldFile, outputFile, patchFile); -``` +| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | +| --- | --- | --- | --- | --- | --- | +| `compressionProvider` | `ICompressionProvider` | `DeflateCompressionProvider` | 可选 | `BZip2CompressionProvider` / `DeflateCompressionProvider` | 补丁压缩提供器 | +| `blockSize` | `int` | `65536`(64 KB) | 可选 | 正整数字节数 | 块大小,用于旧文件哈希索引 | +| `maxWindowSize` | `int` | `134217728`(128 MB) | 可选 | 正整数字节数 | 参与计算的最大内存窗口 | + +**DeflateCompressionProvider 构造参数:** + +| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | +| --- | --- | --- | --- | --- | --- | +| `optimalLevel` | `bool` | `true` | 可选 | `true` / `false` | `true` = `CompressionLevel.Optimal`,`false` = `CompressionLevel.Fastest` | + +**ICompressionProvider 格式标识:** + +| Provider | 格式字节 | 可用性 | 说明 | +| --- | --- | --- | --- | +| `BZip2CompressionProvider` | `0x00` | 完全可用 | BSDIFF 旧补丁兼容,解压成本较高 | +| `DeflateCompressionProvider` | `0x01` | 完全可用 | 解压速度更友好,适合客户端批量应用 | +| `BrotliCompressionProvider` | `0x02` | 仅 .NET 6+ 编译(源码中为完整实现,通过 `#if NET6_0_OR_GREATER` 条件编译) | 当前 `netstandard2.0` 包中不包含,生产不建议使用 | -`CleanAsync` 和 `DirtyAsync` 都支持 `CancellationToken`。当前实现会在任务开始和 Core 管道调度点观察取消请求;单个算法内部不是每一个字节循环都检查取消,因此大文件取消可能会等到当前文件处理结束后才完全停下。 +### 3.2 实例方法 -## 核心 API +**IBinaryDiffer:** -### IBinaryDiffer +| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | +| --- | --- | --- | --- | --- | +| `CleanAsync(string, string, string, CancellationToken)` | `oldFilePath` — 旧文件路径;`newFilePath` — 新文件路径;`patchFilePath` — 补丁输出路径;`cancellationToken` | `Task` | 发布/构建阶段生成补丁 | 大文件取消不会立即响应,需等待当前文件处理完成 | +| `DirtyAsync(string, string, string, CancellationToken)` | `oldFilePath` — 旧文件路径;`newFilePath` — 补丁还原后文件输出路径;`patchFilePath` — 补丁文件路径;`cancellationToken` | `Task` | 客户端升级阶段应用补丁 | 不会直接覆盖旧文件,结果写入 `newFilePath` | + +### 3.3 回调事件 + +Differential 不发布事件。进度报告和事件通知由 Core 的 `DiffPipeline` 通过 `DiffProgress` 和 `EventManager` 实现。 + +--- + +## 4. 扩展示例(高阶用法) + +### 4.1 组件可扩展能力总览 + +| 扩展接口 | 说明 | +| --- | --- | +| `IBinaryDiffer` | 自定义文件级差分算法,可接入原生库或自研算法 | +| `ICompressionProvider` | 自定义补丁段压缩方式 | + +### 4.2 分场景示例 + +#### 场景 1:自定义差分算法 + +【场景说明】接入企业内部自研的高压缩率差分算法。 -`IBinaryDiffer` 是所有文件级差分算法的统一抽象,也是 Core 差分管道接入自定义算法的关键接口。 +【示例代码】 ```csharp -public interface IBinaryDiffer +using GeneralUpdate.Differential.Abstractions; + +public sealed class HighRatioDiffer : IBinaryDiffer { - Task DirtyAsync( + public Task CleanAsync( string oldFilePath, string newFilePath, string patchFilePath, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + { + // 调用自研算法生成补丁 + // NativeMethods.GeneratePatch(oldFilePath, newFilePath, patchFilePath); + return Task.CompletedTask; + } - Task CleanAsync( + public Task DirtyAsync( string oldFilePath, string newFilePath, string patchFilePath, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + { + // 调用自研算法应用补丁 + // NativeMethods.ApplyPatch(oldFilePath, patchFilePath, newFilePath); + return Task.CompletedTask; + } } -``` - -| 参数 | 含义 | -| --- | --- | -| `oldFilePath` | 旧版本文件路径。生成补丁和应用补丁时都需要。 | -| `newFilePath` | `CleanAsync` 中表示新版本源文件;`DirtyAsync` 中表示还原后的输出文件。 | -| `patchFilePath` | 补丁文件路径。`CleanAsync` 写入它,`DirtyAsync` 读取它。 | - -### BsdiffDiffer -`BsdiffDiffer` 实现 BSDIFF 4.0 文件级二进制差分算法。它会把旧文件和新文件读入内存,通过后缀排序寻找匹配块,再输出控制段、差异段和额外段。 +// 在 Core DiffPipeline 中使用 +using GeneralUpdate.Core.Pipeline; -```csharp -using GeneralUpdate.Differential.Differ; +var pipeline = new DiffPipelineBuilder() + .UseDiffer(new HighRatioDiffer()) + .WithParallelism(4) + .Build(); -var differ = new BsdiffDiffer(); -await differ.CleanAsync(oldFile, newFile, patchFile); -await differ.DirtyAsync(oldFile, outputFile, patchFile); +await pipeline.CleanAsync(oldDir, newDir, patchDir); ``` -| 特性 | 说明 | -| --- | --- | -| 默认压缩 | `BZip2CompressionProvider`,兼容历史 BSDIFF 补丁。 | -| 可替换压缩 | 构造函数接受 `ICompressionProvider`。 | -| 补丁兼容 | 支持 32 字节旧 BSDIFF 头,也支持 33 字节扩展头。 | -| 适用场景 | 追求兼容性、补丁体积稳定、单文件体积可控的场景。 | -| 资源特征 | 生成补丁时会读入旧文件和新文件,单文件很大时需要关注内存占用。 | +【效果&注意事项】 +- 必须保证 `CleanAsync` 产出的补丁能被同一算法的 `DirtyAsync` 正确应用 +- 发布侧和客户端必须使用同一套 differ 实现 -`BsdiffDiffer` 也保留了 `Clean(...)` 和 `Dirty(...)` 方法;新代码建议优先面向 `IBinaryDiffer.CleanAsync` / `DirtyAsync`,便于切换算法。 +#### 场景 2:自定义压缩提供器 + BsdiffDiffer -### StreamingHdiffDiffer +【场景说明】使用 BsdiffDiffer 算法 + Deflate 压缩,获得更快的客户端补丁应用速度。 -`StreamingHdiffDiffer` 是当前源码中的另一种 differ 实现。它使用块级 FNV-1a 哈希索引预筛候选位置,再进行字节级扩展匹配,输出 BSDIFF 兼容的补丁结构。 +【示例代码】 ```csharp using GeneralUpdate.Differential.Abstractions; using GeneralUpdate.Differential.Differ; -var differ = new StreamingHdiffDiffer( - compressionProvider: new DeflateCompressionProvider(optimalLevel: true), - blockSize: 64 * 1024, - maxWindowSize: 128 * 1024 * 1024); +// BsdiffDiffer 的精确匹配 + Deflate 的快速解压 +var differ = new BsdiffDiffer( + new DeflateCompressionProvider(optimalLevel: false)); await differ.CleanAsync(oldFile, newFile, patchFile); await differ.DirtyAsync(oldFile, outputFile, patchFile); ``` -| 特性 | 说明 | -| --- | --- | -| 默认压缩 | `DeflateCompressionProvider`。 | -| 块大小 | `BlockSize` 默认 64 KB,用于建立旧文件块哈希索引。 | -| 窗口预算 | `MaxWindowSize` 默认 128 MB,影响生成补丁时参与计算的内存窗口。 | -| 应用补丁 | `DirtyAsync` 委托给 `BsdiffDiffer` 的补丁应用逻辑。 | -| 适用场景 | 需要更快候选匹配、希望与 Core `DiffPipeline` 默认算法保持一致的目录级差分构建。 | - -需要注意的是,当前实现不是完整外存流式差分:当单个文件超过 `MaxWindowSize` 时,算法只会读取预算窗口参与计算。对超大单文件,请在业务侧验证补丁还原结果,或调大 `MaxWindowSize`,或改用 `BsdiffDiffer` 等更适合当前文件规模的实现。 - -## 差分算法选择 - -当前 Differential 内置两种文件级差分算法。它们都输出 BSDIFF 兼容补丁结构,但生成补丁时的匹配方式、默认压缩、性能侧重点不同。 - -| 对比项 | `BsdiffDiffer` | `StreamingHdiffDiffer` | -| --- | --- | --- | -| 核心思路 | 经典 BSDIFF 4.0,基于后缀排序寻找旧文件和新文件之间的最长匹配。 | 使用块级 FNV-1a 哈希建立旧文件索引,先用哈希快速筛选候选块,再做字节级扩展匹配。 | -| 默认压缩 | BZip2 (`0x00`)。 | Deflate (`0x01`)。 | -| 补丁应用 | 自己实现 BSDIFF Dirty 逻辑。 | `DirtyAsync` 委托给 `BsdiffDiffer`,因此应用阶段和 BSDIFF 补丁兼容。 | -| 生成效率 | 匹配更精细,局部或分散变化下补丁生成表现稳定;但后缀排序和全量读入会带来 CPU/内存开销。 | 块命中效果好时生成更快;如果变化分散、块哈希命中少,生成可能变慢。 | -| 客户端应用性能 | 默认 BZip2 解压成本更高,客户端应用大量补丁时耗时可能更明显。 | 默认 Deflate 解压更快,更适合客户端批量应用补丁。 | -| 补丁体积倾向 | 通常更追求细粒度匹配,补丁体积明显更稳定。 | 速度优先,补丁体积与文件变化分布、块大小、窗口预算强相关;块命中差时可能接近完整文件。 | -| 内存特征 | 生成阶段读取旧文件和新文件,单个大文件需要关注内存峰值。 | 通过 `BlockSize` 和 `MaxWindowSize` 控制匹配窗口,超大单文件需要额外验证或调参。 | -| 兼容性 | 最适合需要兼容旧 BSDIFF/BZip2 补丁的场景。 | 适合新项目、目录级批量差分和 Core `DiffPipeline` 默认构建。 | - -可以简单理解为:`BsdiffDiffer` 更偏“兼容和补丁体积稳定”,`StreamingHdiffDiffer` 更偏“客户端应用速度和可调参数”。如果项目非常在意补丁体积或文件变化较分散,优先考虑 `BsdiffDiffer`;如果项目更在意客户端应用速度,并且经过压测确认补丁体积可接受,可以考虑 `StreamingHdiffDiffer`。 +【效果&注意事项】 +- `optimalLevel: false` 生成更快,适合开发/CI 环境 +- `optimalLevel: true` 补丁体积更小,适合生产环境 +- 生成和消费两侧都需要能识别 Deflate 格式(`0x01`) -### 参考基准数据 {#benchmark-reference} +#### 场景 3:StreamingHdiffDiffer 参数调优 -下面数据来自当前源码的一组本地微基准,用于给开发者判断量级,不是跨所有项目的性能承诺。测试环境为 Windows x64、.NET Release 构建,使用 2-4 MB 合成文件;真实结果会受 CPU、磁盘、文件类型、变化比例、压缩级别和并行度影响。 +【场景说明】大型单文件(200MB+)的差分,调整窗口预算避免内存溢出。 -| 场景 | `BsdiffDiffer` 生成 | `StreamingHdiffDiffer` 生成 | `BsdiffDiffer` 应用 | `StreamingHdiffDiffer` 应用 | `BsdiffDiffer` 补丁体积 | `StreamingHdiffDiffer` 补丁体积 | -| --- | ---: | ---: | ---: | ---: | ---: | ---: | -| 2 MB 文本,少量行变更/插入 | 484 ms | 2059 ms | 55 ms | 36 ms | 0.05% | 3.50% | -| 4 MB 二进制,连续局部块变更 | 1030 ms | 318 ms | 55 ms | 11 ms | 2.58% | 100.04% | -| 4 MB 二进制,随机分散字节变更 | 757 ms | 4176 ms | 70 ms | 30 ms | 2.18% | 100.27% | +【示例代码】 -从这组数据可以得到几个实用预估: - -| 指标 | 参考结论 | -| --- | --- | -| 补丁体积 | `BsdiffDiffer` 在测试场景中约为新文件的 0.05%-2.58%;`StreamingHdiffDiffer` 约为 3.50%-100%。如果补丁包大小是第一优先级,优先测试 `BsdiffDiffer`。 | -| 客户端应用速度 | `StreamingHdiffDiffer` 默认 Deflate,在测试中应用补丁约快 1.5-5 倍。大量文件批量应用时,这个差距会更明显。 | -| 生成速度 | 没有绝对赢家:连续局部二进制变更中 `StreamingHdiffDiffer` 约快 3.2 倍;文本和随机分散变更中 `BsdiffDiffer` 约快 4.3-5.5 倍。 | -| 大型项目选择 | 大型项目建议同时看“补丁总体积 + 构建耗时 + 客户端应用耗时”。如果大量文件可以并行,`WithParallelism(...)` 往往比单个 differ 的微小差距更影响总体耗时。 | +```csharp +using GeneralUpdate.Differential.Abstractions; +using GeneralUpdate.Differential.Differ; -> 这组数据的重点是帮助判断方向:`BsdiffDiffer` 通常更容易得到小补丁,`StreamingHdiffDiffer` 的应用阶段更快,但补丁体积和生成速度对文件变化形态非常敏感。正式发布前建议用自己项目的真实产物做一次压测。 +// 大文件场景:增大窗口,减小块大小以获得更精细匹配 +var differ = new StreamingHdiffDiffer( + compressionProvider: new DeflateCompressionProvider(optimalLevel: true), + blockSize: 32 * 1024, // 32 KB 块,更密集的哈希索引 + maxWindowSize: 256 * 1024 * 1024); // 256 MB,允许读入更大文件 -推荐选择: +await differ.CleanAsync(oldLargeFile, newLargeFile, patchFile); +``` -| 场景 | 建议 | -| --- | --- | -| 只需要低层单文件补丁,并希望最大兼容 | 使用 `new BsdiffDiffer()`。 | -| 通过 Core `DiffPipeline` 批量生成目录级补丁 | 先用默认配置跑基准;若补丁体积偏大,可显式切换到 `BsdiffDiffer`;再结合 `WithParallelism(...)` 提升吞吐。 | -| 客户端解压性能更敏感 | 优先选择 Deflate 补丁,即 `StreamingHdiffDiffer` 默认配置,或 `new BsdiffDiffer(new DeflateCompressionProvider())`。 | -| 历史补丁仍是旧 BSDIFF/BZip2 | 使用 `BsdiffDiffer` 应用;32 字节头会按 BZip2 处理。 | -| 大型项目包含大量 DLL、资源文件、插件文件 | 使用 Core `DiffPipeline` 做文件级并行,避免自己逐个文件串行调用 Differential。 | +【效果&注意事项】 +- `blockSize` 越小,哈希索引越密集,匹配更精确但内存消耗更大 +- `maxWindowSize` 决定能参与计算的最大数据量,超出部分不参与匹配 +- 超大文件建议先在业务侧压测补丁体积和应用还原结果 -## 补丁格式与压缩 Provider {#补丁格式与压缩-provider} +--- -Differential 生成的是 BSDIFF 风格补丁。当前实现写入 33 字节扩展头: +## 5. 常规使用示例 -| 偏移 | 长度 | 含义 | -| --- | --- | --- | -| `0` | 8 | 魔数 `"BSDIFF40"`。 | -| `8` | 8 | 压缩后控制段长度。 | -| `16` | 8 | 压缩后差异段长度。 | -| `24` | 8 | 新文件长度。 | -| `32` | 1 | 压缩格式版本。 | +### 5.1 快速入门示例(最简 demo) -应用补丁时也兼容 32 字节旧头:如果没有第 33 个格式字节,就按 BZip2 旧补丁处理。 +```csharp +using GeneralUpdate.Differential.Abstractions; +using GeneralUpdate.Differential.Differ; -### ICompressionProvider +IBinaryDiffer differ = new BsdiffDiffer(); -`ICompressionProvider` 负责把控制段、差异段和额外段包装成压缩流。 +var oldFile = @"D:\releases\1.0.0\app.dll"; +var newFile = @"D:\releases\1.0.1\app.dll"; +var patchFile = @"D:\patches\app.dll.patch"; +var outputFile = @"D:\restore\app.dll"; -```csharp -public interface ICompressionProvider -{ - byte FormatVersion { get; } +// 生成补丁:oldFile + newFile → patchFile +await differ.CleanAsync(oldFile, newFile, patchFile); - Stream CreateCompressStream( - Stream output, - CancellationToken cancellationToken = default); +// 应用补丁:oldFile + patchFile → outputFile +await differ.DirtyAsync(oldFile, outputFile, patchFile); - Stream CreateDecompressStream( - Stream input, - CancellationToken cancellationToken = default); -} +// 验证还原结果 +var newHash = ComputeSha256(newFile); +var outputHash = ComputeSha256(outputFile); +Console.WriteLine(newHash == outputHash ? "Patch verified." : "MISMATCH!"); ``` -| Provider | 格式字节 | 当前可用性 | 说明 | -| --- | --- | --- | --- | -| `BZip2CompressionProvider` | `0x00` | 可用 | `BsdiffDiffer` 默认值,兼容旧 BSDIFF 补丁。 | -| `DeflateCompressionProvider` | `0x01` | 可用 | BCL `DeflateStream`,解压速度更适合客户端更新。 | -| `BrotliCompressionProvider` | `0x02` | 源码中以 `NET6_0_OR_GREATER` 条件编译预留 | 当前 `GeneralUpdate.Differential` 项目目标为 `netstandard2.0`,并且补丁读取逻辑当前只识别 `0x00` / `0x01`,不要把 Brotli 作为生产更新包格式。 | - -自定义压缩时,生成补丁和应用补丁必须使用能被补丁读取逻辑识别的格式字节。当前生产建议只使用 BZip2 或 Deflate。 +### 5.2 基础参数组合示例 ```csharp using GeneralUpdate.Differential.Abstractions; using GeneralUpdate.Differential.Differ; -var differ = new BsdiffDiffer( - new DeflateCompressionProvider(optimalLevel: false)); +// 方案 A:经典 BSDIFF + BZip2 → 补丁体积最小 +var differA = new BsdiffDiffer(); -await differ.CleanAsync(oldFile, newFile, patchFile); -await differ.DirtyAsync(oldFile, outputFile, patchFile); -``` +// 方案 B:经典 BSDIFF + Deflate → 补丁体积小 + 应用更快 +var differB = new BsdiffDiffer(new DeflateCompressionProvider(optimalLevel: true)); -## 与 GeneralUpdate.Core 的关系 {#与-generalupdatecore-的关系} +// 方案 C:Streaming HDiff + Deflate → 生成快 + 应用最快 +var differC = new StreamingHdiffDiffer( + new DeflateCompressionProvider(optimalLevel: true), + blockSize: 64 * 1024, + maxWindowSize: 128 * 1024 * 1024); -`GeneralUpdate.Core` 在 Differential 之上提供目录级差分管道 `DiffPipeline`。它会负责: +// 对同一组文件测试三种方案,选择最优 +foreach (var differ in new IBinaryDiffer[] { differA, differB, differC }) +{ + var sw = Stopwatch.StartNew(); + await differ.CleanAsync(oldFile, newFile, patchFile); + sw.Stop(); -1. 对比旧目录和新目录。 -2. 找出发生变化的文件并调用 `IBinaryDiffer.CleanAsync` 生成 `.patch`。 -3. 复制新增文件到补丁目录。 -4. 生成 `generalupdate.delete.json` 记录删除文件。 -5. 客户端应用补丁时并行调用 `IBinaryDiffer.DirtyAsync`,先写临时文件,成功后替换原文件。 + var patchSize = new FileInfo(patchFile).Length; + Console.WriteLine($"{differ.GetType().Name}: {sw.ElapsedMilliseconds}ms, {patchSize} bytes"); +} +``` -如果你在应用更新流程中使用 `GeneralUpdate.Core`,Core 默认已经集成 Differential 并内置差分管道。也就是说,常规更新接入时不需要额外安装、初始化或手动调用 `GeneralUpdate.Differential`;只要使用 Core 的更新流程,并按业务需要启用补丁更新能力,Core 会在内部完成 differ 创建、补丁应用和目录级编排。 +### 5.3 真实业务落地示例(通过 Core DiffPipeline 使用) -只有在你想替换默认差分算法、调整并行度、改变错误策略或接入自定义 matcher 时,才需要通过 `UseDiffPipeline` 做高级配置: +大多数情况下不直接使用 Differential,而是通过 Core 的 `DiffPipeline` 做目录级差分: ```csharp using GeneralUpdate.Core; +using GeneralUpdate.Core.Pipeline; using GeneralUpdate.Core.Models; using GeneralUpdate.Differential.Differ; +// 构建端:对比新旧版本目录,生成补丁 +var pipeline = new DiffPipelineBuilder() + .UseDiffer(new StreamingHdiffDiffer()) + .WithParallelism(8) // CI 构建机,高并行度 + .WithStopOnFirstError(true) + .WithProgress(new Progress(p => + { + Console.WriteLine($"[Build] {p.Completed}/{p.Total} {p.CurrentFile}"); + })) + .Build(); + +await pipeline.CleanAsync( + @"D:\builds\v1.0.0", + @"D:\builds\v1.0.1", + @"D:\patches\v1.0.0-to-v1.0.1"); + +// 客户端:通过 GeneralUpdateBootstrap 使用 await new GeneralUpdateBootstrap() .SetSource( - updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret") + updateUrl: "https://update.mycompany.com/api/upgrade/verification", + appSecretKey: "prod-key") .SetOption(Option.AppType, AppType.Client) .SetOption(Option.PatchEnabled, true) + .SetOption(Option.DiffMode, DiffMode.Parallel) + .SetOption(Option.MaxConcurrency, 4) .UseDiffPipeline(builder => builder .UseDiffer(new StreamingHdiffDiffer()) - .WithParallelism(4) - .WithStopOnFirstError(true)) + .WithParallelism(4)) .LaunchAsync(); ``` -当前源码里有两个默认层级需要区分: - -| 使用方式 | 默认 differ | -| --- | --- | -| 直接 `new DiffPipeline()` 或 `new DiffPipelineBuilder().Build()` | `StreamingHdiffDiffer` | -| `GeneralUpdateBootstrap` 未显式调用 `UseDiffPipeline(...)` 时内部构建 | `BsdiffDiffer`,并行度 2,带 `DiffProgressReporter` | - -因此,普通用户可以把 Differential 看作 Core 已经带好的底层能力,不需要特地集成;只有希望 Core 更新流程明确使用某个算法或自定义差分行为时,才建议显式调用 `UseDiffPipeline(...)`。 - -## 与 GeneralUpdate.Tools 的关系 {#与-generalupdatetools-的关系} - -`GeneralUpdate.Tools` 面向发布侧,帮助开发者构建更新产物。当前 `DiffService` 会创建 `new DiffPipeline()`,再调用: - -```csharp -await pipeline.CleanAsync(oldDir, newDir, patchDir); -``` - -也就是说,Tools 生成目录级差分包时,本质上使用的是 Core 的 `DiffPipeline`,而 `DiffPipeline` 再调用 Differential 的 `IBinaryDiffer` 生成每个变更文件的补丁。对大多数开发者来说,推荐路径是: - -1. 用 Tools 对比旧版本目录和新版本目录,生成补丁目录和清单产物。 -2. 用 Core 在客户端检查版本、下载补丁包、应用补丁。 -3. 只有在需要自定义差分算法、压缩格式或单文件补丁实验时,才直接使用 Differential。 - -这种分层可以让业务代码保持简单:Tools 负责构建,Core 负责更新,Differential 负责底层文件差分。 - -## 并发模型与性能建议 - -Differential 的单个 differ 实例没有保存某次补丁任务的可变共享状态。只要传入的 `ICompressionProvider` 是线程安全的,内置 differ 可以被 Core 管道并发调用;内置 BZip2、Deflate provider 都会为每次调用创建新的压缩流,适合并发使用。 - -真正的“多线程差分”通常发生在 Core `DiffPipeline` 层: +--- -```csharp -var pipeline = new DiffPipelineBuilder() - .UseDiffer(new StreamingHdiffDiffer()) - .WithParallelism(4) - .Build(); +## 6. 算法选择指南 -await pipeline.CleanAsync(oldDir, newDir, patchDir); -``` +### Clean 与 Dirty 语义 -### 大型项目并行差分 {#大型项目并行差分} +| 术语 | 方法 | 输入 | 输出 | 常用位置 | +| --- | --- | --- | --- | --- | +| Clean | `CleanAsync` | 旧文件、新文件、补丁输出路径 | `.patch` 补丁文件 | 构建/发布阶段 | +| Dirty | `DirtyAsync` | 旧文件、输出新文件路径、补丁路径 | 还原后的新文件 | 客户端升级阶段 | -大型桌面项目通常不是“一个超大文件”,而是由主程序、多个 DLL、插件、资源文件、运行时文件和配置文件组成。Core `DiffPipeline` 会把目录对比结果拆成文件级任务,每个变更文件独立调用 `IBinaryDiffer.CleanAsync` 生成补丁,因此可以通过 `WithParallelism(...)` 同时处理多个文件。 +### 算法对比 -这种并行模型对大型项目很重要: +| 对比维度 | `BsdiffDiffer` | `StreamingHdiffDiffer` | +| --- | --- | --- | +| 核心思路 | 经典 BSDIFF 4.0,后缀排序 + 最长匹配 | 块级 FNV-1a 哈希索引 + 字节级扩展匹配 | +| 默认压缩 | BZip2 (0x00) | Deflate (0x01) | +| 补丁应用 | 自实现 BSDIFF Dirty | 委托给 `BsdiffDiffer`(BSDIF 兼容) | +| 补丁体积 | 更稳定,通常更小 | 受文件变化分布影响大,块命中差时可能接近原文件大小 | +| 客户端应用速度 | BZip2 解压较慢 | Deflate 解压更快(约 1.5-5x) | +| 生成内存 | 全量读入新旧文件 | 按 `maxWindowSize` 预算读入 | +| 兼容性 | 兼容旧 BSDIFF/BZip2 补丁 | 适合新项目 | -1. 构建侧可以同时为多个变更文件生成 `.patch`,缩短发布包构建时间。 -2. 客户端应用补丁时也可以并行处理多个文件,减少升级窗口。 -3. 新增文件复制、删除清单处理和差分补丁生成由 Core 管道统一编排,开发者不需要手写多线程调度。 -4. 并行度可以按机器能力调整,构建机可以设置更高,低配置客户端可以保持较低。 +### 场景推荐 -| 参数/策略 | 建议 | +| 场景 | 推荐 | | --- | --- | -| `WithParallelism(1)` | 资源敏感、机械硬盘、低内存环境。 | -| `WithParallelism(2)` | 默认平衡值,适合多数桌面应用。 | -| `WithParallelism(4-8)` | 多核 CPU、SSD、构建机或发布服务器。 | -| BZip2 | 补丁兼容性好,但客户端解压成本更高。 | -| Deflate | 解压速度更友好,适合客户端大批量应用补丁。 | -| 大文件 | 先压测补丁生成耗时、内存峰值和还原结果,不要只看补丁体积。 | - -并行差分适合“文件数量多、每个文件可独立处理”的大型项目。需要注意的是,单个超大文件内部仍由具体 differ 算法处理,不会因为 `WithParallelism(8)` 就把一个文件拆成 8 份并行计算;并行度提升的是多个文件之间的吞吐。 - -下载与差分可以在上层更新流程中并行:Core 下载阶段可以并发拉取多个资源,差分应用阶段也可以按文件并行处理补丁。Differential 只负责单个文件的补丁计算,不直接管理网络下载线程。 - -## 扩展点 - -### 自定义差分算法 +| 补丁体积优先 | `BsdiffDiffer` + BZip2 | +| 客户端应用速度优先 | `StreamingHdiffDiffer`(默认 Deflate) | +| 兼容旧补丁格式 | `BsdiffDiffer`(32 字节旧头自动按 BZip2 处理) | +| 大文件(>500MB) | 先压测,可能需要 `StreamingHdiffDiffer` + 调大 `maxWindowSize` | +| 目录级批量差分 | 通过 Core `DiffPipeline`,配合 `WithParallelism` 提升吞吐 | +| 新项目 | 先用默认配置跑基准,再根据体积和速度需求调整 | -实现 `IBinaryDiffer` 后即可接入 Core 管道。适合接入其他算法、调用原生库,或对特定文件类型做特殊优化。 +### 并发模型 -```csharp -using GeneralUpdate.Differential.Abstractions; - -public sealed class MyBinaryDiffer : IBinaryDiffer -{ - public Task CleanAsync( - string oldFilePath, - string newFilePath, - string patchFilePath, - CancellationToken cancellationToken = default) - { - // Generate patchFilePath from oldFilePath and newFilePath. - throw new NotImplementedException(); - } - - public Task DirtyAsync( - string oldFilePath, - string newFilePath, - string patchFilePath, - CancellationToken cancellationToken = default) - { - // Restore newFilePath from oldFilePath and patchFilePath. - throw new NotImplementedException(); - } -} -``` - -```csharp -var pipeline = new DiffPipelineBuilder() - .UseDiffer(new MyBinaryDiffer()) - .WithParallelism(4) - .Build(); -``` - -自定义算法需要保证 `CleanAsync` 产出的补丁能被同一算法的 `DirtyAsync` 正确应用;如果补丁要交给 Core 客户端使用,发布侧和客户端必须使用同一套 differ 实现。 - -### 自定义压缩 Provider - -如果仍使用 BSDIFF 兼容补丁结构,只想替换控制段、差异段和额外段的压缩方式,可以实现 `ICompressionProvider`。 - -```csharp -using GeneralUpdate.Differential.Abstractions; - -public sealed class MyCompressionProvider : ICompressionProvider -{ - public byte FormatVersion => 0x01; - - public Stream CreateCompressStream( - Stream output, - CancellationToken cancellationToken = default) - { - return new DeflateStream( - output, - CompressionLevel.Optimal, - leaveOpen: true); - } - - public Stream CreateDecompressStream( - Stream input, - CancellationToken cancellationToken = default) - { - return new DeflateStream( - input, - CompressionMode.Decompress, - leaveOpen: true); - } -} -``` +- 单个 `IBinaryDiffer` 实例线程安全(当 `ICompressionProvider` 线程安全时) +- 真正的多线程差分在 Core `DiffPipeline` 层,通过 `WithParallelism(n)` 控制 +- BZip2 / Deflate 内置 provider 均为每次调用创建新流,支持并发 -不要随意分配新的 `FormatVersion`。当前 `BsdiffDiffer.DirtyAsync` 只识别 BZip2 (`0x00`) 和 Deflate (`0x01`);如果你引入新格式,也需要同步扩展补丁读取逻辑,否则客户端无法应用补丁。 - -## 实战建议 +--- -| 场景 | 推荐做法 | -| --- | --- | -| 普通应用发布差分更新 | 使用 `GeneralUpdate.Tools` 生成产物,客户端使用 Core。 | -| 需要控制目录级并行、错误策略和进度 | 使用 Core `DiffPipelineBuilder`。 | -| 只验证某个文件的补丁效果 | 直接使用 `IBinaryDiffer`。 | -| 对补丁体积和应用速度都敏感 | 对同一组文件分别测试 BZip2、Deflate 和不同算法后再定默认策略。 | -| 更新包需要长期兼容旧客户端 | 保守使用 `BsdiffDiffer` + BZip2,或确保客户端已支持 Deflate 扩展头。 | +## 相关资源 -Differential 的价值在于把复杂的二进制差分能力收敛成稳定的文件级抽象。上层开发者可以把重点放在“什么时候更新、下载什么、如何提示用户”上,把具体补丁生成和应用交给 Core/Tools/Differential 的组合完成。 +- [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) +- [Samples 差分示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/DifferentialSample.cs) +- [Core DiffPipeline 文档](GeneralUpdate.Core.md) +- [Tools 打包指南](../guide/Packaging.md) From f985c07b23d1a7ef9768f089a30e056130ba429e Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Thu, 4 Jun 2026 11:26:45 +0800 Subject: [PATCH 2/4] fix: remove redundant zh-Hans i18n overrides causing MDX build failure The zh-Hans locale is the default locale in Docusaurus config, so docs/doc/ serves zh-Hans content directly. The i18n/zh-Hans/ overrides are unnecessary and were causing MDX compilation errors during website build. Co-Authored-By: Claude Opus 4.8 --- .../current/doc/GeneralUpdate.Core.md | 1014 ----------------- .../current/doc/GeneralUpdate.Differential.md | 367 ------ 2 files changed, 1381 deletions(-) delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md delete mode 100644 website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md deleted file mode 100644 index fd3f600..0000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md +++ /dev/null @@ -1,1014 +0,0 @@ ---- -sidebar_position: 5 ---- - -# GeneralUpdate.Core - -**命名空间:** `GeneralUpdate.Core` | **主要入口:** `GeneralUpdateBootstrap` | **NuGet 包:** `GeneralUpdate.Core` - -## 1. 组件简介 - -### 1.1 组件概述 - -**GeneralUpdate.Core** 是 GeneralUpdate 生态的更新执行核心引擎,负责客户端应用的全生命周期更新管理。它提供可编程的启动器、配置模型、事件通知系统、下载子系统(支持并发、断点续传、重试、校验、后处理管道)、差分补丁管道、版本回写、IPC 进程通信以及平台策略扩展。 - -**核心能力:** - -| 能力 | 说明 | -| --- | --- | -| 多策略更新执行 | 内置标准 Client/Upgrade 更新、OSS 对象存储更新、静默后台轮询更新三种执行策略 | -| 配置驱动 | 通过 `UpdateRequest` 强类型配置或 `SetSource` 轻配置入口,配合 `generalupdate.manifest.json` 实现极简接入 | -| 下载子系统 | 可替换的下载来源、执行器、重试策略、后处理管道和批量编排器,默认支持并发下载、断点续传、SHA256 校验 | -| 差分补丁管道 | 文件级二进制差分(BSDIFF 4.0 / Streaming HDiff),目录级对比与批量补丁分发,支持并行处理 | -| 事件通知 | 7 种事件回调(版本发现、下载进度、完成、错误、异常等),支持批量事件监听器注册 | -| 扩展点体系 | 10 个可替换接口:生命周期钩子、状态上报、SSL 证书策略、HTTP 认证、下载来源/策略/执行器/管道/编排器、平台策略 | -| 版本清单体系 | `generalupdate.manifest.json` 自动发现应用身份,更新后自动回写本地版本,无需业务代码维护版本号 | -| IPC 进程通信 | 主程序与升级程序之间通过加密文件传递更新上下文,保障升级流程的原子性和安全性 | -| SignalR 实时推送 | 基于 SignalR 的版本更新实时推送(`UpgradeHubService`),支持点对点和广播推送、自动重连、多事件订阅 | - -**解决的业务痛点:** -- 桌面应用需要可靠的自动更新能力,但手写更新逻辑涉及版本对比、下载、校验、解压、文件替换、进程重启等多个复杂环节 -- 大型应用的分发包体积大,全量更新带宽成本高,需要差分更新降低下载量 -- 需要灵活的更新策略(静默后台、用户手动触发、OSS/CDN 分发) -- 升级程序版本独立演进,需要主程序和升级程序的版本协调 -- 多产品线需要统一的更新框架,减少重复开发 - -**业务使用场景:** -- WPF / WinForms / Avalonia / WinUI 桌面应用的自动更新 -- 企业内部工具的统一版本管理 -- 通过 CDN / OSS 分发更新包的客户端应用 -- 需要差分更新降低带宽消耗的大型客户端 - -### 1.2 环境与依赖 - -| 项目 | 说明 | -| --- | --- | -| **版本** | `10.5.0-beta.2` | -| **目标框架** | `netstandard2.0`; `net8.0`; `net10.0`(兼容 .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+;`net8.0`+ 支持 AOT/Trim) | -| **依赖包** | `GeneralUpdate.Differential`(差分算法)、`System.Text.Json`、`Microsoft.AspNetCore.SignalR.Client` | -| **兼容性** | Windows / Linux / macOS;支持 x86 / x64 / ARM64 | - ---- - -## 2. 组件功能列表 - -| 功能名称 | 功能描述 | 类型 | 是否必填 | 备注限制 | -| --- | --- | --- | --- | --- | -| 标准 Client 更新 | 主程序检查版本、下载更新包、启动升级程序替换文件 | 基础 | 必选 | 需要服务端版本检查 API | -| 标准 Upgrade 更新 | 独立升级程序读取 IPC 上下文并执行文件替换、差分补丁、版本回写 | 基础 | 必选 | 由主程序启动,通过加密 IPC 传递上下文 | -| OSS Client 更新 | 从 OSS/CDN 下载版本配置,对比后启动升级程序 | 基础 | 可选 | 版本配置文件托管在对象存储,不依赖服务端 API | -| OSS Upgrade 更新 | OSS 模式下的升级程序,下载并解压资源包 | 基础 | 可选 | 配合 OssClient 使用 | -| 静默后台更新 | 后台轮询版本、静默下载、进程退出时触发升级 | 基础 | 可选 | 需设置 `Option.Silent = true` | -| 差分补丁管道 | 文件级二进制差分生成与应用,目录级批量补丁分发 | 基础 | 可选 | 需要 `Option.PatchEnabled = true` | -| 并发下载 | 多资源包并发下载,支持断点续传和 SHA256 校验 | 基础 | 可选 | 通过 `Option.MaxConcurrency` 控制 | -| 事件通知回调 | 版本发现、下载进度、完成、异常等 7 种事件 | 基础 | 可选 | 通过 `AddListener*` 方法注册 | -| 应用身份清单 | `generalupdate.manifest.json` 自动发现与版本回写 | 拓展 | 推荐 | 由 `GeneralUpdate.Tools` 生成 | -| 下载来源扩展 | 自定义版本清单和下载资源来源 | 拓展 | 可选 | 实现 `IDownloadSource` | -| 下载执行器扩展 | 自定义单文件下载实现(HTTP/FTP/SFTP 等) | 拓展 | 可选 | 实现 `IDownloadExecutor` | -| 下载重试策略扩展 | 自定义重试、超时、熔断策略 | 拓展 | 可选 | 实现 `IDownloadPolicy` | -| 下载后处理管道扩展 | 下载完成后自定义校验、解密、扫描等 | 拓展 | 可选 | 实现 `IDownloadPipeline` | -| 批量下载编排扩展 | 完全替换批量下载并发控制逻辑 | 拓展 | 可选 | 实现 `IDownloadOrchestrator` | -| 生命周期钩子 | 更新前、下载后、更新后、异常、启动前的业务逻辑注入 | 拓展 | 可选 | 实现 `IUpdateHooks` | -| 状态上报扩展 | 更新状态上报到自有服务端 | 拓展 | 可选 | 实现 `IUpdateReporter` | -| HTTP 认证扩展 | 自定义 HTTP 请求认证头 | 拓展 | 可选 | 实现 `IHttpAuthProvider` | -| SSL 证书策略扩展 | 自定义 HTTPS 证书校验逻辑 | 拓展 | 可选 | 实现 `ISslValidationPolicy` | -| 平台策略扩展 | 替换平台级文件操作或启动逻辑 | 拓展 | 可选 | 实现 `IStrategy` | -| SignalR 实时推送 | 服务端主动推送版本更新通知,客户端订阅接收,支持点对点和广播推送 | 拓展 | 可选 | `UpgradeHubService`,命名空间 `GeneralUpdate.Core.Hubs` | -| 推送重连机制 | 断线自动重连(随机退避策略),连接生命周期管理 | 拓展 | 可选 | `RandomRetryPolicy` | -| 推送事件订阅 | 接收消息、在线状态、重连通知、关闭通知四种事件 | 拓展 | 可选 | 通过 `AddListener*` 方法注册 | -| 多协议认证 | 支持 HMAC-SHA256、Bearer Token、API Key、HTTP Basic 四种认证方案 | 基础 | 可选 | 通过 `AuthScheme` 枚举或 `HttpAuth()` 自定义 | -| 跨平台策略 | 内置 Windows / Linux / macOS 三种 OS 级更新策略,自动选择 | 基础 | 自动 | 根据运行时平台自动选择对应策略 | -| 文件树比对 | 新旧版本目录结构级差异对比,生成增量文件清单 | 基础 | 可选 | `FileTree` / `FileTreeDiffer` / `FileTreeComparer` | -| AOT/Trim 兼容 | `net8.0`+ 目标框架支持 AOT 发布和裁剪,含源生成 JSON 序列化上下文 | 基础 | 可选 | `JsonContext` 命名空间下 9 个序列化上下文 | - ---- - -## 3. API 配置说明 - -### 3.1 配置字段(属性 Props) - -**UpdateRequest 配置属性:** - -| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | -| --- | --- | --- | --- | --- | --- | -| `UpdateUrl` | `string` | — | 是 | 有效绝对 URL | 更新检查 API 地址 | -| `UpdateAppName` | `string` | `"Update.exe"` | 推荐 | 有效文件名 | 升级程序文件名,如与实际不同必须显式设置 | -| `MainAppName` | `string` | — | 推荐 | 有效文件名 | 主程序文件名,用于重启和识别 | -| `ClientVersion` | `string` | — | 推荐 | SemVer 格式 | 当前主程序版本 | -| `AppSecretKey` | `string` | — | 推荐 | — | 应用密钥,用于服务端认证 | -| `InstallPath` | `string` | `AppDomain.CurrentDomain.BaseDirectory` | 可选 | 有效目录路径 | 应用安装根目录 | -| `ReportUrl` | `string` | `null` | 可选 | 有效绝对 URL | 更新状态上报 API | -| `UpdateLogUrl` | `string` | `null` | 可选 | 有效绝对 URL | 更新日志页面地址 | -| `UpgradeClientVersion` | `string` | — | 可选 | SemVer 格式 | 升级程序自身版本 | -| `ProductId` | `string` | — | 可选 | — | 产品标识,多产品时用于区分 | -| `UpdatePath` | `string` | `InstallPath` | 可选 | 有效目录路径 | 升级程序所在目录 | -| `Bowl` | `string` | `null` | 可选 | 有效文件名 | 更新前需关闭的辅助进程名 | -| `Scheme` | `string` | `null` | 可选 | `"Bearer"` 等 | 认证方案(已废弃,推荐使用 `AuthScheme`) | -| `Token` | `string` | `null` | 可选 | — | 认证令牌 | -| `AuthScheme` | `AuthScheme?` | `null` | 可选 | `Hmac`, `Bearer`, `ApiKey`, `Basic` | 认证方案枚举,设置后自动选择对应 Provider | -| `BasicUsername` | `string` | `null` | 可选 | — | HTTP Basic 认证用户名(`AuthScheme = Basic` 时必填) | -| `BasicPassword` | `string` | `null` | 可选 | — | HTTP Basic 认证密码(`AuthScheme = Basic` 时必填) | -| `Files` | `List` | `null` | 可选 | — | 更新时跳过的指定文件列表 | -| `Formats` | `List` | `null` | 可选 | — | 更新时跳过的扩展名列表 | -| `Directories` | `List` | `null` | 可选 | — | 更新时跳过的目录列表 | -| `DriverDirectory` | `string` | `null` | 可选 | 有效目录路径 | 驱动更新目录 | - -**Option 运行时选项:** - -| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | -| --- | --- | --- | --- | --- | --- | -| `Option.AppType` | `AppType` | `Client` | 是 | `Client(1)`, `Upgrade(2)`, `OssClient(3)`, `OssUpgrade(4)` | 当前进程角色 | -| `Option.DiffMode` | `DiffMode` | `Serial` | 可选 | `Serial`, `Parallel` | 下载执行模式 | -| `Option.Encoding` | `Encoding` | `UTF8` | 可选 | `Encoding` 实例 | 压缩包处理编码 | -| `Option.Format` | `Format` | `Zip` | 可选 | `Zip` | 更新包格式 | -| `Option.DownloadTimeout` | `int?` | `30` | 可选 | 正整数(秒) | 下载超时时间 | -| `Option.PatchEnabled` | `bool?` | `true` | 可选 | `true` / `false` | 是否启用差分补丁 | -| `Option.BackupEnabled` | `bool?` | `true` | 可选 | `true` / `false` | 更新前是否备份被替换文件 | -| `Option.Silent` | `bool` | `false` | 可选 | `true` / `false` | 是否启用静默轮询更新 | -| `Option.SilentPollIntervalMinutes` | `int` | `60` | 可选 | 正整数 | 静默模式轮询间隔(分钟) | -| `Option.LaunchClientAfterUpdate` | `bool` | `true` | 可选 | `true` / `false` | 升级后是否启动主程序 | -| `Option.MaxConcurrency` | `int` | `3` | 可选 | `1` ~ `ProcessorCount × 2` | 下载最大并发数 | -| `Option.EnableResume` | `bool` | `true` | 可选 | `true` / `false` | 是否启用断点续传 | -| `Option.RetryCount` | `int` | `3` | 可选 | 非负整数 | 下载重试次数 | -| `Option.VerifyChecksum` | `bool` | `true` | 可选 | `true` / `false` | 是否校验下载文件 Hash | -| `Option.RetryInterval` | `TimeSpan` | `1s` | 可选 | `TimeSpan` 正值 | 下载重试间隔 | - -### 3.2 实例方法 - -**GeneralUpdateBootstrap:** - -| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | -| --- | --- | --- | --- | --- | -| `LaunchAsync()` | 无(读取已配置的 Option 和 Config) | `Task` | 所有 Core 使用场景的最终入口 | 会根据 `Option.AppType` 自动选择执行策略 | -| `Cancel()` | 无 | `void` | UI 中提供"取消更新"按钮 | 触发内部 `CancellationTokenSource` | -| `SetConfig(UpdateRequest)` | `configInfo` — 更新配置对象 | `GeneralUpdateBootstrap` | 主程序内显式配置更新参数 | 会调用 `Validate()` 检查关键字段 | -| `SetConfig(string)` | `filePath` — JSON 配置文件路径 | `GeneralUpdateBootstrap` | 从文件读取更新配置 | 支持相对路径和绝对路径;UTF-8 JSON 格式 | -| `SetSource(...)` | `updateUrl`(更新检查 API 地址)
`appSecretKey`(应用密钥)
`reportUrl?`(可选上报地址)
`scheme?`(可选认证方案,如 `"Bearer"`)
`token?`(可选认证令牌)
`authScheme?`(可选认证方案枚举,默认 `Hmac`)
`basicUsername?`(可选 Basic 认证用户名)
`basicPassword?`(可选 Basic 认证密码)
`installPath?`(可选安装路径) | `GeneralUpdateBootstrap` | 轻配置入口,配合 manifest 使用 | 只提供服务端入口和密钥,身份信息由 manifest 补齐;支持 HMAC / Bearer / Basic 多种认证方式 | -| `SetOption(Option, T)` | `option` — 选项键, `value` — 选项值 | `GeneralUpdateBootstrap` | 设置运行时选项 | 传入 `null` 给可空选项会移除当前设置 | -| `UseDiffPipeline(Action)` | `configure` — 差分管道配置委托 | `GeneralUpdateBootstrap` | 替换或调整差分补丁管道 | 未调用时使用默认配置 | -| `AddListenerUpdateInfo(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 接收服务端版本信息 | 无更新时也会触发(`Info.Code = 404`) | -| `AddListenerUpdatePrecheck(...)` | `Func` | `GeneralUpdateBootstrap` | 下载前预检查 | 返回 `true` 表示跳过非强制更新 | -| `AddListenerProgress(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 更新进度条、状态文本 | 同时包含下载进度和差分进度 | -| `AddListenerMultiDownloadCompleted(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 标记单个资源下载完成 | 不要当做"全部下载完成" | -| `AddListenerMultiAllDownloadCompleted(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 全部下载完成后的后续处理 | 包含失败汇总 `FailedVersions` | -| `AddListenerMultiDownloadError(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 记录单个资源下载失败 | 整体成功仍以 `MultiAllDownloadCompleted` 为准 | -| `AddListenerMultiDownloadStatistics(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 展示下载速度和剩余时间 | 新代码优先使用 `AddListenerProgress` | -| `AddListenerException(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 上报异常、展示错误信息 | 仅通知,不自动重试 | -| `AddEventListener()` | 泛型参数 — 监听器类型 | `GeneralUpdateBootstrap` | 批量注册事件监听器 | `T` 必须实现 `IUpdateEventListener`,推荐继承 `UpdateEventListenerBase` | -| `Hooks()` | 泛型参数 — Hook 类型 | `GeneralUpdateBootstrap` | 注册生命周期钩子 | `T` 必须实现 `IUpdateHooks` 且有无参构造函数 | -| `UpdateReporter()` | 泛型参数 — Reporter 类型 | `GeneralUpdateBootstrap` | 注册状态上报器 | `T` 必须实现 `IUpdateReporter` | -| `SslPolicy()` | 泛型参数 — SSL 策略类型 | `GeneralUpdateBootstrap` | 自定义 HTTPS 证书校验 | 生产环境不建议无条件返回 `true` | -| `HttpAuth()` | 泛型参数 — 认证提供器类型 | `GeneralUpdateBootstrap` | 自定义 HTTP 请求认证 | `T` 必须实现 `IHttpAuthProvider` | -| `DownloadSource()` | 泛型参数 — 下载来源类型 | `GeneralUpdateBootstrap` | 自定义版本清单来源 | `T` 必须实现 `IDownloadSource` | -| `DownloadPolicy()` | 泛型参数 — 下载策略类型 | `GeneralUpdateBootstrap` | 自定义下载重试/超时策略 | `T` 必须实现 `IDownloadPolicy` | -| `DownloadExecutor()` | 泛型参数 — 下载执行器类型 | `GeneralUpdateBootstrap` | 自定义单文件下载实现 | `T` 必须实现 `IDownloadExecutor` | -| `DownloadPipeline()` | 泛型参数 — 下载管道类型 | `GeneralUpdateBootstrap` | 自定义下载后处理 | `T` 必须实现 `IDownloadPipeline` | -| `DownloadOrchestrator()` | 泛型参数 — 下载编排器类型 | `GeneralUpdateBootstrap` | 完全替换批量下载逻辑 | 只有需要完整替换下载行为时才建议实现 | -| `Strategy()` | 泛型参数 — 策略类型 | `GeneralUpdateBootstrap` | 自定义平台级更新策略 | `T` 必须实现 `IStrategy` | - -**DiffPipelineBuilder:** - -| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | -| --- | --- | --- | --- | --- | -| `UseDiffer(IBinaryDiffer)` | `differ` — 差分算法实例 | `DiffPipelineBuilder` | 替换文件级差分算法 | 可选 `BsdiffDiffer` / `StreamingHdiffDiffer` / 自定义 | -| `UseCleanMatcher(ICleanMatcher)` | `matcher` — Clean 匹配器 | `DiffPipelineBuilder` | 自定义 Clean 阶段的文件匹配逻辑 | 默认 `DefaultCleanMatcher` | -| `UseDirtyMatcher(IDirtyMatcher)` | `matcher` — Dirty 匹配器 | `DiffPipelineBuilder` | 自定义 Dirty 阶段的补丁匹配逻辑 | 默认 `DefaultDirtyMatcher` | -| `WithParallelism(int)` | `degree` — 并行度 | `DiffPipelineBuilder` | 设置差分文件并行处理数 | 默认 2;建议 1-8 | -| `WithStopOnFirstError(bool)` | `stop` — 是否首次错误即停止 | `DiffPipelineBuilder` | 错误策略控制 | 默认 `false` | -| `WithProgress(IProgress)` | `progress` — 进度报告器 | `DiffPipelineBuilder` | 接入差分进度回调 | 可配合 `Progress` 使用 | -| `Build()` | 无 | `DiffPipeline` | 构建差分管道实例 | 一般在 `UseDiffPipeline` 回调内部调用 | - -**UpdateRequestBuilder:** - -| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | -| --- | --- | --- | --- | --- | -| `Create()` | 无 | `UpdateRequestBuilder` | 从 `update_config.json` 读取配置 | 文件不存在会抛出 `FileNotFoundException` | -| `SetUpdateUrl(string)` | `url` | `UpdateRequestBuilder` | 设置更新地址 | 必须为绝对 URL | -| `SetUpgradeAppName(string)` | `name` | `UpdateRequestBuilder` | 设置升级程序文件名 | — | -| `SetMainAppName(string)` | `name` | `UpdateRequestBuilder` | 设置主程序文件名 | — | -| `SetClientVersion(string)` | `version` | `UpdateRequestBuilder` | 设置客户端版本 | SemVer 格式 | -| `SetAppSecretKey(string)` | `key` | `UpdateRequestBuilder` | 设置应用密钥 | — | -| `SetInstallPath(string)` | `path` | `UpdateRequestBuilder` | 设置安装目录 | — | -| `SetProductId(string)` | `id` | `UpdateRequestBuilder` | 设置产品标识 | — | -| `SetReportUrl(string)` | `url` | `UpdateRequestBuilder` | 设置上报地址 | — | -| `SetUpdateLogUrl(string)` | `url` | `UpdateRequestBuilder` | 设置更新日志地址 | — | -| `SetUpgradeClientVersion(string)` | `version` | `UpdateRequestBuilder` | 设置升级程序版本 | — | -| `SetBowl(string)` | `bowl` | `UpdateRequestBuilder` | 设置 Bowl 进程名 | — | -| `SetDriverDirectory(string)` | `path` | `UpdateRequestBuilder` | 设置驱动目录 | — | -| `SetScheme(string)` | `scheme` | `UpdateRequestBuilder` | 设置认证方案 | — | -| `SetToken(string)` | `token` | `UpdateRequestBuilder` | 设置认证令牌 | — | -| `SetAuthScheme(AuthScheme)` | `authScheme` — 认证方案枚举 | `UpdateRequestBuilder` | 设置认证方案(Hmac / Bearer / ApiKey / Basic) | 默认 `Hmac` | -| `SetBasicUsername(string)` | `username` | `UpdateRequestBuilder` | 设置 Basic 认证用户名 | 需配合 `SetAuthScheme(AuthScheme.Basic)` | -| `SetBasicPassword(string)` | `password` | `UpdateRequestBuilder` | 设置 Basic 认证密码 | 需配合 `SetAuthScheme(AuthScheme.Basic)` | -| `SetFiles(List)` | `files` | `UpdateRequestBuilder` | 设置跳过文件列表 | — | -| `SetFormats(List)` | `formats` | `UpdateRequestBuilder` | 设置跳过扩展名列表 | — | -| `SetDirectories(List)` | `dirs` | `UpdateRequestBuilder` | 设置跳过目录列表 | — | -| `Build()` | 无 | `UpdateRequest` | 构建并校验配置对象 | 会执行 `Validate()` | - -**UpgradeHubService:** - -| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | -| --- | --- | --- | --- | --- | -| `UpgradeHubService(string, string?, string?)` | `url` — SignalR Hub 地址;`token` — 可选 ID4 认证令牌;`appkey` — 可选客户端唯一标识 | —(构造函数) | 创建推送服务实例 | `appkey` 用于服务端定向推送,推荐使用固定 GUID | -| `StartAsync()` | 无 | `Task` | 建立 SignalR 长连接,开始接收推送 | 可重复调用(先 `StopAsync` 后重新 `StartAsync`) | -| `StopAsync()` | 无 | `Task` | 优雅停止连接,保留重连能力 | 适合应用进入后台时调用 | -| `DisposeAsync()` | 无 | `Task` | 彻底释放 Hub 及所有资源 | 释放后不可再复用 | -| `AddListenerReceive(Action)` | `receiveMessageCallback` — 接收消息回调 | `void` | 订阅服务端推送的版本更新消息 | 消息内容为服务端推送的 JSON 字符串 | -| `AddListenerOnline(Action)` | `onlineMessageCallback` — 状态回调 | `void` | 订阅在线/离线状态变化通知 | — | -| `AddListenerReconnected(Func?)` | `reconnectedCallback` — 重连回调 | `void` | 订阅断线重连成功通知 | 参数为新的 connectionId(可能为 null) | -| `AddListenerClosed(Func)` | `closeCallback` — 关闭回调 | `void` | 订阅连接关闭通知 | 正常关闭时异常参数为 null | - -### 3.3 回调事件 - -| 事件名称 | 回调参数 | 触发时机 | 使用说明 | -| --- | --- | --- | --- | -| `AddListenerUpdateInfo` | `UpdateInfoEventArgs` — `Info.Code`, `Info.Body`(`VersionEntry` 列表) | 标准 Client 策略完成版本对比后触发 | 无更新时 `Code = 404`;有更新时 `Body` 包含待下载的 `VersionEntry` 列表 | -| `AddListenerUpdatePrecheck` | `Func` — 返回 `true` 跳过(非强制),`false` 继续 | `UpdateInfo` 事件之后、下载之前 | 用于磁盘空间检查、网络检测、用户确认弹窗;强制更新不进入跳过逻辑 | -| `AddListenerProgress` | `ProgressEventArgs` — `Progress`(下载)或 `DiffProgress`(差分) | 下载进度或差分进度更新时 | 同一参数中 `Progress` 和 `DiffProgress` 只有一个非空 | -| `AddListenerMultiDownloadCompleted` | `MultiDownloadCompletedEventArgs` — `Version`, `IsCompleted` | 单个资源包下载完成时 | 不要当作"全部资源下载完成"的判断依据 | -| `AddListenerMultiAllDownloadCompleted` | `MultiAllDownloadCompletedEventArgs` — `IsAllDownloadCompleted`, `FailedVersions` | 所有下载任务结束后触发一次 | 失败明细在 `FailedVersions` 中 | -| `AddListenerMultiDownloadError` | `MultiDownloadErrorEventArgs` — `Exception`, `Version` | 单个资源下载失败时 | 记录失败项用于展示或监控 | -| `AddListenerMultiDownloadStatistics` | `MultiDownloadStatisticsEventArgs` — `Speed`, `Remaining`, `BytesReceived` | 兼容旧下载统计/自定义下载实现 | 新代码建议使用 `AddListenerProgress` | -| `AddListenerException` | `ExceptionEventArgs` — `Exception`, `Message` | 各策略捕获异常时 | 仅通知,不等同于自动重试 | - -**UpgradeHubService 推送事件:** - -| 事件名称 | 回调参数 | 触发时机 | 使用说明 | -| --- | --- | --- | --- | -| `AddListenerReceive` | `Action` — 消息内容(JSON 字符串) | 服务端推送版本更新消息时 | 消息格式由服务端决定,建议 JSON 格式 | -| `AddListenerOnline` | `Action` — 状态描述 | 在线/离线状态变化时 | 用于 UI 状态展示 | -| `AddListenerReconnected` | `Func?` — 新的 connectionId | 断线重连成功后 | 可用于刷新客户端状态 | -| `AddListenerClosed` | `Func` — 关闭原因(null 为正常关闭) | 连接关闭时 | 用于记录日志和清理资源 | - ---- - -## 4. 扩展示例(高阶用法) - -### 4.1 组件可扩展能力总览 - -Core 通过 `AbstractBootstrap` 基类提供 10 个扩展注册方法,全部返回当前 bootstrap 实例,支持链式调用。所有注册的类型必须具有无参构造函数。 - -| 扩展接口 | 注册方法 | 影响范围 | -| --- | --- | --- | -| `IUpdateHooks` | `Hooks()` | 更新生命周期前后置逻辑 | -| `IUpdateReporter` | `UpdateReporter()` | 更新状态上报 | -| `ISslValidationPolicy` | `SslPolicy()` | HTTPS 证书校验 | -| `IHttpAuthProvider` | `HttpAuth()` | HTTP 请求认证 | -| `IDownloadSource` | `DownloadSource()` | 版本清单和下载资源来源 | -| `IDownloadPolicy` | `DownloadPolicy()` | 下载重试、超时、熔断策略 | -| `IDownloadExecutor` | `DownloadExecutor()` | 单文件下载实现 | -| `IDownloadPipeline` | `DownloadPipeline()` | 下载后处理(校验、解密、扫描) | -| `IDownloadOrchestrator` | `DownloadOrchestrator()` | 批量下载完整编排 | -| `IStrategy` | `Strategy()` | 自定义平台级更新策略 | - -### 4.2 分场景示例 - -#### 场景 1:自定义差分算法与并行度 - -【场景说明】大型项目希望使用 `StreamingHdiffDiffer` 获得更快的客户端补丁应用速度,并设置并行度为 4。 - -【示例代码】 - -```csharp -using GeneralUpdate.Core; -using GeneralUpdate.Core.Differential; -using GeneralUpdate.Core.Pipeline; -using GeneralUpdate.Differential.Differ; - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .UseDiffPipeline(builder => - { - builder - .UseDiffer(new StreamingHdiffDiffer()) - .UseCleanMatcher(new DefaultCleanMatcher()) - .UseDirtyMatcher(new DefaultDirtyMatcher()) - .WithParallelism(4) - .WithStopOnFirstError(true); - }) - .SetOption(Option.PatchEnabled, true) - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -【效果&注意事项】 -- `StreamingHdiffDiffer` 默认使用 Deflate 压缩,客户端应用补丁更快 -- `WithStopOnFirstError(true)` 表示任意补丁失败立即停止所有并行任务 -- 并行度 4 适合多核 CPU + SSD 环境 - -#### 场景 2:自定义生命周期钩子 - -【场景说明】在更新前检查磁盘空间、更新后写日志、Linux/macOS 下启动前赋予执行权限。 - -【示例代码】 - -```csharp -using GeneralUpdate.Core.Hooks; - -public sealed class ProductUpdateHooks : IUpdateHooks -{ - public Task OnBeforeUpdateAsync(HookContext ctx) - { - var drive = new DriveInfo(Path.GetPathRoot(ctx.InstallPath)!); - if (drive.AvailableFreeSpace < 500L * 1024 * 1024) - { - Console.WriteLine("Insufficient disk space for update."); - return Task.FromResult(false); // 拒绝更新 - } - return Task.FromResult(true); - } - - public Task OnDownloadCompletedAsync(DownloadContext ctx) - { - Console.WriteLine($"Downloaded {ctx.AssetName}: {ctx.Success}"); - return Task.CompletedTask; - } - - public Task OnAfterUpdateAsync(HookContext ctx) - { - File.AppendAllText( - Path.Combine(ctx.InstallPath, "update-history.log"), - $"{DateTimeOffset.Now:O} {ctx.CurrentVersion} -> {ctx.TargetVersion}{Environment.NewLine}"); - return Task.CompletedTask; - } - - public Task OnUpdateErrorAsync(HookContext ctx, Exception ex) - { - File.AppendAllText( - Path.Combine(ctx.InstallPath, "update-error.log"), - $"{ex}{Environment.NewLine}"); - return Task.CompletedTask; - } - - public Task OnBeforeStartAppAsync(HookContext ctx) - { - // Linux/macOS 下可用 UnixPermissionHooks - return Task.CompletedTask; - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .Hooks() - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -【效果&注意事项】 -- `OnBeforeUpdateAsync` 返回 `false` 会中断本次更新 -- `OnBeforeStartAppAsync` 适合 Linux/macOS 的 `chmod +x` 操作 -- 内置 `UnixPermissionHooks` 可直接使用 - -#### 场景 3:自定义下载来源(私有服务/配置中心) - -【场景说明】从企业内部配置中心获取下载资源列表,而不是调用标准版本检查 API。 - -【示例代码】 - -```csharp -using GeneralUpdate.Core.Download.Abstractions; -using GeneralUpdate.Core.Download.Models; - -public sealed class ConfigCenterDownloadSource : IDownloadSource -{ - public async Task ListAsync(CancellationToken token = default) - { - // 从配置中心拉取资源列表... - var assets = new[] - { - new DownloadAsset( - Name: "MyApp-2.0.0.zip", - Url: "https://cdn.internal.example.com/releases/MyApp-2.0.0.zip", - Size: 50_000_000, - SHA256: "abc123...", - Version: "2.0.0") - }; - - return new DownloadSourceResult - { - Assets = assets, - HasMainUpdate = true, - HasUpgradeUpdate = false - }; - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .DownloadSource() - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -【效果&注意事项】 -- 自定义 `IDownloadSource` 会完全替换默认的 HTTP 版本检查逻辑 -- 需要同时注册 `DownloadOrchestrator()` 时,orchestrator 会接管完整下载流程 - -#### 场景 4:多协议 HTTP 认证 - -【场景说明】Core 内置四种认证方案,通过 `AuthScheme` 枚举一键切换,也可通过 `IHttpAuthProvider` 完全自定义。 - -**方式一:使用内置 `AuthScheme` 枚举(推荐)** - -```csharp -using GeneralUpdate.Core.Configuration; - -// HMAC-SHA256 签名认证(默认) -var request = new UpdateRequest -{ - UpdateUrl = "https://update.example.com/api/upgrade/verification", - AppSecretKey = "your-app-secret", - AuthScheme = AuthScheme.Hmac -}; - -// Bearer Token 认证 -var bearerRequest = new UpdateRequest -{ - UpdateUrl = "https://update.example.com/api/upgrade/verification", - AppSecretKey = "your-app-secret", - AuthScheme = AuthScheme.Bearer, - Token = "your-jwt-token" -}; - -// API Key 认证 -var apiKeyRequest = new UpdateRequest -{ - UpdateUrl = "https://update.example.com/api/upgrade/verification", - AppSecretKey = "your-app-secret", - AuthScheme = AuthScheme.ApiKey, - Token = "your-api-key" -}; - -// HTTP Basic 认证 -var basicRequest = new UpdateRequest -{ - UpdateUrl = "https://update.example.com/api/upgrade/verification", - AppSecretKey = "your-app-secret", - AuthScheme = AuthScheme.Basic, - BasicUsername = "admin", - BasicPassword = "password123" -}; -``` - -**方式二:自定义 `IHttpAuthProvider`(高级)** - -【场景说明】为 Core 发出的 HTTP 请求追加自定义认证逻辑(如从配置中心动态获取 Token)。 - -【示例代码】 - -```csharp -using GeneralUpdate.Core.Security; - -public sealed class JwtAuthProvider : IHttpAuthProvider -{ - private readonly string _token; - - public JwtAuthProvider() - { - // 从配置或环境变量读取 token - _token = Environment.GetEnvironmentVariable("UPDATE_JWT_TOKEN") ?? ""; - } - - public Task ApplyAuthAsync(HttpRequestMessage request, CancellationToken token = default) - { - request.Headers.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token); - return Task.CompletedTask; - } -} - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .HttpAuth() - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -【效果&注意事项】 -- 内置 Provider:`HmacAuthProvider`(默认)、`BearerTokenAuthProvider`、`ApiKeyAuthProvider`、`BasicAuthProvider` -- `HttpAuthProviderFactory` 根据 `AuthScheme` 自动选择对应 Provider -- 自定义 Provider 需要在无参构造函数中自行读取配置 -- HMAC 签名算法:`HMAC-SHA256(body|timestamp)`,请求头 `X-Update-Timestamp` + `X-Update-Signature` - -#### 场景 5:静默更新 + 进程退出触发升级 - -【场景说明】主程序启动后后台轮询更新,下载完成后不打扰用户,待进程退出时启动升级程序。 - -【示例代码】 - -```csharp -using GeneralUpdate.Core; - -// 主程序启动时 -var bootstrap = new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret") - .SetOption(Option.AppType, AppType.Client) - .SetOption(Option.Silent, true) - .SetOption(Option.SilentPollIntervalMinutes, 30) - .SetOption(Option.LaunchClientAfterUpdate, true) - .AddListenerException((_, e) => - { - Console.WriteLine($"Update error: {e.Message}"); - }); - -await bootstrap.LaunchAsync(); - -// 应用退出时检查是否有准备好的更新 -AppDomain.CurrentDomain.ProcessExit += (_, _) => -{ - if (bootstrap.SilentOrchestrator?.HasPreparedUpdate == true) - { - bootstrap.SilentOrchestrator.TryLaunchUpgrade(); - } -}; -``` - -【效果&注意事项】 -- 静默模式在 `AppType.Client` 下生效 -- 文件替换发生在主程序退出之后 -- 轮询间隔建议不低于 30 分钟 - -#### 场景 6:SignalR 实时推送 + 常规更新联动 - -【场景说明】客户端同时使用 `UpgradeHubService` 接收服务端实时推送和 `GeneralUpdateBootstrap` 执行常规更新。服务端有新版时可立即推送通知,客户端无需等待轮询即可触发更新。 - -【示例代码】 - -```csharp -using GeneralUpdate.Core; -using GeneralUpdate.Core.Configuration; -using GeneralUpdate.Core.Hubs; - -// 1. 启动 SignalR 推送监听 -var hub = new UpgradeHubService( - "http://localhost:5000/UpgradeHub", - token: null, - appkey: "dfeb5833-975e-4afb-88f1-6278ee9aeff6"); - -hub.AddListenerReceive(async (message) => -{ - Console.WriteLine($"收到实时推送: {message}"); - // 收到推送后可以立即触发更新检查 - // 或在 UI 中提示用户有新版本可用 -}); - -hub.AddListenerOnline((info) => - Console.WriteLine($"在线状态: {info}")); - -hub.AddListenerReconnected((connectionId) => -{ - Console.WriteLine($"已重连,connectionId={connectionId}"); - return Task.CompletedTask; -}); - -hub.AddListenerClosed((exception) => -{ - if (exception is not null) - Console.WriteLine($"连接异常关闭: {exception.Message}"); - else - Console.WriteLine("连接已正常关闭"); - return Task.CompletedTask; -}); - -await hub.StartAsync(); - -// 2. 常规更新流程 -await new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret") - .SetOption(Option.AppType, AppType.Client) - .AddListenerException((_, e) => Console.WriteLine(e.Exception)) - .LaunchAsync(); - -// 3. 应用退出时清理 -// await hub.StopAsync(); -// await hub.DisposeAsync(); -``` - -【效果&注意事项】 -- `UpgradeHubService` 与 `GeneralUpdateBootstrap` 互补:推送做通知,Bootstrap 做实际更新 -- `appkey` 推荐与 `AppSecretKey` 保持一致,便于服务端定向推送 -- `StopAsync` 保留重连能力,适合应用进入后台时调用 -- `DisposeAsync` 彻底释放,适合应用退出时调用 - -#### 场景 7:DI 容器中注册 UpgradeHubService - -【场景说明】在 Prism / Generic Host / ASP.NET Core 等 DI 容器中注册 `IUpgradeHubService`,管理推送服务生命周期。 - -【示例代码】 - -```csharp -using GeneralUpdate.Core.Hubs; - -// Prism 示例 -protected override void RegisterTypes(IContainerRegistry containerRegistry) -{ - containerRegistry.Register(); -} - -// 在 ViewModel 中使用 -public MainWindowViewModel(IUpgradeHubService hubService) -{ - hubService.AddListenerReceive((message) => - { - Console.WriteLine($"收到推送: {message}"); - }); - _ = hubService.StartAsync(); -} - -// Generic Host / ASP.NET Core 示例 -builder.Services.AddSingleton(sp => -{ - var config = sp.GetRequiredService(); - return new UpgradeHubService( - config["HubUrl"]!, - appkey: config["AppSecretKey"]); -}); -``` - -【效果&注意事项】 -- DI 容器管理生命周期,避免手动 Dispose -- 可将配置从 `appsettings.json` 注入 - -#### 场景 8:跨平台自适应策略 - -【场景说明】Core 根据运行时平台自动选择 `WindowsStrategy` / `LinuxStrategy` / `MacStrategy`,无需手动指定。也可通过 `PlatformType` 显式控制或自定义平台策略。 - -【示例代码】 - -```csharp -using GeneralUpdate.Core; -using GeneralUpdate.Core.Configuration; -using GeneralUpdate.Core.Strategy; - -// 方式一:自动检测(推荐) -// Core 会根据 RuntimeInformation 自动选择对应平台策略 -await new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret") - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -// Windows → WindowsStrategy -// Linux → LinuxStrategy(无 Bowl 支持) -// macOS → MacStrategy - -// 方式二:显式指定平台策略(高级) -await new GeneralUpdateBootstrap() - .SetConfig(request) - .Strategy() - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -【效果&注意事项】 -- 三种平台策略均继承 `AbstractStrategy`,共享 Hash → Compress → Patch 管道 -- `WindowsStrategy` 额外支持 Bowl 辅助进程管理 -- `LinuxStrategy` 建议配合 `UnixPermissionHooks` 使用以自动赋予执行权限 -- 平台策略可被自定义 `IStrategy` 完全替换 - ---- - -## 5. 常规使用示例 - -### 5.1 快速入门示例(最简 demo) - -最简配置:使用 manifest 自动发现身份信息,只需要配置服务端入口和密钥。 - -```csharp -using GeneralUpdate.Core; - -await new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://update.example.com/api/upgrade/verification", - appSecretKey: "your-app-secret") - .SetOption(Option.AppType, AppType.Client) - .LaunchAsync(); -``` - -升级程序入口(`Update.exe`): - -```csharp -await new GeneralUpdateBootstrap() - .SetOption(Option.AppType, AppType.Upgrade) - .AddListenerException((_, e) => Console.WriteLine(e.Exception)) - .LaunchAsync(); -``` - -### 5.2 基础参数组合示例 - -```csharp -using GeneralUpdate.Core; -using GeneralUpdate.Core.Configuration; - -var request = new UpdateRequest -{ - UpdateUrl = "https://update.example.com/api/upgrade/verification", - ReportUrl = "https://update.example.com/api/upgrade/report", - UpdateAppName = "UpgradeSample.exe", - MainAppName = "ClientSample.exe", - InstallPath = AppDomain.CurrentDomain.BaseDirectory, - ClientVersion = "1.0.0", - AppSecretKey = "your-app-secret", - ProductId = "your-product-id", - Files = new List { "appsettings.json" }, - Formats = new List { ".log", ".tmp" }, - Directories = new List { "logs", "cache" } -}; - -await new GeneralUpdateBootstrap() - .SetConfig(request) - .SetOption(Option.AppType, AppType.Client) - .SetOption(Option.DiffMode, DiffMode.Parallel) - .SetOption(Option.MaxConcurrency, 4) - .SetOption(Option.PatchEnabled, true) - .AddListenerProgress((_, e) => - { - if (e.Progress != null) - Console.WriteLine($"{e.Progress.AssetName}: {e.Progress.Percentage:F1}%"); - }) - .AddListenerException((_, e) => Console.WriteLine(e.Exception)) - .LaunchAsync(); -``` - -### 5.2.1 SignalR 实时推送快速入门 - -```csharp -using GeneralUpdate.Core.Hubs; - -// 创建推送客户端 -var hub = new UpgradeHubService( - "http://localhost:5000/UpgradeHub", - appkey: Guid.NewGuid().ToString()); - -// 订阅推送消息 -hub.AddListenerReceive((message) => -{ - Console.WriteLine($"收到更新推送: {message}"); -}); - -// 建立连接 -await hub.StartAsync(); - -Console.WriteLine("已连接,等待服务端推送..."); -Console.ReadLine(); - -// 停止连接(保留重连能力) -await hub.StopAsync(); - -// 释放资源(不可再复用) -await hub.DisposeAsync(); -``` - -### 5.3 真实业务落地示例(多参数联动) - -完整 Client 端更新流程,包含事件监听、差分管道、并发控制和状态上报: - -```csharp -using GeneralUpdate.Core; -using GeneralUpdate.Core.Configuration; -using GeneralUpdate.Core.Download; -using GeneralUpdate.Core.Download.Reporting; -using GeneralUpdate.Core.Event; -using GeneralUpdate.Core.Hooks; -using GeneralUpdate.Core.Pipeline; -using GeneralUpdate.Differential.Differ; - -// 1. 构建配置 -var request = new UpdateRequestBuilder() - .SetUpdateUrl("https://update.mycompany.com/api/upgrade/verification") - .SetReportUrl("https://update.mycompany.com/api/upgrade/report") - .SetUpgradeAppName("MyApp.Upgrade.exe") - .SetMainAppName("MyApp.exe") - .SetClientVersion("1.0.0") - .SetUpgradeClientVersion("1.0.0") - .SetAppSecretKey("prod-secret-key") - .SetProductId("my-product") - .SetInstallPath(AppDomain.CurrentDomain.BaseDirectory) - .SetScheme("Bearer") - .SetToken(Environment.GetEnvironmentVariable("UPDATE_TOKEN") ?? "") - .SetFiles(new List { "appsettings.Development.json" }) - .SetFormats(new List { ".log", ".tmp", ".pdb" }) - .SetDirectories(new List { "logs", "cache", "temp" }) - .Build(); - -// 2. 注入业务钩子 -public sealed class BusinessUpdateHooks : IUpdateHooks -{ - public Task OnBeforeUpdateAsync(HookContext ctx) - { - // 检查磁盘空间 - var drive = new DriveInfo(Path.GetPathRoot(ctx.InstallPath)!); - if (drive.AvailableFreeSpace < 1024L * 1024 * 1024) // < 1GB - return Task.FromResult(false); - return Task.FromResult(true); - } - - public Task OnDownloadCompletedAsync(DownloadContext ctx) - => Task.CompletedTask; - - public Task OnAfterUpdateAsync(HookContext ctx) - { - // 写更新成功日志 - File.AppendAllText( - Path.Combine(ctx.InstallPath, "update.log"), - $"{DateTimeOffset.Now:O} Updated to {ctx.TargetVersion}{Environment.NewLine}"); - return Task.CompletedTask; - } - - public Task OnUpdateErrorAsync(HookContext ctx, Exception ex) - { - // 上报到监控系统 - return Task.CompletedTask; - } - - public Task OnBeforeStartAppAsync(HookContext ctx) - => Task.CompletedTask; -} - -// 3. 配置差分管道 -Action configurePipeline = builder => -{ - builder - .UseDiffer(new StreamingHdiffDiffer()) - .WithParallelism(4) - .WithStopOnFirstError(true) - .WithProgress(new Progress(p => - { - Console.WriteLine($"Patch: {p.Completed}/{p.Total} {p.CurrentFile} {p.Percentage}%"); - })); -}; - -// 4. 启动更新 -var bootstrap = new GeneralUpdateBootstrap() - .SetConfig(request) - .SetOption(Option.AppType, AppType.Client) - .SetOption(Option.DiffMode, DiffMode.Parallel) - .SetOption(Option.MaxConcurrency, 4) - .SetOption(Option.DownloadTimeout, 120) - .SetOption(Option.PatchEnabled, true) - .SetOption(Option.BackupEnabled, true) - .SetOption(Option.VerifyChecksum, true) - .SetOption(Option.RetryCount, 5) - .SetOption(Option.RetryInterval, TimeSpan.FromSeconds(2)) - .Hooks() - .UseDiffPipeline(configurePipeline) - .AddListenerUpdateInfo((_, e) => - { - if (e.Info?.Code == "404") - { - Console.WriteLine("Already up to date."); - return; - } - Console.WriteLine($"Found {e.Info?.Body?.Count ?? 0} version(s) to download."); - }) - .AddListenerUpdatePrecheck(e => - { - var hasUpdate = (e.Info?.Body?.Count ?? 0) > 0; - return !hasUpdate; // 有更新则继续(返回 false = 不跳过) - }) - .AddListenerProgress((_, e) => - { - if (e.Progress != null) - { - var pct = e.Progress.Percentage; - var status = e.Progress.Status; - var name = e.Progress.AssetName; - Console.WriteLine($"[Download] {name}: {pct:F1}% ({status})"); - } - if (e.DiffProgress != null) - { - var d = e.DiffProgress; - Console.WriteLine($"[Patch] {d.CurrentFile}: {d.Completed}/{d.Total} {d.Percentage}%"); - } - }) - .AddListenerMultiAllDownloadCompleted((_, e) => - { - if (e.IsAllDownloadCompleted) - Console.WriteLine("All downloads completed successfully."); - else - { - Console.WriteLine($"Download completed with {e.FailedVersions.Count} failure(s):"); - foreach (var (asset, error) in e.FailedVersions) - Console.WriteLine($" - {asset.Name}: {error}"); - } - }) - .AddListenerException((_, e) => - { - Console.WriteLine($"Update error: {e.Message}"); - Console.WriteLine(e.Exception); - }); - -await bootstrap.LaunchAsync(); -``` - ---- - -## 6. 全局配置 - -Core 不依赖全局配置文件,而是通过 `generalupdate.manifest.json` 提供应用身份信息。下面是 manifest 的配置语法和优先级规则。 - -### Manifest 配置语法 - -```json -{ - "mainAppName": "ClientSample.exe", - "clientVersion": "1.0.0", - "appType": "Client", - "updateAppName": "UpgradeSample.exe", - "upgradeClientVersion": "1.0.0", - "productId": "sample-product", - "updatePath": "update/" -} -``` - -### 配置优先级规则 - -| 优先级 | 配置来源 | 说明 | -| --- | --- | --- | -| 1(最高) | 代码中 `SetConfig(UpdateRequest)` 或 `SetSource(...)` 显式设置的值 | 覆盖所有其他来源 | -| 2 | `generalupdate.manifest.json` 中的字段 | 自动补齐代码中未显式设置的字段 | -| 3(最低) | 组件内部默认值 | `UpdateAppName = "Update.exe"`, `InstallPath = BaseDirectory` 等 | - -### Manifest 字段映射 - -| JSON 字段 | Core 字段 | 说明 | -| --- | --- | --- | -| `mainAppName` | `MainAppName` | 主程序可执行文件名 | -| `clientVersion` | `ClientVersion` | 当前主程序版本 | -| `appType` | `AppType` | 当前进程角色(`Client`, `Upgrade`, `OssClient`, `OssUpgrade`) | -| `updateAppName` | `UpdateAppName` | 升级程序文件名 | -| `upgradeClientVersion` | `UpgradeClientVersion` | 升级程序自身版本 | -| `productId` | `ProductId` | 产品标识 | -| `updatePath` | `UpdatePath` | 升级程序所在目录(相对 `InstallPath`) | - -### 版本回写机制 - -更新成功后,Core 自动回写客户端版本到 `generalupdate.manifest.json`: - -| 场景 | 回写字段 | -| --- | --- | -| 主程序更新完成 | `ClientVersion` | -| 升级程序自身更新完成 | `UpgradeClientVersion` | - -### 日志配置 - -```csharp -using GeneralUpdate.Core; - -// 关闭日志(性能敏感场景) -GeneralTracer.SetTracingEnabled(false); - -// 重新开启(排查问题) -GeneralTracer.SetTracingEnabled(true); - -// 释放日志资源 -GeneralTracer.Dispose(); -``` - -### AOT / Trim 兼容性 - -`net8.0` 和 `net10.0` 目标框架支持 AOT 发布和裁剪。Core 在 `JsonContext` 命名空间下提供了 9 个源生成 JSON 序列化上下文,覆盖所有配置和模型类型的序列化需求,避免反射导致的裁剪问题: - -| 上下文类 | 覆盖类型 | -| --- | --- | -| `BaseResponseJsonContext` | `BaseResponseDTO`、`VersionRespDTO` | -| `VersionEntryJsonContext` | `VersionEntry`、`VersionIdentity` | -| `UpdateRequestJsonContext` | `UpdateRequest`、`UpdateConfiguration` | -| `ProcessContractJsonContext` | `ProcessContract` | -| `ManifestInfoJsonContext` | `ManifestInfo` | -| `OssVersionRecordJsonContext` | `OssVersionRecord` | -| `DownloadAssetJsonContext` | `DownloadAsset` | -| `BlackPolicyJsonContext` | `BlackPolicy` | -| `PushPayloadJsonContext` | `PushPayload` | - -### 进程环境变量(IPC 辅助) - -`Environments` 静态类通过 AES 加密临时文件在 Client 与 Upgrade 进程之间传递键值对环境变量,用于升级失败追踪等场景: - -| 环境变量 Key | 说明 | -| --- | --- | -| `UpgradeFail` | 记录上次升级失败的版本号,避免重复尝试失败版本 | - -```csharp -using GeneralUpdate.Core.Configuration; - -// 设置环境变量(Client 端写入) -Environments.Set("UpgradeFail", "2.0.0"); - -// 读取环境变量(Upgrade 端读取) -var failedVersion = Environments.Get("UpgradeFail"); -``` - ---- - -## 相关资源 - -- [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) -- [Samples 示例代码](https://github.com/GeneralLibrary/GeneralUpdate-Samples) -- [GeneralUpdate.Tools](https://github.com/GeneralLibrary/GeneralUpdate.Tools) -- [入门实战手册](../quickstart/Beginner%20cookbook.md) -- [组件文档](./GeneralUpdate.Core.md) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md deleted file mode 100644 index c78d73f..0000000 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md +++ /dev/null @@ -1,367 +0,0 @@ ---- -sidebar_position: 6 ---- - -# GeneralUpdate.Differential - -**命名空间:** `GeneralUpdate.Differential` | **主要入口:** `IBinaryDiffer`、`BsdiffDiffer`、`StreamingHdiffDiffer` | **NuGet 包:** `GeneralUpdate.Differential` - -## 1. 组件简介 - -### 1.1 组件概述 - -**GeneralUpdate.Differential** 是 GeneralUpdate 的二进制差分组件,专注解决"一个旧文件 + 一个补丁文件 = 一个新文件"的问题。它提供可替换的文件级差分算法(BSDIFF 4.0 / Streaming HDiff)、补丁压缩抽象(BZip2 / Deflate,源码中预留 .NET 6+ Brotli)和 BSDIFF 兼容补丁读写能力。 - -目录级对比、批量补丁生成、并行调度、删除文件处理和更新流程编排由 `GeneralUpdate.Core` 的 `DiffPipeline` 或 `GeneralUpdate.Tools` 承担。 - -**核心能力:** - -| 能力 | 说明 | -| --- | --- | -| 文件级差分生成 | `CleanAsync(oldFile, newFile, patchFile)` — 对比新旧文件生成 `.patch` 补丁 | -| 文件级差分应用 | `DirtyAsync(oldFile, newFile, patchFile)` — 旧文件 + 补丁 → 新文件 | -| 可替换差分算法 | `BsdiffDiffer`(BSDIFF 4.0,后缀排序)和 `StreamingHdiffDiffer`(块哈希索引) | -| 可替换压缩格式 | BZip2 (0x00)、Deflate (0x01),源码中通过 `#if NET6_0_OR_GREATER` 条件编译预留 Brotli (0x02) | -| BSDIFF 兼容格式 | 写入 33 字节扩展头(32 字节 BSDIFF40 + 1 字节压缩格式),兼容 32 字节旧头 | -| 线程安全 | 内置 differ 和压缩提供器均支持并发调用 | - -**解决的业务痛点:** -- 全量更新带宽成本高,差分更新可将更新包从 GB 级降低到 MB 甚至 KB 级 -- 不同文件类型和变化模式需要不同的差分策略(细粒度匹配 vs 快速块匹配) -- 压缩算法的选择影响客户端解压速度和补丁体积的平衡 - -**业务使用场景:** -- 大型桌面应用(多 DLL、资源文件)的增量更新 -- 固件/驱动包的二进制差分分发 -- 游戏资源热更新 -- CI/CD 发布流水线中自动生成增量补丁包 - -### 1.2 环境与依赖 - -| 项目 | 说明 | -| --- | --- | -| **版本** | `10.5.0-beta.2` | -| **目标框架** | `netstandard2.0`(兼容 .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+) | -| **依赖包** | 无外部依赖(纯 .NET BCL) | -| **兼容性** | 所有支持 .NET Standard 2.0 的平台 | - ---- - -## 2. 组件功能列表 - -| 功能名称 | 功能描述 | 类型 | 是否必填 | 备注限制 | -| --- | --- | --- | --- | --- | -| BSDIFF 4.0 差分生成 | 基于后缀排序的经典差分算法,补丁体积稳定 | 基础 | 可选 | `BsdiffDiffer`,默认 BZip2 压缩 | -| BSDIFF 4.0 补丁应用 | 将 BSDIFF 格式补丁应用到旧文件 | 基础 | 可选 | 支持 32/33 字节两种头部格式 | -| Streaming HDiff 差分生成 | 基于 FNV-1a 块哈希索引的快速差分 | 基础 | 可选 | `StreamingHdiffDiffer`,默认 Deflate 压缩 | -| BZip2 压缩 | 补丁控制段/差异段/额外段的 BZip2 压缩 | 基础 | 可选 | 格式字节 `0x00`,`BsdiffDiffer` 默认 | -| Deflate 压缩 | 补丁段的 Deflate 压缩,解压更快 | 基础 | 可选 | 格式字节 `0x01`,`StreamingHdiffDiffer` 默认 | -| 自定义差分算法 | 实现 `IBinaryDiffer` 接入自研算法 | 拓展 | 可选 | 需保证 Clean/Dirty 一致性 | -| 自定义压缩提供器 | 实现 `ICompressionProvider` 替换压缩方式 | 拓展 | 可选 | 新格式字节需配合扩展补丁读取逻辑 | - ---- - -## 3. API 配置说明 - -### 3.1 配置字段(属性 Props) - -Differential 本身是底层库,不提供配置类。所有参数通过构造函数传入。 - -**BsdiffDiffer 构造参数:** - -| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | -| --- | --- | --- | --- | --- | --- | -| `compressionProvider` | `ICompressionProvider` | `BZip2CompressionProvider` | 可选 | `BZip2CompressionProvider` / `DeflateCompressionProvider` | 补丁压缩提供器 | - -**StreamingHdiffDiffer 构造参数:** - -| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | -| --- | --- | --- | --- | --- | --- | -| `compressionProvider` | `ICompressionProvider` | `DeflateCompressionProvider` | 可选 | `BZip2CompressionProvider` / `DeflateCompressionProvider` | 补丁压缩提供器 | -| `blockSize` | `int` | `65536`(64 KB) | 可选 | 正整数字节数 | 块大小,用于旧文件哈希索引 | -| `maxWindowSize` | `int` | `134217728`(128 MB) | 可选 | 正整数字节数 | 参与计算的最大内存窗口 | - -**DeflateCompressionProvider 构造参数:** - -| 字段名 | 数据类型 | 默认值 | 是否必填 | 枚举/取值范围 | 说明 | -| --- | --- | --- | --- | --- | --- | -| `optimalLevel` | `bool` | `true` | 可选 | `true` / `false` | `true` = `CompressionLevel.Optimal`,`false` = `CompressionLevel.Fastest` | - -**ICompressionProvider 格式标识:** - -| Provider | 格式字节 | 可用性 | 说明 | -| --- | --- | --- | --- | -| `BZip2CompressionProvider` | `0x00` | 完全可用 | BSDIFF 旧补丁兼容,解压成本较高 | -| `DeflateCompressionProvider` | `0x01` | 完全可用 | 解压速度更友好,适合客户端批量应用 | -| `BrotliCompressionProvider` | `0x02` | 仅 .NET 6+ 编译(源码中为完整实现,通过 `#if NET6_0_OR_GREATER` 条件编译) | 当前 `netstandard2.0` 包中不包含,生产不建议使用 | - -### 3.2 实例方法 - -**IBinaryDiffer:** - -| 方法名 | 入参明细 | 返回值 | 使用场景 | 注意事项 | -| --- | --- | --- | --- | --- | -| `CleanAsync(string, string, string, CancellationToken)` | `oldFilePath` — 旧文件路径;`newFilePath` — 新文件路径;`patchFilePath` — 补丁输出路径;`cancellationToken` | `Task` | 发布/构建阶段生成补丁 | 大文件取消不会立即响应,需等待当前文件处理完成 | -| `DirtyAsync(string, string, string, CancellationToken)` | `oldFilePath` — 旧文件路径;`newFilePath` — 补丁还原后文件输出路径;`patchFilePath` — 补丁文件路径;`cancellationToken` | `Task` | 客户端升级阶段应用补丁 | 不会直接覆盖旧文件,结果写入 `newFilePath` | - -### 3.3 回调事件 - -Differential 不发布事件。进度报告和事件通知由 Core 的 `DiffPipeline` 通过 `DiffProgress` 和 `EventManager` 实现。 - ---- - -## 4. 扩展示例(高阶用法) - -### 4.1 组件可扩展能力总览 - -| 扩展接口 | 说明 | -| --- | --- | -| `IBinaryDiffer` | 自定义文件级差分算法,可接入原生库或自研算法 | -| `ICompressionProvider` | 自定义补丁段压缩方式 | - -### 4.2 分场景示例 - -#### 场景 1:自定义差分算法 - -【场景说明】接入企业内部自研的高压缩率差分算法。 - -【示例代码】 - -```csharp -using GeneralUpdate.Differential.Abstractions; - -public sealed class HighRatioDiffer : IBinaryDiffer -{ - public Task CleanAsync( - string oldFilePath, - string newFilePath, - string patchFilePath, - CancellationToken cancellationToken = default) - { - // 调用自研算法生成补丁 - // NativeMethods.GeneratePatch(oldFilePath, newFilePath, patchFilePath); - return Task.CompletedTask; - } - - public Task DirtyAsync( - string oldFilePath, - string newFilePath, - string patchFilePath, - CancellationToken cancellationToken = default) - { - // 调用自研算法应用补丁 - // NativeMethods.ApplyPatch(oldFilePath, patchFilePath, newFilePath); - return Task.CompletedTask; - } -} - -// 在 Core DiffPipeline 中使用 -using GeneralUpdate.Core.Pipeline; - -var pipeline = new DiffPipelineBuilder() - .UseDiffer(new HighRatioDiffer()) - .WithParallelism(4) - .Build(); - -await pipeline.CleanAsync(oldDir, newDir, patchDir); -``` - -【效果&注意事项】 -- 必须保证 `CleanAsync` 产出的补丁能被同一算法的 `DirtyAsync` 正确应用 -- 发布侧和客户端必须使用同一套 differ 实现 - -#### 场景 2:自定义压缩提供器 + BsdiffDiffer - -【场景说明】使用 BsdiffDiffer 算法 + Deflate 压缩,获得更快的客户端补丁应用速度。 - -【示例代码】 - -```csharp -using GeneralUpdate.Differential.Abstractions; -using GeneralUpdate.Differential.Differ; - -// BsdiffDiffer 的精确匹配 + Deflate 的快速解压 -var differ = new BsdiffDiffer( - new DeflateCompressionProvider(optimalLevel: false)); - -await differ.CleanAsync(oldFile, newFile, patchFile); -await differ.DirtyAsync(oldFile, outputFile, patchFile); -``` - -【效果&注意事项】 -- `optimalLevel: false` 生成更快,适合开发/CI 环境 -- `optimalLevel: true` 补丁体积更小,适合生产环境 -- 生成和消费两侧都需要能识别 Deflate 格式(`0x01`) - -#### 场景 3:StreamingHdiffDiffer 参数调优 - -【场景说明】大型单文件(200MB+)的差分,调整窗口预算避免内存溢出。 - -【示例代码】 - -```csharp -using GeneralUpdate.Differential.Abstractions; -using GeneralUpdate.Differential.Differ; - -// 大文件场景:增大窗口,减小块大小以获得更精细匹配 -var differ = new StreamingHdiffDiffer( - compressionProvider: new DeflateCompressionProvider(optimalLevel: true), - blockSize: 32 * 1024, // 32 KB 块,更密集的哈希索引 - maxWindowSize: 256 * 1024 * 1024); // 256 MB,允许读入更大文件 - -await differ.CleanAsync(oldLargeFile, newLargeFile, patchFile); -``` - -【效果&注意事项】 -- `blockSize` 越小,哈希索引越密集,匹配更精确但内存消耗更大 -- `maxWindowSize` 决定能参与计算的最大数据量,超出部分不参与匹配 -- 超大文件建议先在业务侧压测补丁体积和应用还原结果 - ---- - -## 5. 常规使用示例 - -### 5.1 快速入门示例(最简 demo) - -```csharp -using GeneralUpdate.Differential.Abstractions; -using GeneralUpdate.Differential.Differ; - -IBinaryDiffer differ = new BsdiffDiffer(); - -var oldFile = @"D:\releases\1.0.0\app.dll"; -var newFile = @"D:\releases\1.0.1\app.dll"; -var patchFile = @"D:\patches\app.dll.patch"; -var outputFile = @"D:\restore\app.dll"; - -// 生成补丁:oldFile + newFile → patchFile -await differ.CleanAsync(oldFile, newFile, patchFile); - -// 应用补丁:oldFile + patchFile → outputFile -await differ.DirtyAsync(oldFile, outputFile, patchFile); - -// 验证还原结果 -var newHash = ComputeSha256(newFile); -var outputHash = ComputeSha256(outputFile); -Console.WriteLine(newHash == outputHash ? "Patch verified." : "MISMATCH!"); -``` - -### 5.2 基础参数组合示例 - -```csharp -using GeneralUpdate.Differential.Abstractions; -using GeneralUpdate.Differential.Differ; - -// 方案 A:经典 BSDIFF + BZip2 → 补丁体积最小 -var differA = new BsdiffDiffer(); - -// 方案 B:经典 BSDIFF + Deflate → 补丁体积小 + 应用更快 -var differB = new BsdiffDiffer(new DeflateCompressionProvider(optimalLevel: true)); - -// 方案 C:Streaming HDiff + Deflate → 生成快 + 应用最快 -var differC = new StreamingHdiffDiffer( - new DeflateCompressionProvider(optimalLevel: true), - blockSize: 64 * 1024, - maxWindowSize: 128 * 1024 * 1024); - -// 对同一组文件测试三种方案,选择最优 -foreach (var differ in new IBinaryDiffer[] { differA, differB, differC }) -{ - var sw = Stopwatch.StartNew(); - await differ.CleanAsync(oldFile, newFile, patchFile); - sw.Stop(); - - var patchSize = new FileInfo(patchFile).Length; - Console.WriteLine($"{differ.GetType().Name}: {sw.ElapsedMilliseconds}ms, {patchSize} bytes"); -} -``` - -### 5.3 真实业务落地示例(通过 Core DiffPipeline 使用) - -大多数情况下不直接使用 Differential,而是通过 Core 的 `DiffPipeline` 做目录级差分: - -```csharp -using GeneralUpdate.Core; -using GeneralUpdate.Core.Pipeline; -using GeneralUpdate.Core.Models; -using GeneralUpdate.Differential.Differ; - -// 构建端:对比新旧版本目录,生成补丁 -var pipeline = new DiffPipelineBuilder() - .UseDiffer(new StreamingHdiffDiffer()) - .WithParallelism(8) // CI 构建机,高并行度 - .WithStopOnFirstError(true) - .WithProgress(new Progress(p => - { - Console.WriteLine($"[Build] {p.Completed}/{p.Total} {p.CurrentFile}"); - })) - .Build(); - -await pipeline.CleanAsync( - @"D:\builds\v1.0.0", - @"D:\builds\v1.0.1", - @"D:\patches\v1.0.0-to-v1.0.1"); - -// 客户端:通过 GeneralUpdateBootstrap 使用 -await new GeneralUpdateBootstrap() - .SetSource( - updateUrl: "https://update.mycompany.com/api/upgrade/verification", - appSecretKey: "prod-key") - .SetOption(Option.AppType, AppType.Client) - .SetOption(Option.PatchEnabled, true) - .SetOption(Option.DiffMode, DiffMode.Parallel) - .SetOption(Option.MaxConcurrency, 4) - .UseDiffPipeline(builder => builder - .UseDiffer(new StreamingHdiffDiffer()) - .WithParallelism(4)) - .LaunchAsync(); -``` - ---- - -## 6. 算法选择指南 - -### Clean 与 Dirty 语义 - -| 术语 | 方法 | 输入 | 输出 | 常用位置 | -| --- | --- | --- | --- | --- | -| Clean | `CleanAsync` | 旧文件、新文件、补丁输出路径 | `.patch` 补丁文件 | 构建/发布阶段 | -| Dirty | `DirtyAsync` | 旧文件、输出新文件路径、补丁路径 | 还原后的新文件 | 客户端升级阶段 | - -### 算法对比 - -| 对比维度 | `BsdiffDiffer` | `StreamingHdiffDiffer` | -| --- | --- | --- | -| 核心思路 | 经典 BSDIFF 4.0,后缀排序 + 最长匹配 | 块级 FNV-1a 哈希索引 + 字节级扩展匹配 | -| 默认压缩 | BZip2 (0x00) | Deflate (0x01) | -| 补丁应用 | 自实现 BSDIFF Dirty | 委托给 `BsdiffDiffer`(BSDIF 兼容) | -| 补丁体积 | 更稳定,通常更小 | 受文件变化分布影响大,块命中差时可能接近原文件大小 | -| 客户端应用速度 | BZip2 解压较慢 | Deflate 解压更快(约 1.5-5x) | -| 生成内存 | 全量读入新旧文件 | 按 `maxWindowSize` 预算读入 | -| 兼容性 | 兼容旧 BSDIFF/BZip2 补丁 | 适合新项目 | - -### 场景推荐 - -| 场景 | 推荐 | -| --- | --- | -| 补丁体积优先 | `BsdiffDiffer` + BZip2 | -| 客户端应用速度优先 | `StreamingHdiffDiffer`(默认 Deflate) | -| 兼容旧补丁格式 | `BsdiffDiffer`(32 字节旧头自动按 BZip2 处理) | -| 大文件(>500MB) | 先压测,可能需要 `StreamingHdiffDiffer` + 调大 `maxWindowSize` | -| 目录级批量差分 | 通过 Core `DiffPipeline`,配合 `WithParallelism` 提升吞吐 | -| 新项目 | 先用默认配置跑基准,再根据体积和速度需求调整 | - -### 并发模型 - -- 单个 `IBinaryDiffer` 实例线程安全(当 `ICompressionProvider` 线程安全时) -- 真正的多线程差分在 Core `DiffPipeline` 层,通过 `WithParallelism(n)` 控制 -- BZip2 / Deflate 内置 provider 均为每次调用创建新流,支持并发 - ---- - -## 相关资源 - -- [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) -- [Samples 差分示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/DifferentialSample.cs) -- [Core DiffPipeline 文档](GeneralUpdate.Core.md) -- [Tools 打包指南](../guide/Packaging.md) From 01ad34f63f3f72c1a36913e1aa03fea92b61b0b6 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Thu, 4 Jun 2026 11:30:07 +0800 Subject: [PATCH 3/4] fix: remove
tags in SetSource table row causing MDX compilation error The
HTML tags inside markdown table cells were being interpreted as JSX elements by the MDX parser (mdast-util-mdx-jsx), causing the Docusaurus website build to fail. Replaced with comma-separated format. Co-Authored-By: Claude Opus 4.8 --- website/docs/doc/GeneralUpdate.Core.md | 2 +- .../current/doc/GeneralUpdate.Core.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/doc/GeneralUpdate.Core.md b/website/docs/doc/GeneralUpdate.Core.md index fd3f600..b446ac2 100644 --- a/website/docs/doc/GeneralUpdate.Core.md +++ b/website/docs/doc/GeneralUpdate.Core.md @@ -143,7 +143,7 @@ sidebar_position: 5 | `Cancel()` | 无 | `void` | UI 中提供"取消更新"按钮 | 触发内部 `CancellationTokenSource` | | `SetConfig(UpdateRequest)` | `configInfo` — 更新配置对象 | `GeneralUpdateBootstrap` | 主程序内显式配置更新参数 | 会调用 `Validate()` 检查关键字段 | | `SetConfig(string)` | `filePath` — JSON 配置文件路径 | `GeneralUpdateBootstrap` | 从文件读取更新配置 | 支持相对路径和绝对路径;UTF-8 JSON 格式 | -| `SetSource(...)` | `updateUrl`(更新检查 API 地址)
`appSecretKey`(应用密钥)
`reportUrl?`(可选上报地址)
`scheme?`(可选认证方案,如 `"Bearer"`)
`token?`(可选认证令牌)
`authScheme?`(可选认证方案枚举,默认 `Hmac`)
`basicUsername?`(可选 Basic 认证用户名)
`basicPassword?`(可选 Basic 认证密码)
`installPath?`(可选安装路径) | `GeneralUpdateBootstrap` | 轻配置入口,配合 manifest 使用 | 只提供服务端入口和密钥,身份信息由 manifest 补齐;支持 HMAC / Bearer / Basic 多种认证方式 | +| `SetSource(...)` | `updateUrl`, `appSecretKey`, `reportUrl?`, `scheme?`, `token?`, `authScheme?`, `basicUsername?`, `basicPassword?`, `installPath?` | `GeneralUpdateBootstrap` | 轻配置入口,配合 manifest 使用 | 只提供服务端入口和密钥,身份信息由 manifest 补齐;支持 HMAC / Bearer / Basic 多种认证方式 | | `SetOption(Option, T)` | `option` — 选项键, `value` — 选项值 | `GeneralUpdateBootstrap` | 设置运行时选项 | 传入 `null` 给可空选项会移除当前设置 | | `UseDiffPipeline(Action)` | `configure` — 差分管道配置委托 | `GeneralUpdateBootstrap` | 替换或调整差分补丁管道 | 未调用时使用默认配置 | | `AddListenerUpdateInfo(...)` | `EventHandler` | `GeneralUpdateBootstrap` | 接收服务端版本信息 | 无更新时也会触发(`Info.Code = 404`) | diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md index 12964f4..e5b8078 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md @@ -138,7 +138,7 @@ sidebar_position: 5 | `Cancel()` | None | `void` | UI "Cancel Update" button | Triggers internal `CancellationTokenSource` | | `SetConfig(UpdateRequest)` | `configInfo` | `GeneralUpdateBootstrap` | Explicit update configuration | Calls `Validate()` on key fields | | `SetConfig(string)` | `filePath` — JSON config file path | `GeneralUpdateBootstrap` | Read config from file | Supports relative/absolute paths; UTF-8 JSON | -| `SetSource(...)` | `updateUrl` (version check API URL)
`appSecretKey` (app secret key)
`reportUrl?` (optional report URL)
`scheme?` (optional auth scheme, e.g., `"Bearer"`)
`token?` (optional auth token)
`authScheme?` (optional auth scheme enum, default `Hmac`)
`basicUsername?` (optional Basic auth username)
`basicPassword?` (optional Basic auth password)
`installPath?` (optional install path) | `GeneralUpdateBootstrap` | Lightweight entry with manifest | Identity info filled by manifest; supports HMAC / Bearer / Basic auth methods | +| `SetSource(...)` | `updateUrl`, `appSecretKey`, `reportUrl?`, `scheme?`, `token?`, `authScheme?`, `basicUsername?`, `basicPassword?`, `installPath?` | `GeneralUpdateBootstrap` | Lightweight entry with manifest | Identity info filled by manifest; supports HMAC / Bearer / Basic auth methods | | `SetOption(Option, T)` | `option` — key, `value` — value | `GeneralUpdateBootstrap` | Set runtime options | Pass `null` to reset nullable options | | `UseDiffPipeline(Action)` | `configure` — delegate | `GeneralUpdateBootstrap` | Replace or tune diff pipeline | Default used if not called | | `AddListenerUpdateInfo(...)` | `EventHandler` | `GeneralUpdateBootstrap` | Receive server version info | Also fires when no update available | From 990947699c1bcb16a7a64ef99a7d5cbf44b31bcc Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Thu, 4 Jun 2026 11:34:37 +0800 Subject: [PATCH 4/4] fix: remove .md suffix from all internal Docusaurus links Docusaurus resolves internal doc links without the .md extension. Links with .md suffix break when the target document's i18n override is removed (Docusaurus falls back to docs/ but can't match the .md suffix). This was pre-existing but masked by zh-Hans i18n overrides for Core and Differential docs. Co-Authored-By: Claude Opus 4.8 --- website/docs/doc/GeneralUpdate.Bowl.md | 2 +- website/docs/doc/GeneralUpdate.Core.md | 4 ++-- .../docs/doc/GeneralUpdate.Differential.md | 4 ++-- website/docs/doc/GeneralUpdate.Drivelution.md | 2 +- website/docs/doc/GeneralUpdate.Extension.md | 2 +- website/docs/doc/UpgradeHub.md | 2 +- website/docs/guide/FAQ.md | 8 +++---- website/docs/guide/Packaging.md | 8 +++---- website/docs/quickstart/Beginner cookbook.md | 22 +++++++++---------- .../quickstart/GeneralUpdate.PacketTool.md | 12 +++++----- .../current/doc/GeneralUpdate.Bowl.md | 2 +- .../current/doc/GeneralUpdate.Core.md | 4 ++-- .../current/doc/GeneralUpdate.Differential.md | 4 ++-- .../current/doc/GeneralUpdate.Drivelution.md | 2 +- .../current/doc/GeneralUpdate.Extension.md | 2 +- .../current/guide/FAQ.md | 8 +++---- .../current/guide/Packaging.md | 8 +++---- .../current/quickstart/Beginner cookbook.md | 22 +++++++++---------- .../quickstart/GeneralUpdate.PacketTool.md | 12 +++++----- .../current/doc/GeneralUpdate.Bowl.md | 2 +- .../current/doc/GeneralUpdate.Drivelution.md | 2 +- .../current/doc/GeneralUpdate.Extension.md | 2 +- .../current/guide/FAQ.md | 8 +++---- .../current/quickstart/Beginner cookbook.md | 22 +++++++++---------- .../quickstart/GeneralUpdate.PacketTool.md | 12 +++++----- 25 files changed, 89 insertions(+), 89 deletions(-) diff --git a/website/docs/doc/GeneralUpdate.Bowl.md b/website/docs/doc/GeneralUpdate.Bowl.md index b15cfd1..d490a12 100644 --- a/website/docs/doc/GeneralUpdate.Bowl.md +++ b/website/docs/doc/GeneralUpdate.Bowl.md @@ -417,4 +417,4 @@ MyApp/ - [Bowl 示例代码](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Bowl) - [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) -- [Dump 指南](../guide/Dump.md) +- [Dump 指南](../guide/Dump) diff --git a/website/docs/doc/GeneralUpdate.Core.md b/website/docs/doc/GeneralUpdate.Core.md index b446ac2..ac3ea6c 100644 --- a/website/docs/doc/GeneralUpdate.Core.md +++ b/website/docs/doc/GeneralUpdate.Core.md @@ -1010,5 +1010,5 @@ var failedVersion = Environments.Get("UpgradeFail"); - [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) - [Samples 示例代码](https://github.com/GeneralLibrary/GeneralUpdate-Samples) - [GeneralUpdate.Tools](https://github.com/GeneralLibrary/GeneralUpdate.Tools) -- [入门实战手册](../quickstart/Beginner%20cookbook.md) -- [组件文档](./GeneralUpdate.Core.md) +- [入门实战手册](../quickstart/Beginner%20cookbook) +- [组件文档](./GeneralUpdate.Core) diff --git a/website/docs/doc/GeneralUpdate.Differential.md b/website/docs/doc/GeneralUpdate.Differential.md index c78d73f..7613c1f 100644 --- a/website/docs/doc/GeneralUpdate.Differential.md +++ b/website/docs/doc/GeneralUpdate.Differential.md @@ -363,5 +363,5 @@ await new GeneralUpdateBootstrap() - [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) - [Samples 差分示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/DifferentialSample.cs) -- [Core DiffPipeline 文档](GeneralUpdate.Core.md) -- [Tools 打包指南](../guide/Packaging.md) +- [Core DiffPipeline 文档](GeneralUpdate.Core) +- [Tools 打包指南](../guide/Packaging) diff --git a/website/docs/doc/GeneralUpdate.Drivelution.md b/website/docs/doc/GeneralUpdate.Drivelution.md index 12b82aa..bf93c9f 100644 --- a/website/docs/doc/GeneralUpdate.Drivelution.md +++ b/website/docs/doc/GeneralUpdate.Drivelution.md @@ -554,4 +554,4 @@ GeneralTracer.SetTracingEnabled(true); - [驱动更新示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/ImDiskQuickInstallSample.cs) - [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) -- [GeneralUpdate.Drivelution 组件文档](../doc/GeneralUpdate.Drivelution.md) +- [GeneralUpdate.Drivelution 组件文档](../doc/GeneralUpdate.Drivelution) diff --git a/website/docs/doc/GeneralUpdate.Extension.md b/website/docs/doc/GeneralUpdate.Extension.md index 569f5d7..81e048a 100644 --- a/website/docs/doc/GeneralUpdate.Extension.md +++ b/website/docs/doc/GeneralUpdate.Extension.md @@ -676,4 +676,4 @@ report-extension_1.0.0.zip - [扩展管理示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/ExtensionSample.cs) - [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) -- [打包指南](../guide/Packaging.md) +- [打包指南](../guide/Packaging) diff --git a/website/docs/doc/UpgradeHub.md b/website/docs/doc/UpgradeHub.md index feb255a..f61a8bb 100644 --- a/website/docs/doc/UpgradeHub.md +++ b/website/docs/doc/UpgradeHub.md @@ -363,4 +363,4 @@ await new GeneralClientBootstrap() - **示例代码:** [查看 GitHub 示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/blob/main/src/Push/Program.cs) - **主仓库:** [GeneralUpdate 项目](https://github.com/GeneralLibrary/GeneralUpdate) -- **相关组件:** [GeneralUpdate.Core](./GeneralUpdate.Core.md) | [GeneralUpdate.Core](./GeneralUpdate.Core.md) +- **相关组件:** [GeneralUpdate.Core](./GeneralUpdate.Core) | [GeneralUpdate.Core](./GeneralUpdate.Core) diff --git a/website/docs/guide/FAQ.md b/website/docs/guide/FAQ.md index 096cdf3..3e60d12 100644 --- a/website/docs/guide/FAQ.md +++ b/website/docs/guide/FAQ.md @@ -332,7 +332,7 @@ await new GeneralClientBootstrap() ## 相关资源 -- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core.md)** — 核心更新引擎 -- **[入门实战手册](../quickstart/Beginner%20cookbook.md)** — 从零跑通更新闭环 -- **[GeneralUpdate.Tools](../quickstart/GeneralUpdate.PacketTool.md)** — 打包工具 -- **[GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md)** — 崩溃监控与回滚 +- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core)** — 核心更新引擎 +- **[入门实战手册](../quickstart/Beginner%20cookbook)** — 从零跑通更新闭环 +- **[GeneralUpdate.Tools](../quickstart/GeneralUpdate.PacketTool)** — 打包工具 +- **[GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl)** — 崩溃监控与回滚 diff --git a/website/docs/guide/Packaging.md b/website/docs/guide/Packaging.md index 0329db7..e8263bf 100644 --- a/website/docs/guide/Packaging.md +++ b/website/docs/guide/Packaging.md @@ -120,7 +120,7 @@ await new GeneralUpdateBootstrap() **避免 C 盘权限问题:** - 建议默认安装到 `%LOCALAPPDATA%` 而非 `C:\Program Files\` -- 如必须安装在 C 盘,参考 [权限指南](./Permission.md) 配置注册表降权 +- 如必须安装在 C 盘,参考 [权限指南](./Permission) 配置注册表降权 **Windows 发布命令:** @@ -280,6 +280,6 @@ jobs: ## 相关资源 - **[GeneralUpdate.Tools](../quickstart/GeneralUpdate.PacketTool)** — 补丁包和配置生成工具 -- **[权限指南](./Permission.md)** — Windows UAC 权限处理 -- **[入门实战手册](../quickstart/Beginner%20cookbook.md)** — 从零跑通更新闭环 -- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core.md)** — 核心更新引擎架构 +- **[权限指南](./Permission)** — Windows UAC 权限处理 +- **[入门实战手册](../quickstart/Beginner%20cookbook)** — 从零跑通更新闭环 +- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core)** — 核心更新引擎架构 diff --git a/website/docs/quickstart/Beginner cookbook.md b/website/docs/quickstart/Beginner cookbook.md index e60f34e..b429b71 100644 --- a/website/docs/quickstart/Beginner cookbook.md +++ b/website/docs/quickstart/Beginner cookbook.md @@ -11,12 +11,12 @@ title: 入门实战手册 | 角色 | 在样例中的位置 | 负责什么 | 深入阅读 | | --- | --- | --- | --- | -| Hub | `src\Hub` | 交互式示例浏览器,通过菜单选择并运行各类更新场景 | [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) | -| Server | `src\Server` | 返回版本信息、接收更新报告、提供补丁下载 | [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) | -| Packet | `src\Server\wwwroot\packages` | 可下载的 `.zip` 更新包和 `versions.json` 元数据 | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md) | -| Tools | GeneralUpdate.Tools 仓库 | 生成补丁包、Hash、OSS 清单、manifest 和仿真报告 | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md) | -| Bowl | Hub Samples 中集成 | 监控进程异常并导出失败信息 | [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md) | -| Differential | Hub Samples + Core 默认集成 | 对 old/new 文件生成差分,并在更新阶段应用 | [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md) | +| Hub | `src\Hub` | 交互式示例浏览器,通过菜单选择并运行各类更新场景 | [GeneralUpdate.Core](../doc/GeneralUpdate.Core) | +| Server | `src\Server` | 返回版本信息、接收更新报告、提供补丁下载 | [GeneralUpdate.Core](../doc/GeneralUpdate.Core) | +| Packet | `src\Server\wwwroot\packages` | 可下载的 `.zip` 更新包和 `versions.json` 元数据 | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool) | +| Tools | GeneralUpdate.Tools 仓库 | 生成补丁包、Hash、OSS 清单、manifest 和仿真报告 | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool) | +| Bowl | Hub Samples 中集成 | 监控进程异常并导出失败信息 | [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl) | +| Differential | Hub Samples + Core 默认集成 | 对 old/new 文件生成差分,并在更新阶段应用 | [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential) | ## Step 1:准备仓库和运行环境 @@ -239,11 +239,11 @@ Hub 提供了 9 个内置样例,覆盖了 GeneralUpdate 的主要使用场景 跑通这条链路后,建议按顺序阅读: -1. [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md):更新策略、事件通知、静默更新、manifest 极简配置。 -2. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md):补丁包、Hash、OSS Config、Simulation。 -3. [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md):差分算法、并行处理和 Clean/Dirty。 -4. [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md):崩溃监控、备份和失败恢复。 -5. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md):补丁包、Hash、OSS Config、Simulation。 +1. [GeneralUpdate.Core](../doc/GeneralUpdate.Core):更新策略、事件通知、静默更新、manifest 极简配置。 +2. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool):补丁包、Hash、OSS Config、Simulation。 +3. [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential):差分算法、并行处理和 Clean/Dirty。 +4. [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl):崩溃监控、备份和失败恢复。 +5. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool):补丁包、Hash、OSS Config、Simulation。 ## Sample UI diff --git a/website/docs/quickstart/GeneralUpdate.PacketTool.md b/website/docs/quickstart/GeneralUpdate.PacketTool.md index c269482..2bd4907 100644 --- a/website/docs/quickstart/GeneralUpdate.PacketTool.md +++ b/website/docs/quickstart/GeneralUpdate.PacketTool.md @@ -144,7 +144,7 @@ dotnet run --project GeneralUpdate.Tools.csproj - Extension Host 调用 `ExtensionManager.QueryRemoteExtensionsAsync(...)` 获取扩展列表 - 安装时下载 ZIP,读取 `manifest.json` 进行兼容性检查和依赖解析 -- 详见 [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension.md) +- 详见 [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension) --- @@ -189,7 +189,7 @@ dotnet run --project GeneralUpdate.Tools.csproj - 将 `oss_config.json` 上传到 OSS bucket 或静态文件服务器 - OSS 客户端读取此文件发现可用版本,下载后校验 Hash -- 详见 [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) OSS 更新策略 +- 详见 [GeneralUpdate.Core](../doc/GeneralUpdate.Core) OSS 更新策略 --- @@ -349,7 +349,7 @@ Generate Sample 额外输出: ## 关联文档 -- [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md):Client/Upgrade 更新主流程 -- [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md):差分算法 Clean/Dirty 模式 -- [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension.md):扩展包安装与版本管理 -- [入门实战手册](./Beginner%20cookbook.md):从零跑通完整更新闭环 +- [GeneralUpdate.Core](../doc/GeneralUpdate.Core):Client/Upgrade 更新主流程 +- [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential):差分算法 Clean/Dirty 模式 +- [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension):扩展包安装与版本管理 +- [入门实战手册](./Beginner%20cookbook):从零跑通完整更新闭环 diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Bowl.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Bowl.md index fda1f9f..ac9e4a3 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Bowl.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Bowl.md @@ -362,4 +362,4 @@ MyApp/ - [Bowl Sample Code](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Bowl) - [GeneralUpdate Repository](https://github.com/GeneralLibrary/GeneralUpdate) -- [Dump Guide](../guide/Dump.md) +- [Dump Guide](../guide/Dump) diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md index e5b8078..3092fa4 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Core.md @@ -667,5 +667,5 @@ GeneralTracer.Dispose(); - [GeneralUpdate Repository](https://github.com/GeneralLibrary/GeneralUpdate) - [Samples Code](https://github.com/GeneralLibrary/GeneralUpdate-Samples) - [GeneralUpdate.Tools](https://github.com/GeneralLibrary/GeneralUpdate.Tools) -- [Beginner Cookbook](../quickstart/Beginner%20cookbook.md) -- [Component docs](./GeneralUpdate.Core.md) +- [Beginner Cookbook](../quickstart/Beginner%20cookbook) +- [Component docs](./GeneralUpdate.Core) diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md index ab1b32f..c423390 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Differential.md @@ -287,5 +287,5 @@ await new GeneralUpdateBootstrap() - [GeneralUpdate Repository](https://github.com/GeneralLibrary/GeneralUpdate) - [Differential Sample](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/DifferentialSample.cs) -- [Core DiffPipeline Docs](GeneralUpdate.Core.md) -- [Packaging Guide](../guide/Packaging.md) +- [Core DiffPipeline Docs](GeneralUpdate.Core) +- [Packaging Guide](../guide/Packaging) diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Drivelution.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Drivelution.md index 09a55a5..7a9444d 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Drivelution.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Drivelution.md @@ -354,4 +354,4 @@ GeneralTracer.SetTracingEnabled(true); // Re-enable for troubleshooting - [Driver Update Sample](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/ImDiskQuickInstallSample.cs) - [GeneralUpdate Repository](https://github.com/GeneralLibrary/GeneralUpdate) -- [GeneralUpdate.Drivelution docs](../doc/GeneralUpdate.Drivelution.md) +- [GeneralUpdate.Drivelution docs](../doc/GeneralUpdate.Drivelution) diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Extension.md b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Extension.md index 899ecfd..e8f87d6 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Extension.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Extension.md @@ -518,4 +518,4 @@ Per-extension setting > Global setting > Default (false) - [Extension Management Sample](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/ExtensionSample.cs) - [GeneralUpdate Repository](https://github.com/GeneralLibrary/GeneralUpdate) -- [Packaging Guide](../guide/Packaging.md) +- [Packaging Guide](../guide/Packaging) diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/guide/FAQ.md b/website/i18n/en/docusaurus-plugin-content-docs/current/guide/FAQ.md index cb1d12b..b4b015a 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/guide/FAQ.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/guide/FAQ.md @@ -279,7 +279,7 @@ Sample projects provide a simple server example. For production, implement your ## Related Resources -- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core.md)** — Core update engine -- **[Beginner Cookbook](../quickstart/Beginner%20cookbook.md)** — Run through the full update loop -- **[GeneralUpdate.Tools](../quickstart/GeneralUpdate.PacketTool.md)** — Packaging tools -- **[GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md)** — Crash monitoring & rollback +- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core)** — Core update engine +- **[Beginner Cookbook](../quickstart/Beginner%20cookbook)** — Run through the full update loop +- **[GeneralUpdate.Tools](../quickstart/GeneralUpdate.PacketTool)** — Packaging tools +- **[GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl)** — Crash monitoring & rollback diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/guide/Packaging.md b/website/i18n/en/docusaurus-plugin-content-docs/current/guide/Packaging.md index ad4fb28..561a1c6 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/guide/Packaging.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/guide/Packaging.md @@ -110,7 +110,7 @@ await new GeneralUpdateBootstrap() - [NSIS Download](https://nsis.sourceforge.io/Download) - Place main app and upgrade assistant in the same directory - Default install to `%LOCALAPPDATA%` to avoid C drive permission issues -- For C drive installs, see [Permission Guide](./Permission.md) +- For C drive installs, see [Permission Guide](./Permission) ```bash dotnet publish src/MyApp/MyApp.csproj -c Release -r win-x64 --self-contained @@ -216,6 +216,6 @@ Before deploying, verify: ## Related Resources - **[GeneralUpdate.Tools](../quickstart/GeneralUpdate.PacketTool)** — Patch and config generation tool -- **[Permission Guide](./Permission.md)** — Windows UAC permission handling -- **[Beginner Cookbook](../quickstart/Beginner%20cookbook.md)** — Complete update walkthrough -- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core.md)** — Core update engine architecture +- **[Permission Guide](./Permission)** — Windows UAC permission handling +- **[Beginner Cookbook](../quickstart/Beginner%20cookbook)** — Complete update walkthrough +- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core)** — Core update engine architecture diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/quickstart/Beginner cookbook.md b/website/i18n/en/docusaurus-plugin-content-docs/current/quickstart/Beginner cookbook.md index aec4cab..33a8cc2 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/quickstart/Beginner cookbook.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/quickstart/Beginner cookbook.md @@ -11,12 +11,12 @@ This cookbook is for first-time GeneralUpdate users. The goal is not to explain | Role | Sample location | Responsibility | Deep dive | | --- | --- | --- | --- | -| Hub | `src\Hub` | Interactive sample browser; run update scenarios via menu | [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) | -| Server | `src\Server` | Returns version metadata, accepts reports, and serves package downloads | [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) | -| Packet | `src\Server\wwwroot\packages` | Downloadable `.zip` packages and `versions.json` metadata | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md) | -| Tools | GeneralUpdate.Tools repository | Generates patch packages, hashes, OSS manifests, and simulation reports | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md) | -| Bowl | Integrated in Hub Samples | Monitors process failures and exports failure data | [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md) | -| Differential | Hub Samples + Core default integration | Generates old/new binary differences and applies them during updates | [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md) | +| Hub | `src\Hub` | Interactive sample browser; run update scenarios via menu | [GeneralUpdate.Core](../doc/GeneralUpdate.Core) | +| Server | `src\Server` | Returns version metadata, accepts reports, and serves package downloads | [GeneralUpdate.Core](../doc/GeneralUpdate.Core) | +| Packet | `src\Server\wwwroot\packages` | Downloadable `.zip` packages and `versions.json` metadata | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool) | +| Tools | GeneralUpdate.Tools repository | Generates patch packages, hashes, OSS manifests, and simulation reports | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool) | +| Bowl | Integrated in Hub Samples | Monitors process failures and exports failure data | [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl) | +| Differential | Hub Samples + Core default integration | Generates old/new binary differences and applies them during updates | [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential) | ## Step 1: Prepare the repository and runtime @@ -239,11 +239,11 @@ Try running each sample and observe the results in the `mock_app` directory. After this flow works, read these pages in order: -1. [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md): update strategies, event notifications, silent updates, and manifest-based minimal configuration. -2. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md): patch packages, Hash, OSS Config, and Simulation. -3. [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md): differential algorithms, parallel processing, and Clean/Dirty. -4. [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md): crash monitoring, backup, and failure recovery. -5. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md): patch packages, Hash, OSS Config, and Simulation. +1. [GeneralUpdate.Core](../doc/GeneralUpdate.Core): update strategies, event notifications, silent updates, and manifest-based minimal configuration. +2. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool): patch packages, Hash, OSS Config, and Simulation. +3. [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential): differential algorithms, parallel processing, and Clean/Dirty. +4. [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl): crash monitoring, backup, and failure recovery. +5. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool): patch packages, Hash, OSS Config, and Simulation. ## Sample UI diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/quickstart/GeneralUpdate.PacketTool.md b/website/i18n/en/docusaurus-plugin-content-docs/current/quickstart/GeneralUpdate.PacketTool.md index 4466b53..1e1fc9d 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/quickstart/GeneralUpdate.PacketTool.md +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/quickstart/GeneralUpdate.PacketTool.md @@ -138,7 +138,7 @@ Example `manifest.json`: - Extension Host calls `ExtensionManager.QueryRemoteExtensionsAsync(...)` to list available extensions - On install, downloads the ZIP, reads `manifest.json` for compatibility checks and dependency resolution -- See [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension.md) for details +- See [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension) for details --- @@ -183,7 +183,7 @@ If you use OSS mode updates (static file server), you need to maintain a `versio - Upload `oss_config.json` to your OSS bucket or static file server - OSS client reads this file to discover versions and validates Hash after download -- See [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) OSS update strategy +- See [GeneralUpdate.Core](../doc/GeneralUpdate.Core) OSS update strategy --- @@ -338,7 +338,7 @@ This sequence chains the six modules into a complete release pipeline: ## Related docs -- [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md): Client/Upgrade main update flow -- [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md): Differential algorithm Clean/Dirty modes -- [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension.md): Extension install and version management -- [Beginner cookbook](./Beginner%20cookbook.md): Complete end-to-end update walkthrough +- [GeneralUpdate.Core](../doc/GeneralUpdate.Core): Client/Upgrade main update flow +- [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential): Differential algorithm Clean/Dirty modes +- [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension): Extension install and version management +- [Beginner cookbook](./Beginner%20cookbook): Complete end-to-end update walkthrough diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Bowl.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Bowl.md index b15cfd1..d490a12 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Bowl.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Bowl.md @@ -417,4 +417,4 @@ MyApp/ - [Bowl 示例代码](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Bowl) - [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) -- [Dump 指南](../guide/Dump.md) +- [Dump 指南](../guide/Dump) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Drivelution.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Drivelution.md index 12b82aa..bf93c9f 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Drivelution.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Drivelution.md @@ -554,4 +554,4 @@ GeneralTracer.SetTracingEnabled(true); - [驱动更新示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/ImDiskQuickInstallSample.cs) - [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) -- [GeneralUpdate.Drivelution 组件文档](../doc/GeneralUpdate.Drivelution.md) +- [GeneralUpdate.Drivelution 组件文档](../doc/GeneralUpdate.Drivelution) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Extension.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Extension.md index 569f5d7..81e048a 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Extension.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/doc/GeneralUpdate.Extension.md @@ -676,4 +676,4 @@ report-extension_1.0.0.zip - [扩展管理示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/tree/main/src/Hub/Samples/ExtensionSample.cs) - [GeneralUpdate 仓库](https://github.com/GeneralLibrary/GeneralUpdate) -- [打包指南](../guide/Packaging.md) +- [打包指南](../guide/Packaging) diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guide/FAQ.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guide/FAQ.md index 096cdf3..3e60d12 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guide/FAQ.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guide/FAQ.md @@ -332,7 +332,7 @@ await new GeneralClientBootstrap() ## 相关资源 -- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core.md)** — 核心更新引擎 -- **[入门实战手册](../quickstart/Beginner%20cookbook.md)** — 从零跑通更新闭环 -- **[GeneralUpdate.Tools](../quickstart/GeneralUpdate.PacketTool.md)** — 打包工具 -- **[GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md)** — 崩溃监控与回滚 +- **[GeneralUpdate.Core](../doc/GeneralUpdate.Core)** — 核心更新引擎 +- **[入门实战手册](../quickstart/Beginner%20cookbook)** — 从零跑通更新闭环 +- **[GeneralUpdate.Tools](../quickstart/GeneralUpdate.PacketTool)** — 打包工具 +- **[GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl)** — 崩溃监控与回滚 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quickstart/Beginner cookbook.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quickstart/Beginner cookbook.md index e60f34e..b429b71 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quickstart/Beginner cookbook.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quickstart/Beginner cookbook.md @@ -11,12 +11,12 @@ title: 入门实战手册 | 角色 | 在样例中的位置 | 负责什么 | 深入阅读 | | --- | --- | --- | --- | -| Hub | `src\Hub` | 交互式示例浏览器,通过菜单选择并运行各类更新场景 | [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) | -| Server | `src\Server` | 返回版本信息、接收更新报告、提供补丁下载 | [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) | -| Packet | `src\Server\wwwroot\packages` | 可下载的 `.zip` 更新包和 `versions.json` 元数据 | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md) | -| Tools | GeneralUpdate.Tools 仓库 | 生成补丁包、Hash、OSS 清单、manifest 和仿真报告 | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md) | -| Bowl | Hub Samples 中集成 | 监控进程异常并导出失败信息 | [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md) | -| Differential | Hub Samples + Core 默认集成 | 对 old/new 文件生成差分,并在更新阶段应用 | [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md) | +| Hub | `src\Hub` | 交互式示例浏览器,通过菜单选择并运行各类更新场景 | [GeneralUpdate.Core](../doc/GeneralUpdate.Core) | +| Server | `src\Server` | 返回版本信息、接收更新报告、提供补丁下载 | [GeneralUpdate.Core](../doc/GeneralUpdate.Core) | +| Packet | `src\Server\wwwroot\packages` | 可下载的 `.zip` 更新包和 `versions.json` 元数据 | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool) | +| Tools | GeneralUpdate.Tools 仓库 | 生成补丁包、Hash、OSS 清单、manifest 和仿真报告 | [GeneralUpdate.Tools](./GeneralUpdate.PacketTool) | +| Bowl | Hub Samples 中集成 | 监控进程异常并导出失败信息 | [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl) | +| Differential | Hub Samples + Core 默认集成 | 对 old/new 文件生成差分,并在更新阶段应用 | [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential) | ## Step 1:准备仓库和运行环境 @@ -239,11 +239,11 @@ Hub 提供了 9 个内置样例,覆盖了 GeneralUpdate 的主要使用场景 跑通这条链路后,建议按顺序阅读: -1. [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md):更新策略、事件通知、静默更新、manifest 极简配置。 -2. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md):补丁包、Hash、OSS Config、Simulation。 -3. [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md):差分算法、并行处理和 Clean/Dirty。 -4. [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl.md):崩溃监控、备份和失败恢复。 -5. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool.md):补丁包、Hash、OSS Config、Simulation。 +1. [GeneralUpdate.Core](../doc/GeneralUpdate.Core):更新策略、事件通知、静默更新、manifest 极简配置。 +2. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool):补丁包、Hash、OSS Config、Simulation。 +3. [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential):差分算法、并行处理和 Clean/Dirty。 +4. [GeneralUpdate.Bowl](../doc/GeneralUpdate.Bowl):崩溃监控、备份和失败恢复。 +5. [GeneralUpdate.Tools](./GeneralUpdate.PacketTool):补丁包、Hash、OSS Config、Simulation。 ## Sample UI diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quickstart/GeneralUpdate.PacketTool.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quickstart/GeneralUpdate.PacketTool.md index c269482..2bd4907 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quickstart/GeneralUpdate.PacketTool.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/quickstart/GeneralUpdate.PacketTool.md @@ -144,7 +144,7 @@ dotnet run --project GeneralUpdate.Tools.csproj - Extension Host 调用 `ExtensionManager.QueryRemoteExtensionsAsync(...)` 获取扩展列表 - 安装时下载 ZIP,读取 `manifest.json` 进行兼容性检查和依赖解析 -- 详见 [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension.md) +- 详见 [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension) --- @@ -189,7 +189,7 @@ dotnet run --project GeneralUpdate.Tools.csproj - 将 `oss_config.json` 上传到 OSS bucket 或静态文件服务器 - OSS 客户端读取此文件发现可用版本,下载后校验 Hash -- 详见 [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md) OSS 更新策略 +- 详见 [GeneralUpdate.Core](../doc/GeneralUpdate.Core) OSS 更新策略 --- @@ -349,7 +349,7 @@ Generate Sample 额外输出: ## 关联文档 -- [GeneralUpdate.Core](../doc/GeneralUpdate.Core.md):Client/Upgrade 更新主流程 -- [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential.md):差分算法 Clean/Dirty 模式 -- [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension.md):扩展包安装与版本管理 -- [入门实战手册](./Beginner%20cookbook.md):从零跑通完整更新闭环 +- [GeneralUpdate.Core](../doc/GeneralUpdate.Core):Client/Upgrade 更新主流程 +- [GeneralUpdate.Differential](../doc/GeneralUpdate.Differential):差分算法 Clean/Dirty 模式 +- [GeneralUpdate.Extension](../doc/GeneralUpdate.Extension):扩展包安装与版本管理 +- [入门实战手册](./Beginner%20cookbook):从零跑通完整更新闭环