- A+
所属分类:.NET技术
工作逻辑是用户启动主程序,主程序启动更新程序,更新程序立刻检查是否有已经下载好的更新包,如果有则立刻关闭主程序进行更新,如果没有则访问服务器查询更新包,并在后台静默下载,下载完成后等下一次主程序启动时更新
由于只是简单的更新程序,所以没有用数据库,客户端版本号以一个json文件保存,服务端则直接以压缩包的名称作为版本号
那么首先就要有一个服务端,我这里建了一个简单的Asp.Net Core WebApi程序,只有一个获取包列表和一个下载包的方法
Program 中要先添加允许访问的物理路径设置:
string packagePath = Path.Combine(AppContext.BaseDirectory, "Packages"); if (!Directory.Exists(packagePath)) Directory.CreateDirectory(packagePath); app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(packagePath), //添加允许访问的路径 RequestPath = "/Packages" });
控制器方法:
[HttpGet] public IActionResult GetPackages(long version) { _logger.LogInformation($"用户查询了一次{version}以后的更新包列表"); string[] files = Directory.GetFiles(_path, "*.zip", SearchOption.TopDirectoryOnly); List<long> packages = new List<long>(); for (int i = 0; i < files.Length; i++) { long serverVersion = Convert.ToInt64(Path.GetFileNameWithoutExtension(files[i])); //懒得用数据库,直接用压缩包的文件名作为版本号,可自行改进 if (serverVersion > version) packages.Add(serverVersion); } packages.Sort(); //排序一下给回客户端,让客户端从旧到新下载(当然也可以返回到客户端后再排序) return new JsonResult(packages); } [HttpGet] public IActionResult Download(long version) { _logger.LogInformation($"一位用户请求下载{version}版本"); string fileName = Path.Combine("Packages", version + ".zip"); //这里是绝对路径 string fileFullName = Path.Combine(AppContext.BaseDirectory, fileName); if (!System.IO.File.Exists(fileFullName)) return NotFound(); //如果客户端申请下载的更新包不存在,返回404 return Redirect($"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}/{fileName}"); //重定向到文件路径 }
接下来是客户端那边更新程序的部分,由于健壮性判断有点多,所以只贴下载和替换关键部分,其余部分请下载附件查看
下载部分:
/// <summary> /// 查找服务器上比当前版本新的包列表 /// </summary> /// <param name="version">当前版本</param> private static async Task<long[]> GetNewPackages(long version) { var resp = await _client.GetAsync($"{Constant.Config.Host}UpdateService/GetPackages?version={version}"); if (!resp.IsSuccessStatusCode) throw new HttpRequestException("查找更新失败", null, resp.StatusCode); var jsonPackageList = await resp.Content.ReadAsStringAsync(); long[]? packages = JsonSerializer.Deserialize<long[]>(jsonPackageList); if (packages is null) throw new Exception("更新包名称不是数字"); return packages; } /// <summary> /// 下载指定版本的包 /// </summary> /// <param name="packages">包名列表</param> private static async Task DownloadPackage(long[] packages) { for (int i = 0; i < packages.Length; i++) { string packageName = Path.Combine(Constant.DownloadDirect, $"{packages[i]}.zip"); var resp = await _client.GetAsync($"{Constant.Config.Host}UpdateService/Download?version={packages[i]}"); if (!resp.IsSuccessStatusCode) throw new HttpRequestException("下载更新失败", null, resp.StatusCode); using Stream stream = resp.Content.ReadAsStream(); using FileStream fs = new FileStream(packageName, FileMode.OpenOrCreate, FileAccess.Write); stream.CopyTo(fs); } }
替换文件部分:
/// <summary> /// 解压 /// </summary> private async Task UnzipAllPackages() { _needUpdatePackages.Sort(); progUpdate.Maximum = _needUpdatePackages.Count; for (int i = 0; i < _needUpdatePackages.Count; i++) { txbUpdate.Text = $"正在解压,第{i + 1}个,共{_needUpdatePackages.Count}个"; progUpdate.Value = i; await Task.Delay(30); ZipFile.ExtractToDirectory(_needUpdatePackages[i], Constant.UnzipDirect, Encoding.GetEncoding("GB2312"), true); } progUpdate.Value = _needUpdatePackages.Count; if (long.TryParse(Path.GetFileNameWithoutExtension(_needUpdatePackages.Last()), out long lVersion)) { _updateingVersion = lVersion; } else { MessageBox.Show("更新失败,请联系软件供应商。rn错误代码:0001", "错误"); return; } await Task.Delay(30); } /// <summary> /// 替换文件 /// </summary> /// <param name="files">文件列表</param> private async Task<bool> UpdateFiles(string[] files) { for (int i = 0; i < files.Length; i++) { txbUpdate.Text = $"正在更新文件,第{i + 1}个,共{files.Length}个"; progUpdate.Value = i; if (files[i].Contains("自动更新程序")) { continue; //自己不能被替换,跳过自己,由主程序替换 } string relativeFileName = files[i][Constant.UnzipDirect.Length..]; if (relativeFileName[0] == '\') relativeFileName = relativeFileName[1..]; string toName = Path.Combine(Environment.CurrentDirectory, relativeFileName); string toDir = Path.GetDirectoryName(toName)!; if (!Directory.Exists(toDir)) Directory.CreateDirectory(toDir); try { File.Copy(files[i], toName, true); } catch { MessageBox.Show($"更新失败,{toName.Substring(Environment.CurrentDirectory.Length)}无法被替换,即将回滚更新", "错误"); return false; } _coveredFiles.Add(toName); await Task.Delay(30); } progUpdate.Value = files.Length; await Task.Delay(30); return true; }
还有备份和更新失败后根据备份文件回滚的部分,由于和替换代码大量重叠就不贴出来了,附件的项目里面有,可直接编译运行
本文的主要目的也只是自己做个记录,以后有需要的时候可以直接拿过来改,肯定不如其他软件的更新程序这么完善,大佬们看一乐就好