一、概要

本篇文章将向各位小伙伴介绍GeneralUpdate组件的使用,帮助第一次接触开发者快速上手应用在自己或企业项目中。如果本篇文章对您有帮助,希望帮忙点一下star。感谢各位开发者的支持。

帮助文档

  • 讲解视频:https://www.bilibili.com/video/BV1aX4y137dd

  • 官方网站:http://justerzhu.cn/

  • 相关工具:GeneralUpdate.PacketTool (该工具使用avalonia编写,可在linux、windows、mac操作系统使用)

  • github release: https://github.com/WELL-E/AutoUpdater/releases/tag/GeneralUpdateTool

    gitee release: https://gitee.com/Juster-zhu/GeneralUpdate/releases/GeneralupdateTool

  • Nuget版本管理参考标准:https://docs.microsoft.com/zh-cn/nuget/concepts/package-versioning

  • 应用程序集版本管理参考标准:https://docs.microsoft.com/zh-cn/dotnet/standard/assembly/versioning (被组件更新的客户端程序,说通俗点就是你公司的产品;组件的操作将按照这个标准执行。)

开源地址

  • https://github.com/WELL-E/AutoUpdater

  • https://gitee.com/Juster-zhu/GeneralUpdate

Q&A

(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更新体系中的一些基础概念、名词。

(1)名词解释

  • client:是指你的主应用程序,是被更新的客户端。也就是你公司的产品(假设项目结构如上)。它将需要在nuget平台安装GeneralUpdate.ClientCore。

  • upgrade:是指升级程序,它是一个独立的进程。需要和clinet放在同一个目录下,在使用的过程中不可以和任何业务关联、必须保持独立引用(项目结构如上)。有人会问我不保持会怎么样?会因为其他组件引用、文件占用更新失败。它将需要在nuget平台安装GeneralUpdate.Core。

  • server:是指服务端应用(asp.net)将提供版本更新信息、版本验证信息用来判断是否需要更新以及更新包下载地址。它将需要在nuget平台安装GeneralUpdate.AspNetCore。

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;
  • SQL:关于server端需要用的sql表的脚本(mysql)已经写好了,运行以上脚本即可创建表。

字段名称 字段类型 C#类型 备注
md5 varchar string 更新包的MD5码,也是唯一标识
pubtime int int 更新包发布时间戳(10位)
name varchar string 更新包名称
url varchar string 下载地址
version varchar string 版本号(1.0.0.0)
clienttype int int 1:客户端 2:升级程序

(2)更新流程

基于以上的了解,我们再来看更新流程将会很清晰,思路清晰有助于我们使用。

  • 第一步

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。

1.1 Client的应用

安装完成之后,将会在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!");}}

到这里基础的功能代码已完成,剩下的事件回传的内容根据需要使用即可。推荐用法为:将事件回传参数在客户端中用独立遮罩层类似于“转圈圈的”界面显示升级进度信息,或者用日志记录下来。

1.2Client的应用(非必要)

订阅接收,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的应用分享已完成。


2.1 Upgrade的应用

安装完成之后,将会在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的应用分享已完成。


3.1 Server的应用

这里使用新推出的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";
}

3.2 Server的应用(非必要)

这里分享的是最新版本推送的功能,基于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、自动更新、自动升级、更新、推送。

