【C#】C# Winform自动更新
目录
当前项目已停止维护,推荐使用 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
猜你喜欢
- 【C#】C# Winform自动更新
- 目录一、需求二、更新文件列表生成器三、软件启动器1.判断是否需要更新2.文件下载3.执行 下载,覆盖,删除任务4.执行结果四、搭建更新服务器1.启动服务器2.新建项目本体3.给启动软件加密4.修改版本号五、整体测试1.生成更新文件2.软件更新3.下载最新的版本4.打开软件本体5.总结结束当前项目已停止维护,推荐使用 FTP 版自动更新C# 自动更新(基于FTP)_c# 程序自动升级-CSDN博客一、需求在Unity里面,有XLua,ILRuntime 这样的热更新框架,有Unity经验的人都知道
- 【C#】C# Winform GDI+ 绘图
- 目录一、概述二、绘图1.画直线2.画矩形3.画圆、圆弧4.画扇形5.画多边形6.绘制字符串7.填充图形结束一、概述Graphics类是GDI+技术的一个基本类。GDI+(Graphics Device Interface)是.NET框架的重要组成部分,提供对二维图形图像和文字排版处理的支持。GDI+相关的类分布在下列命名空间中: System.Drawing:提供了最基本的绘图功能(比如画直线、矩形、椭圆等); System.Drawing.Drawing2D: 提供了高级的二维和矢量绘图功能(
- 【C#】C# Winform DataGridView 数据刷新问题
- 目录一、问题二、创建项目三、绑定空的数据源四、绑定有数据的数据源五、修改绑定的数据源六、解决数据源刷新问题七、解决刷新数据界面闪烁一、问题DataGridView 是比较常用的表格控件,在 DataGridView 中显示数据, 一般使用 dataGridView1.DataSource = 数据源,来绑定数据,数据源可以是 DataTable、List、Dictionary 等,那么如何做到及时刷新数据呢,这里我提出几个问题:1.绑定一个空的数据源,后面向数据源添加数据。2.Data
- 【C#】C# Winfrom Chart 图表控件 柱状图、折线图
- 目录一、参考二、柱状图1.新建项目2.修改图表样式3.绑定数据4.删除Series1图例1)使用属性窗体删除2)使用代码删除5.自定义X轴的刻度值1)使用List绑定2)使用LabelStyle.Format绑定6.自定义Y轴的刻度值7.X轴刻度值显示不全的解决方法8.修改X Y刻度值的字体样式9.X轴刻度值旋转90°10.禁用Y轴刻度线11.去掉Y轴刻度值12.改变柱子的宽度13.设置网格线的颜色14.设置网格线的线型三、折线图1.图表设置2.绑定数据结束效果:一、参考c# Chart设置样式
- 【C#】Winform NanUI 相关功能整合
- 目录NanUI 0.88版本 去掉启动界面(遮罩)NanUI 0.88版本 读取本地资源和嵌入式资源NanUI 0.77版本 打开控制台NanUI 0.77版本 C#调用多个参数的JS方法NanUI 0.77版本 传递数组参数NanUI 0.77版本 设置窗体全屏显示(类似Kiosk模式)NanUI 0.77版本 Bootstrap类 APINanUI 0.88版本 去掉启动界面(遮罩)启动界面是作者给我们显示公司产品的Logo用的,如果用不着可以去掉,但必须要更改源码,如果不想改源码,直接将启
- 【C#】CSDK/IDE-VSCode 搭建 C# 开发环境
- 最近准备写 C# 的笔记总结专栏 bug 笔记本硬盘空间实在是不够用了 根本没有办法再安装一个 Visual Studio 集成开发环境了!!! 在学 Java 的过程中基本都是用记事本和命令提示符……再也不想经历了 &nbs
- 【C#】C# Winform 文本面板带滚动条
- 在PC软件开发中经常有这样的需求,需要在一个固定大小的面板中显示一些内容,并且面板能上下拖动,将所有的内容完整的展示,有点类似网页上看新闻,如果要在 winfrom 中要如何实现的呢,下面就演示如何实现的吧效果:1.新建一个winform 项目,在界面中拖入一个Panel 将 panel1 的 AutoScroll 设置为 True2.再次拖入一个 Panel ,将高度拉长,这时就自动出现了滚动条,只是此时里面还没有任何内容,下面就在 panel2 中加入一点内容。
- 【C#】C# Winform 自定义进度条ProgressBar
- 效果:一、前言Winfrom各种老毛病真的不适合做大型项目,甚至中型项目都不适合,一些小功能都能把你折腾半死,比如,我想在界面上显示一个进度条,用来显示现在硬盘和内存已经使用了多少,使用了 ProgressBar 控件你看看效果:进度条中间一直有个白色光影在晃来晃去的,是不是想让别人感慨:“哇!好强的光芒,我的眼睛快睁不开了...”。而且背景颜色无法改变,这个动画也无法关掉,为了解决这两个问题,我找了很久,终于找到了下面的解决方法。二、自定义进度条于是我在网上找了一些资料,有到效果有,但不是特别