本篇文章将向各位小伙伴介绍GeneralUpdate组件的使用,帮助第一次接触开发者快速上手应用在自己或企业项目中。如果本篇文章对您有帮助,希望帮忙点一下star。感谢各位开发者的支持。
(1)主程序和升级程序之间是否支持相互升级?
答:支持。
(2)是否需要开发者写代码关闭进程的时机或者其他代码?
答:不需要,组件已经将整个更新流程考虑到了。所以除了组件代码以外,不需要开发者额外多写任何辅助代码。
(3)更新程序是否需要和主程序放在同一个目录下?
答:是的,需要。但一定要保持升级程序不能引用主程序的里的任何代码。否则会更新失败。
(4)更新完成之后会删除更新包的补丁文件吗?
答:会的,组件更新完成之后会保证文件列表干净,不会出现冗余文件污染、磁盘空间占用的情况。
(5)可以运用在服务端吗?就是服务与服务之间的升级。
答:理论上支持的,作者没有实际这么使用过。据反馈有的小伙伴已经这么干了。本次分享是针对C/S架构的场景。
(6)怎么获取更新包的MD5码?
答:使用项目源码里的,AutoUpdate.MD5工程。
(7)怎么制作一个更新包?
答:使用GeneralUpdate.PacketTool工具生成即可。在源码仓库的release中可以看到打包好的安装程序。
(8)关于组件的其他内容如何了解到?
答:可以通过官方网站、或者相关Q群、以及我gitee或github的issue中与我交流。
(9)下载包解压在C盘下Program Files (x86)时,没有权限操作怎么处理?
答:https://gitee.com/Juster-zhu/GeneralUpdate/issues/I4ZKQ4
(10)更新文件较小时,下载速度显示为:0B/S 。
答:https://gitee.com/Juster-zhu/GeneralUpdate/issues/I3POMG
在开始讲解使用之前,我们先需要搞明白GeneralUpdate更新体系中的一些基础概念、名词。
CREATE TABLE `updateversioninfo` ( `MD5` varchar(32) NOT NULL DEFAULT 'update packet md5', `PubTime` int DEFAULT '0', `Name` varchar(64) NOT NULL DEFAULT 'version name', `Url` varchar(255) NOT NULL DEFAULT 'update url', `Version` varchar(20) NOT NULL DEFAULT 'last version number', `ClientType` int NOT NULL DEFAULT '1', PRIMARY KEY (`MD5`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
基于以上的了解,我们再来看更新流程将会很清晰,思路清晰有助于我们使用。
client启动后将会向服务器发送http请求,确认upgrade是否需要更新。
如果upgrade需要更新将会下载upgrade的更新包并更新。
如果client发现upgrade不需要更新或者upgrade更新完毕之后,那么将会直接通过进程启动upgrade独立进程的应用程序。(也就是上面为什么需要保持引用独立)
upgrade被启动之后,会自动去请求client的更新包。用于更新client的内容;
在client、upgrade请求更新期间,server将会起到关键作用。提供版本更新信息、版本验证信息用来判断是否需要更新以及更新包下载地址。
server响应upgrade的请求后,upgrade将执行更新client的操作。
更新完成之后upgrade通过进程启动client。
client被启动之后,完成更新。(流程结束)
应用GeneralUpdate组件总共分为,三个部分Client 、Upgrade、Server。
安装完成之后,将会在nuget包引用中看到这些内容。
接下来就可以写代码了,在最新版本中简化了启动配置。(如果需要自定义配置则参考:https://gitee.com/Juster-zhu/GeneralUpdate/blob/master/src/AutoUpdate.ClientCore/MainWindow.xaml.cs)
public class MainViewModel { private const string baseUrl = @"http://127.0.0.1:5001"; public MainViewModel() { Upgrade(); } private void Upgrade() { Task.Run(async () => { var generalClientBootstrap = new GeneralClientBootstrap(); generalClientBootstrap.MutiDownloadProgressChanged += OnMutiDownloadProgressChanged; generalClientBootstrap.MutiDownloadStatistics += OnMutiDownloadStatistics; generalClientBootstrap.MutiDownloadCompleted += OnMutiDownloadCompleted; generalClientBootstrap.MutiAllDownloadCompleted += OnMutiAllDownloadCompleted; generalClientBootstrap.MutiDownloadError += OnMutiDownloadError; generalClientBootstrap.Exception += OnException; generalClientBootstrap.Config(baseUrl). Option(UpdateOption.DownloadTimeOut, 60). Option(UpdateOption.Encoding, Encoding.Default). Option(UpdateOption.Format, "zip"). Strategy<ClientStrategy>(); await generalClientBootstrap.LaunchTaskAsync(); }); } private void OnMutiDownloadStatistics(object sender, MutiDownloadStatisticsEventArgs e) { //e.Remaining 剩余下载时间 //e.Speed 下载速度 //e.Version 当前下载的版本信息 } private void OnMutiDownloadProgressChanged(object sender, MutiDownloadProgressChangedEventArgs e) { //e.TotalBytesToReceive 当前更新包需要下载的总大小 //e.ProgressValue 当前进度值 //e.ProgressPercentage 当前进度的百分比 //e.Version 当前下载的版本信息 //e.Type 当前正在执行的操作 1.ProgressType.Check 检查版本信息中 2.ProgressType.Donwload 正在下载当前版本 3. ProgressType.Updatefile 更新当前版本 4. ProgressType.Done更新完成 5.ProgressType.Fail 更新失败 //e.BytesReceived 已下载大小 } private void OnException(object sender, ExceptionEventArgs e) { Debug.WriteLine(e.Exception.Message); } private void OnMutiAllDownloadCompleted(object sender, MutiAllDownloadCompletedEventArgs e) { //e.FailedVersions; 如果出现下载失败则会把下载错误的版本、错误原因统计到该集合当中。 Debug.WriteLine($"Is all download completed { e.IsAllDownloadCompleted }."); } private void OnMutiDownloadCompleted(object sender, MutiDownloadCompletedEventArgs e) { //Debug.WriteLine($"{ e.Version.Name } download completed."); } private void OnMutiDownloadError(object sender, MutiDownloadErrorEventArgs e) { //Debug.WriteLine($"{ e.Version.Name } error!"); } }
到这里基础的功能代码已完成,剩下的事件回传的内容根据需要使用即可。推荐用法为:将事件回传参数在客户端中用独立遮罩层类似于“转圈圈的”界面显示升级进度信息,或者用日志记录下来。
订阅接收,Server端的最新版本推送。
private const string baseUrl = @"http://127.0.0.1:5001",hubName = "versionhub"; public MainViewModel() { InitializeComponent(); InitVersionHub(); } #region VersionHub /// <summary> /// Subscription server push version message. /// </summary> private void InitVersionHub() { VersionHub<string>.Instance.Subscribe($"{ baseUrl }/{ hubName }", "TESTNAME", new Action<string>(GetMessage)); } private void GetMessage(string msg) { TxtMessage.Text = msg;//这里接收推送的内容跟服务端约定好能解析即可,也可以在这里启动更新。 } #endregion VersionHub
到这里为止,clinet的应用分享已完成。
安装完成之后,将会在nuget包引用中看到这些内容。
接下来就可以写代码了,和ClientCore不同的是它不在需要配置url等内容将从进程传参中拿到RemoteAddressBase64的内容(内容是自动生成好的不需要关心)。
首先需要找到app.cs文件:
然后修改代码如下,这里是为了拿到client端进程传递过来的配置参数:
public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { MainWindow window = new MainWindow(e.Args[0]); window.ShowDialog(); base.OnStartup(e); } }
拿到的base64的示例内容如下:
eyJBcHBUeXBlIjoxLCJBcHBOYW1lIjoiQXV0b1VwZGF0ZS5DbGllbnRDb3JlIiwiTWFpbkFwcE5hbWUiOm51bGwsIkluc3RhbGxQYXRoIjoiRDpcXFVwZGF0ZXRlc3RfaHViXFxSdW5fYXBwIiwiQ2xpZW50VmVyc2lvbiI6IjEuMS4xIiwiTGFzdFZlcnNpb24iOiI5LjEuMy4wIiwiVXBkYXRlTG9nVXJsIjpudWxsLCJJc1VwZGF0ZSI6ZmFsc2UsIlVwZGF0ZVVybCI6bnVsbCwiVmFsaWRhdGVVcmwiOm51bGwsIk1haW5VcGRhdGVVcmwiOiJodHRwOi8vMTI3LjAuMC4xOjUwMDEvdmVyc2lvbnMvMS8xLjEuMS4xIiwiTWFpblZhbGlkYXRlVXJsIjoiaHR0cDovLzEyNy4wLjAuMTo1MDAxL3ZhbGlkYXRlLzEvMS4xLjEuMSIsIkNvbXByZXNzRW5jb2RpbmciOjcsIkNvbXByZXNzRm9ybWF0IjoiLnppcCIsIkRvd25sb2FkVGltZU91dCI6NjAsIlVwZGF0ZVZlcnNpb25zIjpbeyJQdWJUaW1lIjoxNjI2NzExNzYwLCJOYW1lIjpudWxsLCJNRDUiOiI1ZmI3NWU0NGQ3YzQ1ZTNmYzlkNmFhNDdjMDVhMGU5YSIsIlZlcnNpb24iOiI5LjEuMy4wIiwiVXJsIjpudWxsLCJJc1VuWmlwIjpmYWxzZX1dfQ==
MainViewModel.cs代码:
internal class MainViewModel : BaseViewModel { private string _tips1, _tips2, _tips3, _tips4, _tips5, _tips6; private double _progressVal, _progressMin, _progressMax; public MainViewModel(string args) { ProgressMin = 0; Task.Run(async () => { var bootStrap = new GeneralUpdateBootstrap(); bootStrap.MutiAllDownloadCompleted += OnMutiAllDownloadCompleted; bootStrap.MutiDownloadCompleted += OnMutiDownloadCompleted; bootStrap.MutiDownloadError += OnMutiDownloadError; bootStrap.MutiDownloadProgressChanged += OnMutiDownloadProgressChanged; bootStrap.MutiDownloadStatistics += OnMutiDownloadStatistics; bootStrap.Exception += OnException; bootStrap.Strategy<DefaultStrategy>(). Option(UpdateOption.Encoding, Encoding.Default). Option(UpdateOption.DownloadTimeOut, 60). Option(UpdateOption.Format, "zip"). RemoteAddressBase64(args); await bootStrap.LaunchTaskAsync(); }); } public string Tips1 { get => _tips1; set => SetProperty(ref _tips1, value); } public string Tips2 { get => _tips2; set => SetProperty(ref _tips2, value); } public string Tips3 { get => _tips3; set => SetProperty(ref _tips3, value); } public string Tips4 { get => _tips4; set => SetProperty(ref _tips4, value); } public string Tips5 { get => _tips5; set => SetProperty(ref _tips5, value); } public string Tips6 { get => _tips6; set => SetProperty(ref _tips6, value); } public double ProgressVal { get => _progressVal; set => SetProperty(ref _progressVal, value); } public double ProgressMin { get => _progressMin; set => SetProperty(ref _progressMin, value); } public double ProgressMax { get => _progressMax; set => SetProperty(ref _progressMax, value); } private void OnMutiDownloadStatistics(object sender, GeneralUpdate.Core.Update.MutiDownloadStatisticsEventArgs e) { Tips1 = $" { e.Speed } , { e.Remaining.ToShortTimeString() }"; } private void OnMutiDownloadProgressChanged(object sender, GeneralUpdate.Core.Update.MutiDownloadProgressChangedEventArgs e) { switch (e.Type) { case ProgressType.Check: break; case ProgressType.Donwload: ProgressVal = e.BytesReceived; if (ProgressMax != e.TotalBytesToReceive) { ProgressMax = e.TotalBytesToReceive; } Tips2 = $" { Math.Round(e.ProgressValue * 100, 2) }% , Receivedbyte:{ e.BytesReceived }M ,Totalbyte:{ e.TotalBytesToReceive }M"; break; case ProgressType.Updatefile: break; case ProgressType.Done: break; case ProgressType.Fail: break; default: break; } } private void OnMutiDownloadCompleted(object sender, GeneralUpdate.Core.Update.MutiDownloadCompletedEventArgs e) { //Tips3 = $"{ e.Version.Name } download completed."; } private void OnMutiAllDownloadCompleted(object sender, GeneralUpdate.Core.Update.MutiAllDownloadCompletedEventArgs e) { if (e.IsAllDownloadCompleted) { Tips4 = "AllDownloadCompleted"; } else { //foreach (var version in e.FailedVersions) //{ // Debug.Write($"{ version.Item1.Name }"); //} } } private void OnMutiDownloadError(object sender, GeneralUpdate.Core.Update.MutiDownloadErrorEventArgs e) { //Tips5 = $"{ e.Version.Name },{ e.Exception.Message }."; } private void OnException(object sender, GeneralUpdate.Core.Update.ExceptionEventArgs e) { Tips6 = $"{ e.Exception.Message }"; } }
到这里为止,upgrade的应用分享已完成。
这里使用新推出的Minimal api演示,其他的api的模板也同样适用。
创建完成之后项目结构如下:
这个时候我们再安装nuget。
安装完成之后的目录。
接下来我们再写代码。
using GeneralUpdate.AspNetCore.Services; using GeneralUpdate.Core.DTOs; var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton<IUpdateService, GeneralUpdateService>();//必须再这里添加这段代码 var app = builder.Build(); app.MapGet("/versions/{clientType}/{clientVersion}", async (int clientType, string clientVersion, IUpdateService updateService) => { return await updateService.UpdateVersionsTaskAsync(clientType, clientVersion, UpdateVersions); }); app.MapGet("/validate/{clientType}/{clientVersion}", async (int clientType, string clientVersion, IUpdateService updateService) => { return await updateService.UpdateValidateTaskAsync(clientType, clientVersion, GetLastVersion(), true, GetValidateInfos); }); app.Run(); async Task<List<UpdateVersionDTO>> UpdateVersions(int clientType, string clientVersion) { //这里需连接数据库查询对应内容,我这里用假数据辅助调试而已。 //TODO:Link database query information.Different version information can be returned according to the 'clientType' of request. var results = new List<UpdateVersionDTO>(); results.Add(new UpdateVersionDTO("5001fd3732b91dfe46196ceb0a5bc4b2", 1626711760, "9.1.3.0", "http://192.168.50.170/patchs.zip", "updatepacket1")); //results.Add(new UpdateVersionDTO("d9a3785f08ed3dd92872bd807ebfb917", 1626711820, "9.1.4.0", //"http://192.168.50.170/Update2.zip", //"updatepacket2")); //results.Add(new UpdateVersionDTO("224da586553d60315c55e689a789b7bd", 1626711880, "9.1.5.0", //"http://192.168.50.170/Update3.zip", //"updatepacket3")); return await Task.FromResult(results); } async Task<List<UpdateVersionDTO>> GetValidateInfos(int clientType, string clientVersion) { //这里需连接数据库查询对应内容,我这里用假数据辅助调试而已。 //TODO:Link database query information.Different version information can be returned according to the 'clientType' of request. var results = new List<UpdateVersionDTO>(); results.Add(new UpdateVersionDTO("5001fd3732b91dfe46196ceb0a5bc4b2", 1626711760, "9.1.3.0", null, null)); //results.Add(new UpdateVersionDTO("d9a3785f08ed3dd92872bd807ebfb917", 1626711820, "9.1.4.0", null, null)); //results.Add(new UpdateVersionDTO("224da586553d60315c55e689a789b7bd", 1626711880, "9.1.5.0", null, null)); return await Task.FromResult(results); } string GetLastVersion() { //这里需连接数据库查询对应内容,我这里用假数据辅助调试而已。这里需查询出最新发布日期的版本信息。 //TODO:Link database query information. return "1.1.5"; }
这里分享的是最新版本推送的功能,基于singal R来实现的。需要对singal r有一定了解。代码如下:
using GeneralUpdate.AspNetCore.Hubs; using GeneralUpdate.AspNetCore.Services; using GeneralUpdate.Core.DTOs; var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton<IUpdateService, GeneralUpdateService>(); builder.Services.AddSignalR(); var app = builder.Build(); app.MapHub<VersionHub>("/versionhub"); app.Use(async (context, next) => { var hubContext = context.RequestServices.GetRequiredService<IHubContext<VersionHub>>(); await CommonHubContextMethod((IHubContext)hubContext); if (next != null) { await next.Invoke(); } }); async Task CommonHubContextMethod(IHubContext context) { await context.Clients.All.SendAsync("clientMethod", ""); }
到这里为止,server的应用分享已完成。
开源不易希望大家能多多支持。可能或多或少会有些bug希望大家多多反馈,感谢各位的支持。
关键词:C/S、WPF、MAUI、Winfrom、Avalonia、Console App、UWP、WinUI、Linux、Windows、MacOS、自动更新、自动升级、更新、推送。