如何使用GeneralUpdte构建客户端自动升级功能相关推荐

  1. 喧喧 2.5 发布,新增客户端自动升级,优化界面交互性能

    喧喧是由然之协同团队推出的一款轻量级的开源企业聊天软件.提供企业内部通讯交流.企业通讯录.协同办公通讯交流.企业IM解决方案. 喧喧官网: https://xuan.im/ 更新明细 新增 客户端自动 ...

  2. Android简易新闻客户端自动升级 简易新闻(二十)

    Android简易新闻客户端自动升级 简易新闻(二十) 关于 说明 第一步,添加引用 第二步,注册蒲公英账号 第三步,添加AndroidManifest.xml配置 初始化 最后一步 打包上传 关于 ...

  3. 【uniapp】uniapp安卓APP在线自动升级功能

    纯前端实现uniapp写的安卓APP跟IOS在线自动升级功能 关于Uniapp自动升级用到的阿里云要收费的问题 使用的UI框架为UVIEW2.0 用到的请求等 总结下思路 就是通过获取XML文件返回来 ...

  4. Android 一s个相对完整的自动升级功能实现代码

    由于项目的需要最近做了一个关于Android自动升级的功能,下面将贴出Android手机客户端的完整代码.这段代码参考别的代码居多,由于不满足需求,所以自己仅仅改了一些需要变动的内容,其他功能都是按照 ...

  5. 关于appcan自动升级功能

    2019独角兽企业重金招聘Python工程师标准>>> 我们现在UAT环境的app是用appcan和html5进行制作,好多朋友都在问UAT app升级如何做的 我们在zy_cont ...

  6. 【C#】C#客户端自动升级技术简析

    升级程序为独立的exe程序,由客户端程序调用实现. 客户端调用部分 /* 客户端代码中,调用更新程序部分 */ static bool CheckUpdate() {try{//string tmpF ...

  7. (关闭/开启)Mac系统中Google浏览器自动升级功能

    核心内容:更改自动更新组件的权限,无权限自然就不能完成升级了,想升级的话将权限改回来即可. 进入/Users/`用户目录`/Library/Google目录 如果Users目录不存在可以到" ...

  8. 关闭mysql凌晨自动升级功能

    某次在计划任务表中偶然发现Mysql默认安装时创建一个升级任务,触发时间正好是凌晨 1.win+R (运行)输入 taskschd.msc 2.找到mysql的计划任务,选中Installer,点击右 ...

  9. 在WinForm中使用Web Services 来实现软件自动升级(转)

    一.升级的好处. 长期以来,广大程序员为到底是使用Client/Server,还是使用Browser/Server结构争论不休,在这些争论当中,C/S结构的程序的可维护性差,布置困难,升级不方便,维护 ...

最新文章

  1. 明晚8点直播 | 顺丰科技如何利用深度学习赋能智慧物流?
  2. AI教父争夺秘史:百度2.88亿天价求才,因中国身份惜败谷歌
  3. 15个初学者必看的基础SQL查询语句
  4. Java中集合 练习 计算疯狂值
  5. git 学习之基础知识
  6. 【caffe-Windows】cifar实例编译之model的使用
  7. 【跃迁之路】【651天】程序员高效学习方法论探索系列(实验阶段408-2018.11.24)...
  8. 这说明什么?【转载】早点长大的飞秋2013
  9. 异构平台同步(Mysql到Oracle)
  10. 最大矩阵和 2015-05-13 21:23 8人阅读 评论(0) 收藏...
  11. 清除eclipse当前登录的SVN账户
  12. vue使用echarts错误Failed to mount component: template or render function not defined.
  13. SDWebImage缓存图片的机制(转)
  14. 鼠标屏幕取词技术的原理和实现 (转)
  15. 拼音转换成汉字html,汉字转换成拼音的种(转)
  16. transE(Translating Embedding)详解+简单python实现
  17. 三分钟了解阿里云和腾讯云的DDoS防御策略
  18. L2-016 愿天下有情人都是失散多年的兄妹 (25 分)
  19. 11 年膨胀 575 倍,微信为何从“小而美”变成了“大而肥”?
  20. java游戏开局选宠物可以转职,创世之光人物资料及转职大全

热门文章

  1. 安卓 Input Events(输入事件)
  2. SQL Server 2008如何导出带数据的脚本文件
  3. Sersync+Rsync 增量实时同步
  4. iOS开发 CGAffineTransform 让图片旋转, 旋转后获得图片旋转的角度
  5. 刚接触git,提交文件时,遇到no changes added to commit
  6. 添加dubbo.xsd的方法
  7. segnet 编译与测试
  8. Linux中一些常用的很巧妙的命令
  9. 内核态和用户态的区别
  10. POJ 1904 【强连通分量】.cpp