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

一、概要

0acc834b9985e7fb8969d9ab8732f760.png

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

帮助文档

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

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

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

c7f47042d02eeac96450288ec64871c2.png

0ad3dfb0a254e6547d0803d3616a0c48.png

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

0dd0c5f13482a3040133a22378116780.png

(1)名词解释

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

7fc8d650e7cfc5a02e050efbb5e6a829.png

927d24a00c77c1942c0f4fd1e3d691ca.png

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

c966fe255ac2b6bb628c9290e7d88f0f.png

449e2ae1ba416999b96a4fcf5d04ea2c.png

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

7399b3ec3551aeef41f45b3a586b42a3.png

4602a7acc05e9f30fa731334077a73a8.png

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#类型备注
md5varcharstring更新包的MD5码,也是唯一标识
pubtimeintint更新包发布时间戳(10位)
namevarcharstring更新包名称
urlvarcharstring下载地址
versionvarcharstring版本号(1.0.0.0)
clienttypeintint1:客户端 2:升级程序

(2)更新流程

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

f8345f836e1a79de0b6d2152b7855a63.png

  • 第一步

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的应用

82e37e0d31ecd426bba4fc9b87cdcb39.png

安装完成之后,将会在nuget包引用中看到这些内容。

b822a335ebdc4bcf96aa4d3cd5965cad.png

