目录

一、需求

二、更新文件列表生成器

三、软件启动器

1.判断是否需要更新

2.文件下载

3.执行 下载,覆盖,删除任务

4.执行结果

四、搭建更新服务器

1.启动服务器

2.新建项目本体

3.给启动软件加密

4.修改版本号

五、整体测试

1.生成更新文件

2.软件更新

3.下载最新的版本

4.打开软件本体

5.总结

结束


当前项目已停止维护,推荐使用 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

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