C# Winform自动更新
在Unity里面,有XLua,ILRuntime 这样的热更新框架,Unity和Winform不同之处是,Unity生成的项目,哪怕是Windows平台,根本不必关闭程序才能进行更新,但是Winform项目必须关闭程序才进行下载替换,在Winform平台目前还没看到什么好的开源框架,于是我自己动手写了一个效果如下:客户端自动更新方法大概如下面几条,最少需要做三个软件1.更新文件列表生成器2.软件启
目录
当前项目已停止维护,推荐使用 FTP 版自动更新
C# 自动更新(基于FTP)_c# 程序自动升级-CSDN博客
一、需求
在Unity里面,有XLua,ILRuntime 这样的热更新框架,有Unity经验的人都知道,在Unity里面发布的各个平台,哪怕是Windows平台,根本不必关闭程序才能进行更新,但是 Winform 项目必须关闭程序才进行下载替换,我也在网上找了一下,目前 Winform 还没看到什么比较好的开源框架,于是我自己动手写了一个
效果如下:
在 Windows 平台,客户端自动更新方法一般如下面几个步骤,最少需要做三个软件
1.更新文件列表生成器
2.软件启动器
3.软件本体(只能由软件启动器 打开)
软件启动入口可以加密码,防止用户随意打开
二、更新文件列表生成器
我们知道,在原有基础上更新文件必要的三个基础:新增,覆盖,删除,那么问题来了,新增和删除好理解,在覆盖文件这块,假如:最新文件和旧版文件有一个同名的文件都叫 Test.dll,这时候要怎么判断是否进行下载和替换?
这个问题,可以用文件的哈希值去解决,如果客户端匹配不上,那么就需要从服务器下载和替换了,代码如下:
using System;
using System.IO;
using System.Security.Cryptography;
namespace Test
{
internal class Program
{
static void Main(string[] args)
{
string path1 = "D:\\单据备份\\53.png";
string saveHash = "97-9F-FC-54-BF-C0-95-B8-15-64-C5-AC-81-03-DE-90-13-CD-03-D5";
using (HashAlgorithm hash = HashAlgorithm.Create())
{
using (FileStream file1 = new FileStream(path1, FileMode.Open))
{
byte[] hashByte1 = hash.ComputeHash(file1);//哈希算法根据文本得到哈希码的字节数组
string str1 = BitConverter.ToString(hashByte1);//将字节数组装换为字符串
Console.WriteLine(str1);
if (saveHash.Equals(str1))
Console.WriteLine("两个文件相同");
else
Console.WriteLine("两个文件不相同");
}
}
Console.ReadKey();
}
}
}
更新文件生成器界面如下,将最新的软件文件放到服务器一个固定的文件夹,然后用更新文件生成器就可以生成文件列表等数据了
新建一个类 DES加密类,这个是给json加密用的,不过源码中,我将这功能注释了
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Utils
{
public class DESKey
{
/// <summary>
/// Des默认密钥向量
/// </summary>
public static byte[] IV = { 0x13, 0x35, 0x16, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
/// <summary>
/// Des加解密钥必须8位
/// </summary>
public const string Key = "4hghhgg";
}
public class DES
{
/// <summary>
/// Des加密
/// </summary>
/// <param name="source">源字符串</param>
/// <param name="key">des密钥,长度必须8位</param>
/// <param name="iv">密钥向量</param>
/// <returns>加密后的字符串</returns>
public static string EncryptDes(string source, string key, byte[] iv)
{
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
byte[] rgbKeys = GetDesKey(key),
rgbIvs = iv,
inputByteArray = Encoding.UTF8.GetBytes(source);
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, desProvider.CreateEncryptor(rgbKeys, rgbIvs), CryptoStreamMode.Write))
{
cryptoStream.Write(inputByteArray, 0, inputByteArray.Length);
cryptoStream.FlushFinalBlock();
return Convert.ToBase64String(memoryStream.ToArray());
}
}
}
}
/// <summary>
/// Des解密
/// </summary>
/// <param name="source">源字符串</param>
/// <param name="key">des密钥,长度必须8位</param>
/// <param name="iv">密钥向量</param>
/// <returns>解密后的字符串</returns>
public static string DecryptDes(string source, string key, byte[] iv)
{
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
byte[] rgbKeys = GetDesKey(key), rgbIvs = iv, inputByteArray = Convert.FromBase64String(source);
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, desProvider.CreateDecryptor(rgbKeys, rgbIvs), CryptoStreamMode.Write))
{
cryptoStream.Write(inputByteArray, 0, inputByteArray.Length);
cryptoStream.FlushFinalBlock();
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
}
}
}
/// <summary>
/// 获取Des8位密钥
/// </summary>
/// <param name="key">Des密钥字符串</param>
/// <returns>Des8位密钥</returns>
private static byte[] GetDesKey(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key", "Des密钥不能为空");
}
if (key.Length > 8)
{
key = key.Substring(0, 8);
}
if (key.Length < 8)
{
// 不足8补全
key = key.PadRight(8, '0');
}
return Encoding.UTF8.GetBytes(key);
}
}
}
Form1.cs
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace UpdateFileCreater
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private class FileType
{
/// <summary>
/// 路径
/// </summary>
public string Path { get; set; }
/// <summary>
/// 哈希值
/// </summary>
public string Hashs { get; set; }
}
private class UpdateFile
{
/// <summary>
/// 文件夹列表
/// </summary>
public List<string> DirectoryList { get; set; } = new List<string>();
/// <summary>
/// 文件列表
/// </summary>
public List<FileType> FilesinfoList { get; set; } = new List<FileType>();
/// <summary>
/// 服务器版本号
/// </summary>
public string Version { get; set; }
}
//需要生成列表的目录
private string Path = "D:\\H\\本地服务器工具\\Software\\News\\";
//存放json的位置
private string FileConfigJsonPath = "D:\\H\\本地服务器工具\\Software\\FileList.json";
//本地文件的黑名单(不参与到更新)
private static List<string> LocalFileBlacklist = new List<string>();
//文件夹列表
private static List<string> DirectorysList = new List<string>();
//文件列表
private static List<FileType> FilesinfoList = new List<FileType>();
private void Form1_Load(object sender, EventArgs e)
{
this.TextBox_Path.Text = Path;
//添加黑名单
LocalFileBlacklist.Add("log4net.dll");
LocalFileBlacklist.Add("log4net.xml");
LocalFileBlacklist.Add("Newtonsoft.Json.dll");
LocalFileBlacklist.Add("Newtonsoft.Json.xml");
string localVersionPath = FileVersionInfo.GetVersionInfo(Path + "\\Test1.exe").FileVersion;
Console.WriteLine("软件的版本号" + localVersionPath);
}
/// <summary>
/// 选择路径
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button_SelectionPath_Click(object sender, EventArgs e)
{
FolderBrowserDialog dialog = new FolderBrowserDialog();
dialog.Description = "请选择文件夹";
dialog.SelectedPath = Path;
if (dialog.ShowDialog() == DialogResult.OK)
{
if (string.IsNullOrEmpty(dialog.SelectedPath))
{
MessageBox.Show(this, "文件夹路径不能为空", "提示");
return;
}
Path = dialog.SelectedPath;
this.TextBox_Path.Text = Path;
}
}
/// <summary>
/// 生成文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button_CreaterFile_Click(object sender, EventArgs e)
{
DirectorysList.Clear();
FilesinfoList.Clear();
if (System.IO.Directory.Exists(Path))
{
Task.Run(() =>
{
GetDirectoryFileList(Path);
if (FilesinfoList.Count > 0)
{
UpdateFile updateFile = new UpdateFile();
updateFile.DirectoryList = DirectorysList;
updateFile.FilesinfoList = FilesinfoList;
updateFile.Version = FileVersionInfo.GetVersionInfo(Path + "\\Test1.exe").FileVersion;
//生成json文件
string json = JsonConvert.SerializeObject(updateFile);
//加密json文件
//json = Utils.DES.EncryptDes(json, Utils.DESKey.Key, Utils.DESKey.IV);
WriteJsonFile(FileConfigJsonPath, json);
MessageBox.Show("生成文件成功!");
}
});
}
}
/// <summary>
/// 获取一个文件夹下的所有文件和文件夹集合
/// </summary>
/// <param name="path"></param>
private void GetDirectoryFileList(string path)
{
DirectoryInfo directory = new DirectoryInfo(path);
FileSystemInfo[] filesArray = directory.GetFileSystemInfos();
foreach (var item in filesArray)
{
if (item.Attributes == FileAttributes.Directory)
{
//添加文件夹
string dir = item.FullName.Replace(Path, "");
DirectorysList.Add(dir);
GetDirectoryFileList(item.FullName);
}
else
{
string fileName = item.FullName.Replace(Path, "");
fileName = fileName.Replace(@"\", @"/");
//是否在黑名单中
bool isBlackList = false;
if (LocalFileBlacklist.Count > 0)
{
for (int i = 0; i < LocalFileBlacklist.Count; i++)
{
if (LocalFileBlacklist[i].Equals(fileName))
{
isBlackList = true;
break;
}
}
}
if (!isBlackList)
{
//添加文件
FileType fileType = new FileType();
fileType.Path = fileName;
fileType.Hashs = GetHashs(item.FullName);
FilesinfoList.Add(fileType);
}
}
}
}
/// <summary>
/// 获取文件的哈希值
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private string GetHashs(string path)
{
//创建一个哈希算法对象
using (HashAlgorithm hash = HashAlgorithm.Create())
{
using (FileStream file1 = new FileStream(path, FileMode.Open))
{
//哈希算法根据文本得到哈希码的字节数组
byte[] hashByte1 = hash.ComputeHash(file1);
//将字节数组装换为字符串
return BitConverter.ToString(hashByte1);
}
}
}
/// <summary>
/// 将json字符串内容写入Json文件并保存(若json文件不存在则创建)
/// </summary>
/// <param name="path">路径</param>
/// <param name="jsonConents">Json内容</param>
private void WriteJsonFile(string path, string jsonConents)
{
using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite, FileShare.ReadWrite))
{
fs.Seek(0, SeekOrigin.Begin);
fs.SetLength(0);
using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
{
sw.WriteLine(jsonConents);
}
}
}
}
}
这样 更新文件列表生成器 就基本完成了。
三、软件启动器
在我们平时用到的很多Windows软件打开时都会有软件启动器,大部分的作用都是基于软件的更新和文件的效验,这也形成了软件体系一个固定的框架,那么为什么要用软件启动器呢,那是因为,Windows软件打开以后,软件的文件就会被占用,无法进行删除,替换等操作,所以,想要更新软件,软件启动器是必须的。
软件启动器需要给项目添加管理员权限,软件启动器是启动软件之前,做的一些安全判断,比如文件是否完整,是否需要更新到最新版本,然后执行文件的新增,删除,覆盖 等任务。
1.判断是否需要更新
判断版本是否更新的方法:
Version v1 = new Version(txt1.Text);
Version v2 = new Version(txt2.Text);
if (v1 > v2)
{
MessageBox.Show("版本1高于版本2");
}
if (v1 < v2)
{
MessageBox.Show("版本1低于版本2");
}
2.文件下载
1.如果需要更新,将本地文件的哈希值和服务器返回的列表进行判断,如果服务器文件哈希值和本地文件哈希值不一致,那么就加入到下载列表
2.文件操作,更新软件本体通常的操作是:
① 新增文件直接下载
② 相同文件直接覆盖
③ 删除文件直接删除
3.执行 下载,覆盖,删除任务
需要操作的文件加入到任务列表(下载,覆盖,删除),需要专门写一个任务系统来执行此次操作,将每个子任务的结果显示到UI界面,这里可以用一个进度条和文本来显示执行的进度,
但也需要注意一点,自动更新系统都有一个毛病,就是不能确保文件的完整性,如果在下载的中途结束软件启动器进程,会导致软件的文件不全,从而打不开软件,解决这个问题,可以用一个文件夹先下载好文件,下完之后,直接覆盖现有的文件。
4.执行结果
如果执行完成,打开软件本体
如果执行失败,弹框显示错误信息,退出
软件启动器的界面就这样,没有过于复杂的部分
代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Utils;
namespace FileBootUp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Label_DownLog.Visible = false;
ProgressBar_DownProgress.Visible = false;
Task.Run(() =>
{
//获取服务器文件列表
GetConfig.StartGetConfig();
//对比本地文件
ComparisonVersion.Instance.StartComparison(StartDown);
});
}
private void StartDown(List<DownInfo> addedList, List<DownInfo> coverList, List<string> deleteList)
{
if(addedList.Count == 0 && coverList.Count == 0 && deleteList.Count == 0)
{
MessageBox.Show("没有需要更新的文件");
return;
}
FormControlExtensions.InvokeIfRequired(this, () =>
{
Label_DownLog.Visible = true;
ProgressBar_DownProgress.Visible = true;
});
//删除
if (deleteList.Count > 0)
{
for (int i = 0; i < deleteList.Count; i++)
{
string paths = deleteList[i];
File.Delete(paths);
Console.WriteLine(string.Format("删除文件完成,路径:{0}", paths));
}
}
//新增
if (addedList.Count > 0)
{
for (int i = 0; i < addedList.Count; i++)
{
DownInfo downInfo = addedList[i];
HTTPDownManager.DownloadFileData(downInfo.DownURL, downInfo.SavePath, DownProgress, DownEnd);
}
}
//覆盖
if(coverList.Count > 0)
{
for (int i = 0; i < coverList.Count; i++)
{
DownInfo downInfo = coverList[i];
HTTPDownManager.DownloadFileData(downInfo.DownURL, downInfo.SavePath, DownProgress, DownEnd);
}
}
AllTaskEnd();
}
private void DownProgress(string fileName, string size, int percent)
{
FormControlExtensions.InvokeIfRequired(this, () =>
{
this.Label_DownLog.Text = string.Format("正在下载:{0} 大小:{1}", fileName, size);
this.Label_DownProgress.Text = string.Format("{0}%", percent);
this.ProgressBar_DownProgress.Value = percent;
});
}
private void DownEnd(string fileName)
{
Console.WriteLine(string.Format("{0} 下载完成", fileName));
}
private void AllTaskEnd()
{
FormControlExtensions.InvokeIfRequired(this, () =>
{
this.Label_DownLog.Text = string.Empty;
this.Label_DownProgress.Text = string.Empty;
this.ProgressBar_DownProgress.Value = 0;
this.Label_DownLog.Visible = false;
this.ProgressBar_DownProgress.Visible = false;
MessageBox.Show("所有任务完成");
});
}
}
}
上面代码中用到的一部分工具类,我这里就不展示了,代码太多了。
其中的配置文件 App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<appSettings>
<!--服务器版本URL-->
<add key="ServerFileList" value="http://localhost/Software/FileList.json"/>
<!--服务器文件库-->
<add key="ServerFileVersion" value="http://localhost/Software/News"/>
</appSettings>
</configuration>
四、搭建更新服务器
服务器可以使用软件来实现,不用的时候就关掉,在测试的时候,我们不必使用 IIS 这样的服务器,配置起来太麻烦了,软件名如下图
1.启动服务器
首先是上面图片中的 index.html 文件,里面没有html 代码,就是一句话
在双击 NetBox2.exe 后,就回自动打开一个网站
并且有任务栏中,可以看到图标
这样服务器就运行起来了
2.新建项目本体
项目本体,就用来作为更新的软件,那就新建一个 winform 项目吧,取名 Test1,界面不重要,也用不上,随便弄一下
目前作为测试,最主要的是,让软件的生存目录的文件多一些,比如选择这个插件
Swashbuckle.AspNetCore.Swagger.6.4.0
安装完成后,我们来修改一下,Program.cs 的代码
3.给启动软件加密
代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Test1
{
internal static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args.Length == 0)
return;
if (args[0] != "密码")
return;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
这里的 string[] arrgs 原本是没有的,我这里加上后,另外加上密码,直接双击exe 文件是打不开的,这里代码的主要作用也是如此。
代码添加完成后,点击生成
由于刚刚添加的插件,生成的文件特别多,先别管他了
4.修改版本号
另外,就是版本号的修改了,打开 AssemblyInfo.cs
一般修改版本号改 AssemblyFileVersion
将版本改为 1.0.0.1 吧
现在准备工作也做的差不多了,下面开始整体测试。
五、整体测试
1.生成更新文件
将Test1项目生成的文件复制到服务器固定目录
打开更新文件列表生成器
点击生成
这时候,就看到了生成的文件
由于是加密的,直接看是不知道里面有什么的
在源码中,我将加密部分注释了,那么效果就是这样的
{
"DirectoryList": [],
"FilesinfoList": [{
"Path": "Microsoft.AspNetCore.Http.Abstractions.dll",
"Hashs": "4F-30-77-3D-BA-F6-84-0E-F7-57-E5-EF-64-7A-96-B2-54-F9-BA-3B"
}, {
"Path": "Microsoft.AspNetCore.Http.Abstractions.xml",
"Hashs": "25-2C-B5-23-37-0F-B2-2F-BF-F9-DA-B1-7E-EE-CA-63-42-7A-06-65"
}, {
"Path": "Microsoft.AspNetCore.Http.Extensions.dll",
"Hashs": "74-56-B5-F3-E1-37-45-D6-A2-99-2F-F9-17-E1-20-E1-BA-7E-37-CE"
}, {
"Path": "Microsoft.AspNetCore.Http.Extensions.xml",
"Hashs": "A8-B0-E3-46-56-25-8A-19-FE-33-91-27-67-3C-EC-62-F5-E0-8F-AE"
}, {
"Path": "Microsoft.AspNetCore.Http.Features.dll",
"Hashs": "1E-CD-4E-A7-C0-11-CE-91-A9-E2-54-25-ED-E4-B0-C1-A6-4D-88-FD"
}, {
"Path": "Microsoft.AspNetCore.Http.Features.xml",
"Hashs": "5F-5D-0D-BB-A3-42-5B-E8-CA-F2-99-5C-26-BC-2F-F4-35-AC-EE-0F"
}, {
"Path": "Microsoft.AspNetCore.Routing.Abstractions.dll",
"Hashs": "60-64-E5-E5-8B-00-A0-EF-6F-0A-0B-82-83-9C-FA-84-91-CD-10-BC"
}, {
"Path": "Microsoft.AspNetCore.Routing.Abstractions.xml",
"Hashs": "40-98-34-26-61-84-70-B0-4E-F4-67-7A-FF-5E-B0-85-25-20-DB-29"
}, {
"Path": "Microsoft.AspNetCore.Routing.dll",
"Hashs": "4A-32-C9-0C-F5-3D-3C-29-E3-B9-81-8C-3C-25-02-E3-31-F7-70-72"
}, {
"Path": "Microsoft.AspNetCore.Routing.xml",
"Hashs": "05-B5-91-BF-D1-2B-97-99-31-9B-4F-7A-88-40-68-A5-75-57-E2-1E"
}, {
"Path": "Microsoft.Extensions.DependencyInjection.Abstractions.dll",
"Hashs": "5E-46-B9-38-63-9A-28-65-1B-D4-DE-8E-DA-43-8C-CC-5A-21-2E-1C"
}, {
"Path": "Microsoft.Extensions.DependencyInjection.Abstractions.xml",
"Hashs": "05-3C-29-79-BE-6B-34-77-69-4D-25-FD-CE-0E-89-C6-FD-71-6D-E1"
}, {
"Path": "Microsoft.Extensions.FileProviders.Abstractions.dll",
"Hashs": "F1-D2-1C-08-B2-AC-28-18-17-37-6C-DB-2D-F2-E5-FA-A2-1E-C8-45"
}, {
"Path": "Microsoft.Extensions.FileProviders.Abstractions.xml",
"Hashs": "D2-BD-BA-26-10-70-52-BE-8D-BE-02-88-63-49-7E-0D-63-BB-D8-50"
}, {
"Path": "Microsoft.Extensions.Logging.Abstractions.dll",
"Hashs": "65-5B-52-5B-F7-33-AF-9F-1F-F7-A7-89-8E-98-11-63-47-E6-7C-F8"
}, {
"Path": "Microsoft.Extensions.Logging.Abstractions.xml",
"Hashs": "E1-82-DC-EA-78-DF-37-4B-5E-16-1D-93-D2-D9-6D-09-B0-78-4F-88"
}, {
"Path": "Microsoft.Extensions.ObjectPool.dll",
"Hashs": "4E-FD-D1-C7-9B-20-F9-D4-A8-18-2E-30-2D-1E-2C-86-98-BB-23-06"
}, {
"Path": "Microsoft.Extensions.ObjectPool.xml",
"Hashs": "4C-0C-2F-42-F1-B0-70-E7-2F-B5-6F-AD-9E-93-40-D8-F3-E7-65-DE"
}, {
"Path": "Microsoft.Extensions.Options.dll",
"Hashs": "C2-F4-79-71-79-93-EA-E1-61-42-A2-07-F1-75-48-21-69-D8-BC-CB"
}, {
"Path": "Microsoft.Extensions.Options.xml",
"Hashs": "CB-37-C1-6A-43-5C-57-BE-7D-4C-C7-1D-29-AD-17-39-11-3C-A6-25"
}, {
"Path": "Microsoft.Extensions.Primitives.dll",
"Hashs": "91-4D-D1-1A-94-E9-FB-1C-A7-9D-BF-F6-BE-C6-61-D4-E2-9C-D0-B0"
}, {
"Path": "Microsoft.Extensions.Primitives.xml",
"Hashs": "DE-7E-1A-8D-C9-27-3F-C0-B6-3A-3D-4D-1B-88-EB-A2-F5-E1-84-9B"
}, {
"Path": "Microsoft.Net.Http.Headers.dll",
"Hashs": "98-D4-88-0C-05-CD-B1-32-BB-D7-F7-3B-EF-A0-CF-A7-B2-2A-57-78"
}, {
"Path": "Microsoft.Net.Http.Headers.xml",
"Hashs": "63-68-C4-F0-5D-24-8D-94-9D-1A-F5-29-C9-BF-05-DB-E0-81-A0-38"
}, {
"Path": "Microsoft.OpenApi.dll",
"Hashs": "45-FB-EB-24-39-BE-3F-E9-15-A4-62-3C-01-30-DC-B1-08-AC-E8-08"
}, {
"Path": "Microsoft.OpenApi.pdb",
"Hashs": "FA-6C-06-BA-24-9B-80-50-BF-A0-FB-17-57-BE-82-EE-D1-CE-B0-DE"
}, {
"Path": "Microsoft.OpenApi.xml",
"Hashs": "1E-1D-79-43-25-67-4C-52-5C-A3-02-E9-30-2A-17-A4-A9-4E-38-B2"
}, {
"Path": "Swashbuckle.AspNetCore.Swagger.dll",
"Hashs": "A8-52-E6-35-A3-A4-9B-B5-A8-B3-F5-44-C6-C3-89-75-87-BB-7D-A8"
}, {
"Path": "Swashbuckle.AspNetCore.Swagger.pdb",
"Hashs": "48-3B-7C-A7-EA-B8-E2-61-67-F9-FE-00-A3-9F-AD-A2-20-D4-73-D7"
}, {
"Path": "Swashbuckle.AspNetCore.Swagger.xml",
"Hashs": "AA-2F-DD-84-B8-9E-2C-68-04-92-4A-8E-0B-12-D3-81-9E-A9-B2-78"
}, {
"Path": "System.Buffers.dll",
"Hashs": "BA-C3-4C-8A-68-C1-20-51-C6-F5-39-5C-5A-75-9D-7A-B5-19-A8-BA"
}, {
"Path": "System.Buffers.xml",
"Hashs": "7F-40-69-34-1C-6B-62-EC-FC-99-9A-6C-2D-8A-2D-5F-B5-9D-44-F6"
}, {
"Path": "System.Memory.dll",
"Hashs": "A8-8D-AB-84-A7-D3-13-BF-49-EE-01-C7-00-04-37-E5-7D-BB-A6-97"
}, {
"Path": "System.Memory.xml",
"Hashs": "CF-44-E6-55-7F-DE-93-28-8F-F2-56-7A-00-2A-69-27-99-65-CA-BA"
}, {
"Path": "System.Numerics.Vectors.dll",
"Hashs": "35-9F-9F-8A-3E-0E-E6-3F-9E-B6-BC-56-E3-BA-C3-00-C7-31-C0-80"
}, {
"Path": "System.Numerics.Vectors.xml",
"Hashs": "E2-A3-B3-AC-B3-80-A4-EB-62-6B-44-FF-6E-E0-4A-37-11-0A-33-89"
}, {
"Path": "System.Runtime.CompilerServices.Unsafe.dll",
"Hashs": "FD-55-A3-BC-6B-09-28-A0-29-B2-9D-D0-55-9F-ED-4C-E3-0B-79-D4"
}, {
"Path": "System.Runtime.CompilerServices.Unsafe.xml",
"Hashs": "E7-05-41-4B-E7-2B-48-66-BC-3A-D0-2B-95-29-65-60-14-C6-3C-B1"
}, {
"Path": "System.Text.Encodings.Web.dll",
"Hashs": "D5-9E-68-86-E0-1F-31-F5-6F-84-8A-5A-A7-28-19-0F-0C-27-AA-49"
}, {
"Path": "System.Text.Encodings.Web.xml",
"Hashs": "84-8E-B4-3B-4C-C5-37-51-FC-DC-27-B3-6B-A6-4B-AF-4B-E2-28-80"
}, {
"Path": "Test1.exe",
"Hashs": "92-04-71-3C-1B-B0-7B-05-EA-3F-46-71-4A-55-1D-E4-17-39-C1-23"
}, {
"Path": "Test1.exe.config",
"Hashs": "61-F0-51-D8-F3-00-F7-BA-4C-AA-36-CE-89-11-D0-EC-0F-17-5C-C4"
}, {
"Path": "Test1.pdb",
"Hashs": "E3-D6-DD-6B-AF-A4-FB-C8-4F-2C-65-96-D4-E4-B0-BA-8C-25-CD-3D"
}],
"Version": "1.0.0.0"
}
2.软件更新
打开 软件启动器
如果是第一次运行的话,会直接下载所有的文件
然后就可以看到这个Test1 所有项目文件
这时候,就完成了基本的更新功能了,当然,我们要做的功能不是下载文件就完事了,最主要的是更新的功能,现在我们更新服务器的版本看看。
在更新服务器文件之前,最好先把刚刚下载好的文件删除一些,不然软件启动器只会下载服务器端和本地端文件不一致的文件,删除后如下图
Test1.exe 相关的文件不要删除了,不然软件启动器会认为当前是第一次下载,将把所有的文件下载一遍。
3.下载最新的版本
打开 Test1的项目,将 Test1 的版本号改为 1.0.0.2
然后生成项目,将这三个文件复制到 D:\H\本地服务器工具\Software\News 目录下,就是刚刚搭建的服务器那个目录,这个根据个人的配置,目录不一定非得和我的一样。
然后用 更新文件列表生成器 重新生成 json 文件,可以打开json文件,看看版本是否和你设置的一致。
这时候,打开 软件启动器,就会发现弹出了是否更新的提示框
点击确定,由于文件比较小,所以下载的有点快
下载完成
在控制台同样有下载的日志
这样更新的工作就算完成了
4.打开软件本体
在这里需要更新一下 软件启动器的 Form1 的代码,如下
private void AllTaskEnd()
{
FormControlExtensions.InvokeIfRequired(this, () =>
{
this.Label_DownLog.Text = string.Empty;
this.Label_DownProgress.Text = string.Empty;
this.ProgressBar_DownProgress.Value = 0;
this.Label_DownLog.Visible = false;
this.ProgressBar_DownProgress.Visible = false;
//MessageBox.Show("所有任务完成");
Console.WriteLine("所有任务完成");
string path = Application.StartupPath + "\\Test1.exe";
string pwd = "密码";
string[] parameter = { pwd };
bool startResult = StartProcess(path, parameter);
if (startResult)
System.Environment.Exit(0);//退出软件启动器
});
}
/// <summary>
/// 启动一个软件,并传入参数
/// </summary>
/// <param name="runFilePath"></param>
/// <param name="args"></param>
/// <returns></returns>
public bool StartProcess(string runFilePath, params string[] args)
{
string s = "";
foreach (string arg in args)
{
s = s + arg + " ";
}
s = s.Trim();
Process process = new Process();//创建进程对象
ProcessStartInfo startInfo = new ProcessStartInfo(runFilePath, s); // 括号里是(程序名,参数)
process.StartInfo = startInfo;
process.Start();
return true;
}
再次重复和上节重复的操作,就会实现下面的效果
更新完成后,会自动打开软件本体,这样,我们所有的效果都实现了。
5.总结
虽然说效果基本是实现了,但是项目依然有不足的地方,需要好好打磨,比如,假如更新过程中突然断网了,就会出现更新了一部分,还有一部分没有更新完成,这样可能会导致软件本体打不开,或者打开后异常,我其实也想好好完善这些功能,但是,自己的工作中,各种烦人的事实在太多了,每次下班基本什么都不想弄了,这个帖子也一直拖了很久才更新完,如果你发现代码有那些bug ,或者需要完善的地方,欢迎评论告诉我,谢谢。
项目源码:点击下载
源码有错误,或者疑问的,可以随时私信给我,我看到了都会回复的,谢谢
结束
如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢
end
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)