diff --git a/src/Bowl/BowlSample/BowlSample.csproj b/src/Bowl/BowlSample/BowlSample.csproj index 455a6c2..80bc0db 100644 --- a/src/Bowl/BowlSample/BowlSample.csproj +++ b/src/Bowl/BowlSample/BowlSample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Client/ClientSample.csproj b/src/Client/ClientSample.csproj index cccc8b0..0cd0e4c 100644 --- a/src/Client/ClientSample.csproj +++ b/src/Client/ClientSample.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Client/Program.cs b/src/Client/Program.cs index 2166a2c..321e3b9 100644 --- a/src/Client/Program.cs +++ b/src/Client/Program.cs @@ -11,13 +11,13 @@ Console.WriteLine("当前运行目录:" + Thread.GetDomain().BaseDirectory); var configinfo = new Configinfo { - //configinfo.UpdateLogUrl = "https://www.baidu.com"; + //UpdateLogUrl = "https://www.baidu.com", ReportUrl = "http://127.0.0.1:5000/Upgrade/Report", UpdateUrl = "http://127.0.0.1:5000/Upgrade/Verification", AppName = "UpgradeSample.exe", MainAppName = "ClientSample.exe", InstallPath = Thread.GetDomain().BaseDirectory, - //configinfo.Bowl = "Generalupdate.CatBowl.exe"; + //Bowl = "Generalupdate.CatBowl.exe", //当前客户端的版本号 ClientVersion = "1.0.0.0", //当前升级端的版本号 @@ -28,11 +28,11 @@ AppSecretKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6", //BlackFiles = new List { "123.exe" }, //BlackFormats = new List { "123.dll" }, - //BlackDirectories = new List { "123" }, + //SkipDirectorys = new List { "logs" }, //Scheme = "Bearer", //Token = "..." }; - _ = await new GeneralClientBootstrap() //单个或多个更新包下载通知事件 + _ = await new GeneralClientBootstrap() //单个或多个更新包下载速度、剩余下载事件、当前下载版本信息通知事件 .AddListenerMultiDownloadStatistics(OnMultiDownloadStatistics) //单个或多个更新包下载完成 @@ -43,6 +43,11 @@ .AddListenerMultiDownloadError(OnMultiDownloadError) //整个更新过程出现的任何问题都会通过这个事件通知 .AddListenerException(OnException) + //服务端返回更新信息后的通知(可用于显示更新日志、版本信息等) + .AddListenerUpdateInfo(OnUpdateInfo) + //更新预检回调:返回 true 跳过本次更新,返回 false 继续自动更新 + //(强制更新版本会忽略此回调) + .AddListenerUpdatePrecheck(OnUpdatePrecheck) .SetConfig(configinfo) .Option(UpdateOption.DownloadTimeOut, 60) .Option(UpdateOption.Encoding, Encoding.Default) @@ -58,7 +63,7 @@ void OnMultiDownloadError(object arg1, MultiDownloadErrorEventArgs arg2) { var version = arg2.Version as VersionInfo; - Console.WriteLine($"{version.Version} {arg2.Exception}"); + Console.WriteLine($"{version?.Version} {arg2.Exception}"); } void OnMultiAllDownloadCompleted(object arg1, MultiAllDownloadCompletedEventArgs arg2) @@ -69,17 +74,31 @@ void OnMultiAllDownloadCompleted(object arg1, MultiAllDownloadCompletedEventArgs void OnMultiDownloadCompleted(object arg1, MultiDownloadCompletedEventArgs arg2) { var version = arg2.Version as VersionInfo; - Console.WriteLine(arg2.IsComplated ? $"当前下载版本:{version.Version}, 下载完成!" : $"当前下载版本:{version.Version}, 下载失败!"); + Console.WriteLine(arg2.IsComplated ? $"当前下载版本:{version?.Version}, 下载完成!" : $"当前下载版本:{version?.Version}, 下载失败!"); } void OnMultiDownloadStatistics(object arg1, MultiDownloadStatisticsEventArgs arg2) { var version = arg2.Version as VersionInfo; Console.WriteLine( - $"当前下载版本:{version.Version},下载速度:{arg2.Speed},剩余下载时间:{arg2.Remaining},已下载大小:{arg2.BytesReceived},总大小:{arg2.TotalBytesToReceive}, 进度百分比:{arg2.ProgressPercentage}%"); + $"当前下载版本:{version?.Version},下载速度:{arg2.Speed},剩余下载时间:{arg2.Remaining},已下载大小:{arg2.BytesReceived},总大小:{arg2.TotalBytesToReceive}, 进度百分比:{arg2.ProgressPercentage}%"); } void OnException(object arg1, ExceptionEventArgs arg2) { Console.WriteLine($"{arg2.Exception}"); -} \ No newline at end of file +} + +void OnUpdateInfo(object arg1, UpdateInfoEventArgs arg2) +{ + // arg2.Info 包含服务端返回的完整版本信息(VersionRespDTO) + Console.WriteLine($"服务端返回更新信息:Code={arg2.Info.Code}, 版本数量={arg2.Info.Body?.Count ?? 0}"); +} + +bool OnUpdatePrecheck(UpdateInfoEventArgs arg) +{ + // 返回 true:跳过本次更新;返回 false:继续执行自动更新 + // 可在此处添加自定义判断逻辑,例如检查磁盘空间、询问用户是否立即更新等 + Console.WriteLine($"更新预检:发现 {arg.Info.Body?.Count ?? 0} 个可用版本,继续更新..."); + return false; // false = 继续更新 +} diff --git a/src/Compress/CompressSample/CompressSample.csproj b/src/Compress/CompressSample/CompressSample.csproj index 256e46a..f7d91bc 100644 --- a/src/Compress/CompressSample/CompressSample.csproj +++ b/src/Compress/CompressSample/CompressSample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Diff/DiffSample.csproj b/src/Diff/DiffSample.csproj index b3b222c..f7d91bc 100644 --- a/src/Diff/DiffSample.csproj +++ b/src/Diff/DiffSample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Diff/Program.cs b/src/Diff/Program.cs index 9138bcb..75a517a 100644 --- a/src/Diff/Program.cs +++ b/src/Diff/Program.cs @@ -1,8 +1,16 @@ using GeneralUpdate.Differential; +using GeneralUpdate.Differential.Matchers; + +// Binary differential update sample: +// - Clean: generate a patch from source (old) vs target (new) +// - Dirty: apply the patch to the app directory var source = @"D:\packet\app"; var target = @"D:\packet\release"; -var patch = @"D:\packet\patch"; +var patch = @"D:\packet\patch"; + +// Generate binary differential patch (old → new) +await DifferentialCore.Clean(source, target, patch, new DefaultCleanStrategy(new DefaultCleanMatcher())); -await DifferentialCore.Instance?.Clean(source, target, patch); -await DifferentialCore.Instance?.Dirty(source, patch); \ No newline at end of file +// Apply the patch to the running app directory +await DifferentialCore.Dirty(source, patch, new DefaultDirtyStrategy(new DefaultDirtyMatcher())); \ No newline at end of file diff --git a/src/Drivelution/DrivelutionSample/DrivelutionSample.csproj b/src/Drivelution/DrivelutionSample/DrivelutionSample.csproj index 7aa88ef..53b2b0f 100644 --- a/src/Drivelution/DrivelutionSample/DrivelutionSample.csproj +++ b/src/Drivelution/DrivelutionSample/DrivelutionSample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Extension/ExtensionSample/ExtensionSample.csproj b/src/Extension/ExtensionSample/ExtensionSample.csproj index 0f1436d..e0f994a 100644 --- a/src/Extension/ExtensionSample/ExtensionSample.csproj +++ b/src/Extension/ExtensionSample/ExtensionSample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/OSS/OSSClientSample/OSSClientSample.csproj b/src/OSS/OSSClientSample/OSSClientSample.csproj index 19b3805..bf45494 100644 --- a/src/OSS/OSSClientSample/OSSClientSample.csproj +++ b/src/OSS/OSSClientSample/OSSClientSample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/OSS/OSSUpgradeSample/OSSUpgradeSample.csproj b/src/OSS/OSSUpgradeSample/OSSUpgradeSample.csproj index 156bf67..4dca0d4 100644 --- a/src/OSS/OSSUpgradeSample/OSSUpgradeSample.csproj +++ b/src/OSS/OSSUpgradeSample/OSSUpgradeSample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Push/Program.cs b/src/Push/Program.cs index c81e324..b41bccf 100644 --- a/src/Push/Program.cs +++ b/src/Push/Program.cs @@ -1,10 +1,85 @@ -using System.Diagnostics; -using GeneralUpdate.ClientCore.Hubs; +using GeneralUpdate.ClientCore.Hubs; -var hub = new UpgradeHubService("http://localhost:5000/UpgradeHub" - , null,"dfeb5833-975e-4afb-88f1-6278ee9aeff6"); +/* + * PushSample — 演示 UpgradeHubService (SignalR 推送) 的完整用法 + * PushSample — Demonstrates full usage of UpgradeHubService (SignalR push notifications) + * + * 使用前提 / Prerequisites: + * 1. 服务端已运行并注册了 UpgradeHub (MapHub("/UpgradeHub")) + * The server is running and has registered UpgradeHub + * 2. 客户端 appkey 与服务端约定一致 + * The client appkey matches the server-side agreement + */ + +const string HubUrl = "http://localhost:5000/UpgradeHub"; +const string AppKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6"; + +Console.WriteLine($"=== UpgradeHubService 推送示例 / Push Sample ==="); +Console.WriteLine($"连接地址 / Hub URL: {HubUrl}"); +Console.WriteLine($"应用密钥 / App Key: {AppKey}"); +Console.WriteLine(); + +// UpgradeHubService 参数说明: +// url — SignalR Hub 订阅地址 +// token — ID4 认证令牌(可选),例如 Bearer Token +// appkey — 客户端唯一标识,推荐使用 GUID,用于服务端识别推送目标 +var hub = new UpgradeHubService(HubUrl, token: null, appkey: AppKey); + +// 1. 订阅服务端推送的版本更新消息 +// Subscribe to version update messages pushed by the server +// message: 服务端推送的内容,建议使用 JSON 格式的 Packet 对象字符串 hub.AddListenerReceive((message) => { - Debug.WriteLine(message); + Console.WriteLine($"[ReceiveMessage] 收到更新推送 / Received update push:"); + Console.WriteLine($" {message}"); }); -await hub.StartAsync(); \ No newline at end of file + +// 2. 订阅在线 / 离线状态通知 +// Subscribe to online/offline status notifications +hub.AddListenerOnline((info) => +{ + Console.WriteLine($"[Online] 在线状态变化 / Online status change: {info}"); +}); + +// 3. 订阅重连成功通知 +// Subscribe to reconnection notifications +hub.AddListenerReconnected((connectionId) => +{ + Console.WriteLine($"[Reconnected] 已重连 / Reconnected, connectionId={connectionId}"); + return Task.CompletedTask; +}); + +// 4. 订阅连接关闭通知 +// Subscribe to connection closed notifications +hub.AddListenerClosed((exception) => +{ + if (exception is not null) + Console.WriteLine($"[Closed] 连接因异常关闭 / Connection closed due to exception: {exception.Message}"); + else + Console.WriteLine("[Closed] 连接已正常关闭 / Connection closed normally"); + return Task.CompletedTask; +}); + +try +{ + // 5. 建立 SignalR 连接,启动推送监听 + // Establish SignalR connection and start listening for push notifications + Console.WriteLine("正在连接 Hub... / Connecting to Hub..."); + await hub.StartAsync(); + Console.WriteLine("已成功连接 Hub,等待服务端推送... / Connected. Waiting for server push..."); + Console.WriteLine("按 Enter 键断开连接并退出 / Press Enter to disconnect and exit"); + Console.ReadLine(); +} +finally +{ + // 6. 先暂停连接(可在应用进入后台时调用) + // First stop the connection gracefully (call when the app enters background) + Console.WriteLine("正在停止连接... / Stopping connection..."); + await hub.StopAsync(); + + // 7. 彻底释放 Hub 实例(停止后不可再复用) + // Fully dispose the Hub instance (cannot be reused after disposal) + Console.WriteLine("正在释放资源... / Disposing resources..."); + await hub.DisposeAsync(); + Console.WriteLine("资源已释放 / Resources disposed."); +} diff --git a/src/Push/PushSample.csproj b/src/Push/PushSample.csproj index d9fa1aa..7b312ac 100644 --- a/src/Push/PushSample.csproj +++ b/src/Push/PushSample.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Server/DTOs/HttpResponseDTO.cs b/src/Server/DTOs/HttpResponseDTO.cs index b21cb22..cffcc1d 100644 --- a/src/Server/DTOs/HttpResponseDTO.cs +++ b/src/Server/DTOs/HttpResponseDTO.cs @@ -10,7 +10,7 @@ public class HttpResponseDTO /// /// 响应消息内容 /// - public string Message { get; set; } + public string Message { get; set; } = string.Empty; public static HttpResponseDTO Success(string message = "Success") { diff --git a/src/Server/DTOs/VerificationResultDTO.cs b/src/Server/DTOs/VerificationResultDTO.cs index da02dcf..0c129ce 100644 --- a/src/Server/DTOs/VerificationResultDTO.cs +++ b/src/Server/DTOs/VerificationResultDTO.cs @@ -55,7 +55,7 @@ public record VerificationResultDTO /// /// 文件格式 /// - public string Format { get; set; } + public string Format { get; set; } = string.Empty; /// /// 文件大小 diff --git a/src/Server/Program.cs b/src/Server/Program.cs index 61d3a4e..433a8b0 100644 --- a/src/Server/Program.cs +++ b/src/Server/Program.cs @@ -16,7 +16,7 @@ count++; if (count > 2) { - return HttpResponseDTO>.Success(null,"Upgrade completed."); + return HttpResponseDTO>.Success(Enumerable.Empty(), "Upgrade completed."); } var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", "packages", $"{packageName}.zip"); diff --git a/src/Upgrade/Program.cs b/src/Upgrade/Program.cs index 2e63bca..3721282 100644 --- a/src/Upgrade/Program.cs +++ b/src/Upgrade/Program.cs @@ -34,7 +34,7 @@ void OnMultiDownloadError(object arg1, MultiDownloadErrorEventArgs arg2) { var version = arg2.Version as VersionInfo; - Console.WriteLine($"{version.Version} {arg2.Exception}"); + Console.WriteLine($"{version?.Version} {arg2.Exception}"); } void OnMultiAllDownloadCompleted(object arg1, MultiAllDownloadCompletedEventArgs arg2) @@ -45,13 +45,13 @@ void OnMultiAllDownloadCompleted(object arg1, MultiAllDownloadCompletedEventArgs void OnMultiDownloadCompleted(object arg1, MultiDownloadCompletedEventArgs arg2) { var version = arg2.Version as VersionInfo; - Console.WriteLine(arg2.IsComplated ? $"当前下载版本:{version.Version}, 下载完成!" : $"当前下载版本:{version.Version}, 下载失败!"); + Console.WriteLine(arg2.IsComplated ? $"当前下载版本:{version?.Version}, 下载完成!" : $"当前下载版本:{version?.Version}, 下载失败!"); } void OnMultiDownloadStatistics(object arg1, MultiDownloadStatisticsEventArgs arg2) { var version = arg2.Version as VersionInfo; - Console.WriteLine($"当前下载版本:{version.Version},下载速度:{arg2.Speed},剩余下载时间:{arg2.Remaining},已下载大小:{arg2.BytesReceived},总大小:{arg2.TotalBytesToReceive}, 进度百分比:{arg2.ProgressPercentage}%"); + Console.WriteLine($"当前下载版本:{version?.Version},下载速度:{arg2.Speed},剩余下载时间:{arg2.Remaining},已下载大小:{arg2.BytesReceived},总大小:{arg2.TotalBytesToReceive}, 进度百分比:{arg2.ProgressPercentage}%"); } void OnException(object arg1, ExceptionEventArgs arg2) diff --git a/src/Upgrade/UpgradeSample.csproj b/src/Upgrade/UpgradeSample.csproj index 6e7d218..707757e 100644 --- a/src/Upgrade/UpgradeSample.csproj +++ b/src/Upgrade/UpgradeSample.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/dotnet10_scripts/upgrade.cs b/src/dotnet10_scripts/upgrade.cs index 4ae5334..51e2bf7 100644 --- a/src/dotnet10_scripts/upgrade.cs +++ b/src/dotnet10_scripts/upgrade.cs @@ -1,4 +1,4 @@ -#:package GeneralUpdate.Core @9.5.10 +#:package GeneralUpdate.Core @10.4.6 using GeneralUpdate.Common.Download; using GeneralUpdate.Common.Internal; @@ -36,7 +36,7 @@ void OnMultiDownloadError(object arg1, MultiDownloadErrorEventArgs arg2) { var version = arg2.Version as VersionInfo; - Console.WriteLine($"{version.Version} {arg2.Exception}"); + Console.WriteLine($"{version?.Version} {arg2.Exception}"); } void OnMultiAllDownloadCompleted(object arg1, MultiAllDownloadCompletedEventArgs arg2) @@ -47,13 +47,13 @@ void OnMultiAllDownloadCompleted(object arg1, MultiAllDownloadCompletedEventArgs void OnMultiDownloadCompleted(object arg1, MultiDownloadCompletedEventArgs arg2) { var version = arg2.Version as VersionInfo; - Console.WriteLine(arg2.IsComplated ? $"当前下载版本:{version.Version}, 下载完成!" : $"当前下载版本:{version.Version}, 下载失败!"); + Console.WriteLine(arg2.IsComplated ? $"当前下载版本:{version?.Version}, 下载完成!" : $"当前下载版本:{version?.Version}, 下载失败!"); } void OnMultiDownloadStatistics(object arg1, MultiDownloadStatisticsEventArgs arg2) { var version = arg2.Version as VersionInfo; - Console.WriteLine($"当前下载版本:{version.Version},下载速度:{arg2.Speed},剩余下载时间:{arg2.Remaining},已下载大小:{arg2.BytesReceived},总大小:{arg2.TotalBytesToReceive}, 进度百分比:{arg2.ProgressPercentage}%"); + Console.WriteLine($"当前下载版本:{version?.Version},下载速度:{arg2.Speed},剩余下载时间:{arg2.Remaining},已下载大小:{arg2.BytesReceived},总大小:{arg2.TotalBytesToReceive}, 进度百分比:{arg2.ProgressPercentage}%"); } void OnException(object arg1, ExceptionEventArgs arg2) diff --git a/website/docs/doc/UpgradeHub.md b/website/docs/doc/UpgradeHub.md index fb0fb19..b5ac4c5 100644 --- a/website/docs/doc/UpgradeHub.md +++ b/website/docs/doc/UpgradeHub.md @@ -2,101 +2,365 @@ sidebar_position: 7 --- -### Definition +# UpgradeHub -Namespace: GeneralUpdate.ClientCore.Hubs +## 组件概览 -Assembly: GeneralUpdate.ClientCore.dll +**UpgradeHubService** 是基于 SignalR 的实时版本推送服务,集成在 `GeneralUpdate.ClientCore` 中。服务端通过 `UpgradeHub` 向已连接的客户端主动推送版本更新通知,客户端通过 `UpgradeHubService` 订阅并接收消息,支持点对点(一对一)和广播(一对多)两种推送模式。 +**命名空间:** `GeneralUpdate.ClientCore.Hubs` +**程序集:** `GeneralUpdate.ClientCore.dll` +```csharp +public class UpgradeHubService : IUpgradeHubService +``` -The `UpgradeHubService` is a mechanism based on SignalR for pushing updated version information. It supports both one-to-one and one-to-many push notifications. +--- -```c# -public class UpgradeHubService : IUpgradeHubService +## 核心特性 + +### 1. 实时推送 +- 基于 SignalR 长连接,服务端主动推送,无需客户端轮询 +- 支持点对点推送(按 appkey 定向推送) +- 支持广播推送(同时通知所有在线客户端) + +### 2. 自动重连 +- 内置随机退避重连策略(`RandomRetryPolicy`) +- 断线后自动尝试重连,保证连接稳定性 + +### 3. 完整生命周期管理 +- 连接建立(`StartAsync`) +- 优雅停止(`StopAsync`) +- 资源释放(`DisposeAsync`) + +### 4. 多事件订阅 +- 接收版本推送消息(`AddListenerReceive`) +- 在线 / 离线状态通知(`AddListenerOnline`) +- 重连成功通知(`AddListenerReconnected`) +- 连接关闭通知(`AddListenerClosed`) + +--- + +## 快速开始 + +### 安装 + +`UpgradeHubService` 包含在 `GeneralUpdate.ClientCore` 中,无需单独安装: + +```bash +dotnet add package GeneralUpdate.ClientCore ``` +### 初始化与使用 +以下示例展示了如何在客户端应用中接收服务端推送的版本更新消息: -### Example +```csharp +using GeneralUpdate.ClientCore.Hubs; -The following example defines methods that include the use of `VersionHub`. +// 创建 UpgradeHubService 实例 +// url — SignalR Hub 订阅地址 +// token — ID4 认证令牌(可选) +// appkey — 客户端唯一标识(推荐使用 GUID),服务端用此字段定向推送 +var hub = new UpgradeHubService( + "http://localhost:5000/UpgradeHub", + token: null, + appkey: "dfeb5833-975e-4afb-88f1-6278ee9aeff6"); -```c# -//1. General usage -var hub = new UpgradeHubService("http://localhost:5000/UpgradeHub" - , null,"dfeb5833-975e-4afb-88f1-6278ee9aeff6"); - hub.AddListenerReceive((message) => - { - // The message is currently limited to a JSON string of the Packet object - Debug.WriteLine(message); - }); +// 订阅服务端推送的版本更新消息 +// message 为服务端推送的 JSON 字符串(通常为 Packet 对象序列化结果) +hub.AddListenerReceive((message) => +{ + Console.WriteLine($"收到更新推送: {message}"); +}); + +// 订阅在线 / 离线状态通知 +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; +}); + +// 建立 SignalR 连接 await hub.StartAsync(); -//2. In projects with dependency injection capability, you can also use dependency injection, for example: Prism +Console.WriteLine("已连接,等待服务端推送..."); +Console.ReadLine(); + +// 停止连接(可在应用进入后台时调用,保留重连能力) +await hub.StopAsync(); + +// 释放资源(不可再复用) +await hub.DisposeAsync(); +``` + +### 在依赖注入项目中使用(以 Prism 为例) + +```csharp +// 注册服务 protected override void RegisterTypes(IContainerRegistry containerRegistry) { - // Register Services containerRegistry.Register(); } -public MainWindowViewModel(IUpgradeHubService service) +// 在 ViewModel 中使用 +public MainWindowViewModel(IUpgradeHubService hubService) { - service.StartAsync(); - //... + hubService.AddListenerReceive((message) => + { + Console.WriteLine($"收到推送: {message}"); + }); + _ = hubService.StartAsync(); } ``` -**(1) Point-to-point push** +--- + +## 核心 API 参考 + +### 构造函数 + +```csharp +public UpgradeHubService(string url, string? token = null, string? appkey = null) +``` + +**参数:** + +| 参数 | 类型 | 说明 | +| -------- | --------- | ------------------------------------------------------------ | +| `url` | `string` | SignalR Hub 订阅地址,例如 `http://127.0.0.1/UpgradeHub` | +| `token` | `string?` | ID4 认证令牌字符串(可选) | +| `appkey` | `string?` | 客户端唯一标识,推荐使用 GUID,服务端用于定向推送(可选) | + +--- + +### 方法 + +#### AddListenerReceive 方法 + +订阅服务端推送的版本更新消息。 + +```csharp +public void AddListenerReceive(Action receiveMessageCallback) +``` + +**参数:** +- `receiveMessageCallback` — 接收到消息时的回调,`string` 为消息内容(建议为 JSON 格式的 `Packet` 对象) + +--- + +#### AddListenerOnline 方法 + +订阅在线 / 离线状态变化通知。 + +```csharp +public void AddListenerOnline(Action onlineMessageCallback) +``` + +**参数:** +- `onlineMessageCallback` — 状态变化时的回调,`string` 为状态描述 + +--- + +#### AddListenerReconnected 方法 + +订阅断线重连成功通知。 + +```csharp +public void AddListenerReconnected(Func? reconnectedCallback) +``` + +**参数:** +- `reconnectedCallback` — 重连成功后的异步回调,`string?` 为新的 connectionId(可能为 null) + +--- + +#### AddListenerClosed 方法 + +订阅连接关闭通知。 + +```csharp +public void AddListenerClosed(Func closeCallback) +``` + +**参数:** +- `closeCallback` — 连接关闭时的异步回调,`Exception?` 为关闭原因(正常关闭时为 null) + +--- + +#### StartAsync 方法 + +建立 SignalR 连接,开始订阅推送通知。 + +```csharp +public Task StartAsync() +``` + +--- + +#### StopAsync 方法 + +优雅停止连接。当前正在处理的消息会完成,不再接受新消息。适合在应用进入后台或暂停时调用,之后可重新调用 `StartAsync` 恢复连接。 + +```csharp +public Task StopAsync() +``` + +--- + +#### DisposeAsync 方法 + +彻底释放 Hub 实例及所有资源,释放后不可再复用。 + +```csharp +public Task DisposeAsync() +``` + +--- -![](imgs/maui_windows_push_version.png) +## 实际使用示例 -**(2) Push updates to multiple clients at once** +### 示例 1:基本推送订阅 -![push_version_mutil](imgs/push_version_mutil.png) +```csharp +using GeneralUpdate.ClientCore.Hubs; -### Notes +var hub = new UpgradeHubService("http://localhost:5000/UpgradeHub", + appkey: Guid.NewGuid().ToString()); -`UpgradeHubService` provides the functionality to receive server push messages. +hub.AddListenerReceive((message) => +{ + Console.WriteLine($"[更新推送] {message}"); +}); -#### Methods +await hub.StartAsync(); +``` -| Method | Description | -| ------------------------ | ------------------------------------------------------------ | -| AddListenerReceive() | Subscribe to the latest version info pushed by the server in real-time. | -| AddListenerOnline() | Listen for online and offline notifications | -| AddListenerReconnected() | Notification for reconnection | -| AddListenerClosed() | Notification for connection closure | -| StartAsync() | Start the connection | -| StopAsync() | Pause the connection | -| DisposeAsync() | Release the Hub object instance | +### 示例 2:完整生命周期管理 -### 🌼UpgradeHubService() +```csharp +using GeneralUpdate.ClientCore.Hubs; -**Constructor** +var hub = new UpgradeHubService( + "http://localhost:5000/UpgradeHub", + token: null, + appkey: "dfeb5833-975e-4afb-88f1-6278ee9aeff6"); -Initializes the Hub constructor. +hub.AddListenerReceive(msg => Console.WriteLine($"推送: {msg}")); +hub.AddListenerOnline(info => Console.WriteLine($"在线状态: {info}")); +hub.AddListenerReconnected(id => { Console.WriteLine($"重连: {id}"); return Task.CompletedTask; }); +hub.AddListenerClosed(ex => { Console.WriteLine(ex != null ? $"异常: {ex.Message}" : "关闭"); return Task.CompletedTask; }); -```c# -UpgradeHubService(string url, string? token = null, string? appkey = null) +try +{ + await hub.StartAsync(); + Console.ReadLine(); // 等待用户输入 +} +finally +{ + await hub.StopAsync(); + await hub.DisposeAsync(); +} ``` -**Parameters** +### 示例 3:搭配 GeneralClientBootstrap 使用 -```c# -url string The subscription address of the Hub. +实际项目中,`UpgradeHubService` 通常与 `GeneralClientBootstrap` 一起使用。客户端先通过 `GeneralClientBootstrap` 完成常规版本检查和下载,同时用 `UpgradeHubService` 接收服务端的实时推送通知,无需等待下一次轮询即可立即触发更新。 -token string The token string used in the Id4 authentication process. +```csharp +using System.Text; +using GeneralUpdate.ClientCore; +using GeneralUpdate.ClientCore.Hubs; +using GeneralUpdate.Common.Download; +using GeneralUpdate.Common.Internal; +using GeneralUpdate.Common.Internal.Bootstrap; +using GeneralUpdate.Common.Shared.Object; + +// 1. 启动 SignalR 推送监听 +var hub = new UpgradeHubService("http://localhost:5000/UpgradeHub", + appkey: "dfeb5833-975e-4afb-88f1-6278ee9aeff6"); +hub.AddListenerReceive(msg => Console.WriteLine($"实时推送: {msg}")); +await hub.StartAsync(); -appkey string The client key, uniquely identified, recommended value is a Guid, which can be randomly generated. +// 2. 常规更新流程 +var config = new Configinfo +{ + UpdateUrl = "http://127.0.0.1:5000/Upgrade/Verification", + ReportUrl = "http://127.0.0.1:5000/Upgrade/Report", + AppName = "UpgradeSample.exe", + MainAppName = "ClientSample.exe", + ClientVersion = "1.0.0.0", + UpgradeClientVersion = "1.0.0.0", + InstallPath = AppDomain.CurrentDomain.BaseDirectory, + ProductId = "2d974e2a-31e6-4887-9bb1-b4689e98c77a", + AppSecretKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6" +}; + +await new GeneralClientBootstrap() + .AddListenerException((_, e) => Console.WriteLine(e.Exception)) + .SetConfig(config) + .LaunchAsync(); ``` -### Applicable to +--- + +## 注意事项 + +### ⚠️ 重要提示 + +1. **服务端配置** + - 服务端需要安装 `Microsoft.AspNetCore.SignalR` 并注册 `UpgradeHub` + - 在 `Program.cs` 中:`app.MapHub("/UpgradeHub");` + +2. **appkey 唯一性** + - `appkey` 用于服务端定向推送,每个客户端实例应使用唯一值 + - 推荐使用固定 GUID(与 `Configinfo.AppSecretKey` 保持一致) + +3. **StopAsync vs DisposeAsync** + - `StopAsync`:暂停连接,之后可重新调用 `StartAsync` 恢复 + - `DisposeAsync`:彻底释放,不可再复用 + +4. **消息格式** + - `AddListenerReceive` 中接收的消息格式由服务端决定,建议使用 JSON 字符串(`Packet` 对象序列化结果) + +### 💡 最佳实践 + +- 在应用启动时调用 `StartAsync`,在应用关闭时调用 `StopAsync` + `DisposeAsync` +- 使用依赖注入管理 `UpgradeHubService` 实例的生命周期 +- 在 `AddListenerClosed` 回调中记录日志,便于排查连接问题 + +--- + +## 适用平台 + +| 产品 | 版本 | +| -------------- | --------------------- | +| .NET | 5, 6, 7, 8, 9, 10 | +| .NET Framework | 4.6.1 | +| .NET Standard | 2.0 | +| .NET Core | 2.0 | +| ASP.NET | Any | + +--- + +## 相关资源 -| Product | Version | -| -------------- | ------------- | -| .NET | 5, 6, 7, 8, 9, 10 | -| .NET Framework | 4.6.1 | -| .NET Standard | 2.0 | -| .NET Core | 2.0 | -| ASP.NET | Any | \ No newline at end of file +- **示例代码:** [查看 GitHub 示例](https://github.com/GeneralLibrary/GeneralUpdate-Samples/blob/main/src/Push/Program.cs) +- **主仓库:** [GeneralUpdate 项目](https://github.com/GeneralLibrary/GeneralUpdate) +- **相关组件:** [GeneralUpdate.ClientCore](./GeneralUpdate.ClientCore.md) | [GeneralUpdate.Core](./GeneralUpdate.Core.md)