接下来就可以写代码了,在最新版本中简化了启动配置。(如果需要自定义配置则参考: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的应用

e88b7b1dccce166bdaa49b0d880ad1d7.png

安装完成之后,将会在nuget包引用中看到这些内容。

85d5c21fa4c47f6a465fa9f81aa4cb9b.png

接下来就可以写代码了,和ClientCore不同的是它不在需要配置url等内容将从进程传参中拿到RemoteAddressBase64的内容(内容是自动生成好的不需要关心)。

首先需要找到app.cs文件:

2bf33bf4ca5d7d954cde6421f82bf527.png

然后修改代码如下,这里是为了拿到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的模板也同样适用。

efcd3b4a14c0539581f0466f3ead109e.png

创建完成之后项目结构如下:

2a2e998d150f13edce5078af276e9ab5.png

eca016246a3bd8e8f5437b35d8200214.png

15251004a03502e3a202af83b02995a2.png

这个时候我们再安装nuget。

27de4bc3c38e4b58b14108748ce021f2.png

安装完成之后的目录。

ac8a75d50adc5c99ec719c8f9b3e4aab.png

接下来我们再写代码。

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、自动更新、自动升级、更新、推送。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289634.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

30道四则运算题目---课堂作业--软件工程c++

问题&#xff1a;设计一程序&#xff0c;给二年级小学生随机产生四则运算题目。 一、设计思考问题&#xff1a; 1.四则运算需要俩个运算数和一个运算符。 2.如何产生随机数? 3.如何实现随机产生四则运算&#xff1f; 4.题目是否符合小学生学习范围&#xff1f;&#xff08;减法…

javascript 函数属性prototype(转)

在JavaScript中并没有类的概念&#xff0c;但javascript中的确可以实现重载&#xff0c;多态&#xff0c;继承。这些实现其实方法都可以用JavaScript中的引用和变量作用域结合prototype来解释。 1、prototype 在JavaScript中并没有类的概念&#xff0c;但JavaScript中的确可以实…

java离职交接文档_财务人员工作交接你知道么?没处理好不止将来风险大还可能违法...

今天给大家分享一下在工作中&#xff0c;财务人员应该怎么保护自己&#xff1f;1.虚假报销那些事儿在企业内部报销程序中&#xff0c;部分管理人员往往安排下属或助理经办报销程序&#xff0c;所有的费用申请、报销单据的填写均系下属完成&#xff0c;而一旦虚假报销案件浮出水…

SQL Server 数据库没有有效所有者的三种解决办法

问题描述: 开发的过程中,操作系统出了问题,决定重装系统。但是没有将SQL Server中的数据库文件分离出来,直接将系统格了。在新系统数据库中附加了数据库文件,一切还算正常,但当打开数据库关系图的时候出现了问题,如下图所示: 针对以上问题,网上有很多解决办法,但是由…

Javascript:原型模式类继承

原型模式每个函数&#xff08;准确说不是类、对象&#xff09;都有一个prototype属性&#xff0c;这个属性是一个指针&#xff0c;指向一个对象。使用原型对象的好处是可以让所有对象实例共享它包含的属性和方法。1.原型对象&#xff08;1&#xff09;当创建一个新函数&#xf…

C# 学习经验分享

NET 20 周年的学习挑战赛刚结束了第⼀阶段&#xff0c;不知道各位⼩伙伴参加了没有&#xff1f;有⼈问现在学习 C# 是不是有点过时&#xff1f;也有⼈问现在C# 能做什么&#xff1f;更有⼈问学习 C# 能否找到⼯作 &#xff1f;或者你从不同的专家&#xff0c;不同的从业者可以有…

有限服务器延时计算_机房设备功率统计,UPS不间断电源和蓄电池的计算选择

当然了&#xff0c;也可以不用这么麻烦&#xff0c;网上有UPS计算器的&#xff0c;输入条件直接求个结果就行了。弱电笔记&#xff5c;弱电&#xff08;安防&#xff09;计算器诺&#xff0c;就上面那个&#xff01;下面进入正文&#xff1a;一个计算机机房有4台PC机&#xff0…

linux把2块盘挂到一个分区,linux系统如何挂载第二块硬盘

云主机有两块硬盘&#xff0c;第一块硬盘盘一般为10G&#xff0c;用于安装系统&#xff0c;第二块磁盘根据云主机类型不同&#xff0c;大小也不相同。我们建议您在使用的时候第二块硬盘作为数据盘&#xff0c;存放你的网站数据&#xff0c;这样在您恢复系统的时候&#xff0c;数…

Visual Studio/SQL Server系统开发常见问题归纳

问题1.在利用C Sharp(操作系统为Windows7 64位)将Excel数据导入到SQL Server 2005中时,出现如下提示: 解决办法:VS菜单栏 生成→“配置管理器→平台→新建→x86。

Xamarin效果第十八篇之GIS中复合型Mark

在前面一篇文章中简单在GIS上添加了最基础的Mark,今天再次分享一下早几天摸索的复合型Mark;啥也不说了都在效果里:1、关于数据我就是直接抓的高德的(至于后面需要的参数那就自行抓):https://restapi.amap.com/v3/airquality/aqilist?2、至于Mark无非就是用了CompositeSymbol:C…

WPF定时刷新UI界面

代码&#xff1a; using NHibernate.Criterion; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; using System.Linq; using System.Text; using System.Threading; using System.Wi…

找不到libmmd.dll无法继续执行代码_300 行代码带你秒懂 Java 多线程!| 原力计划...

作者 | 永远在路上【】责编 | 胡巍巍出品 | CSDN博客线程线程的概念&#xff0c;百度是这样解释的&#xff1a;线程(英语&#xff1a;Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的…

苹果、联想及华硕均看准美国电脑运输的增长

据国外媒体雅虎金融报道&#xff0c;根据国际数据公司的研究&#xff0c;美国个人电脑在4Q14中下跌至1820万台&#xff0c;在4Q15中下跌至1740万台&#xff0c;跌幅4.3%。而苹果、联想和华硕的产品出货量却呈现上市趋势。苹果出货量上升8.9%&#xff0c;至220万台&#xff0c;华…

如何升级浏览器_前谷歌员工爆料:谷歌工程师们是如何合谋“杀死”IE6浏览器的...

浏览器在互联网中的地位可见一斑&#xff0c;甚至可以说&#xff0c;它是比搜索引擎和社交软件更强的流量入口。微软IE6当年在2001年作为Windows XP的默认浏览器发布&#xff0c;比Chrome首次发布还要早六年。 但是&#xff0c;谷歌Chrome却后来居上&#xff0c;如今的市场份额…

安卓设备刷linux发行版,绝对精华,大牛教你在Android系统上安装linux发行版

Install linux distribution(Debian/Ubuntu/Kali Linux/Fedora/openSUSE/Gentoo/RootFS/Arch Linux) on Android system在Android系统上安装linux发行版(Debian/Ubuntu/Kali Linux/Fedora/openSUSE/Gentoo/RootFS/Arch Linux)1、所用的手机类型为Samsung Note2 Verizon i6052、…

在 ASP.NET Core 中使用 HTTP 标头传播

前言我们常用 JWT 令牌用于身份验证&#xff0c;前端一般是在请求中包含 HTTP 标头 Authorization 实现。但是&#xff0c;当服务间需要互相调用时&#xff0c;也需要"按原样"将标头传播到目标服务。原来的解决方案是从请求中读取标头&#xff0c;并将其添加到对外请…

中英文标点符号切换的组合键_易混标点符号:一字线(—)、短横线(-)、浪纹线(~)...

前情提要&#xff1a;上次辨析了容易混淆的中英文冒号&#xff08;&#xff1a;、:&#xff09;、比号&#xff08;∶&#xff09;【点此查看】&#xff0c;这次继续介绍易混标点符号。易混标点符号&#xff1a;一字线&#xff08;—&#xff09;、短横线&#xff08;-&#xf…

Linux命令-网络命令:wall

wall hello word 向所有登录用户发送消息hello world root用户自己也会收到消息,wangyunpeng用户收到消息如下图:

win定时关机_两种方法让你设置电脑定时关机

方法一&#xff1a;利用计划程序步骤一&#xff1a;在开始菜单搜索框中输入计划任务&#xff0c;点击任务计划程序步骤二&#xff1a;点击操作再点击创建基本任务创建基本任务中输入名称和相关描述步骤三&#xff1a;触发器选择为每天&#xff0c;然后点击下一步步骤四&#xf…

AsyncEx - async/await 的辅助库

简介AsyncEx - async/await 的辅助库安装使用Nuget安装Nito.AsyncEx使用AsyncLock构造AsyncLock函数可以采用异步等待队列&#xff1b;传递自定义等待队列以指定您自己的排队逻辑。private readonly AsyncLock _mutex new AsyncLock(); public async Task UseLockAsync() {// …