启明办公

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 68|回复: 0

一种简单的软件自动更新的实现方式

[复制链接]

1

主题

3

帖子

5

积分

新手上路

Rank: 1

积分
5
发表于 2023-1-17 04:03:59 | 显示全部楼层 |阅读模式
一、基本情况
最近的项目在做一个桌面程序,做完之后到客户机器上部署想到后续可能会新需求需要不定期程序,为了给客户最(lan)好(de)的(pao)体(xian)验(chang),想到做一个自动更新程序。简单的一顿百度,找到了这样一个项目 WPF客户端自动升级 比较简单,且对环境要求比较低,可以支持到XP。
原项目有些瑕疵,这里做了些改进,现记录如下。
1.开发环境:

c#+WPF
2.实现方式:


  • 双击版本更新程序;
  • 版本更新程序检测服务端版本配置信息并于本地版本配置信息比对;
  • 发现服务器端版本与本地版本不一致则自动下载新版本压缩包,版本一致则启动程序;
  • 新版本程序压缩包下载后解压缩覆盖本地文件,然后启动程序。
3.需要的依赖

1.原项目用到了压缩包处理工具库CL.IO.Zip,这里继续使用;
2.新增NLog依赖,记录操作日志,NuGet上可以获取。
4.准备工作

新建一个项目名称为:AutoUpdateTool

Talk is cheep, show you the code
二、上代码

1.版本配置信息xml对应的实体类
namespace AutoUpdateTool.Model
{
    public class UpdateInfo
    {
        /// <summary>
        /// 软件版本号
        /// </summary>
        public string Version { get; set; }
        /// <summary>
        /// 更新工具版本号
        /// </summary>
        public string ToolVersion { get; set; }
        /// <summary>
        ///  更新工具url
        /// </summary>
        public string AutoUpdateToolUrl { get; set; }
        /// <summary>
        /// 版本配置xml
        /// </summary>
        public string VersionXml { get; set; }
        /// <summary>
        /// 更新信息
        /// </summary>
        public string UpdateContent { get; set; }
        /// <summary>
        /// 更新包的下载地址
        /// </summary>
        public string DownLoadUrl { get; set; }
        /// <summary>
        /// 启动脚本名称
        /// </summary>
        public string StartName { get; set; }
        /// <summary>
        /// 关闭脚本名称
        /// </summary>
        public string StopName { get; set; }
        /// <summary>
        /// 下载文件进度显示的计量单位.GB/MB/KB/Byte
        /// </summary>
        public string DownloadFileUnit { get; set; }
        /// <summary>
        /// 更新时间
        /// </summary>
        public string Time { get; set; }
        /// <summary>
        /// 更新包大小
        /// </summary>
        public string Size { get; set; }
    }
}
2.xml操作接口及实现
using System.Collections.Generic;
using System.Xml.Linq;

namespace AutoUpdateTool.Class
{
    interface IXmlHelper
    {
        void SaveToXml(string path,Dictionary<string,string> dic);
        XDocument ReadFromXml(string path);
        XElement ReadFromXml(XDocument document);
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.IO;
using AutoUpdateTool.Model;
namespace AutoUpdateTool.Class
{
    /// <summary>
    /// xml帮助
    /// </summary>
    public class XmlHelper : IXmlHelper
    {
        private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
        private static object _insLocker = new object();//实例化对象锁
        private static XmlHelper ins;
        public static XmlHelper Instance
        {
            get
            {
                if (ins == null)
                {
                    lock (_insLocker)
                    {
                        if (ins == null)
                        {
                            ins = new XmlHelper();
                        }
                    }
                }
                return ins;
            }
        }
        public void SaveToXml(string path, Dictionary<string, string> dic)
        {
            if (string.IsNullOrEmpty(path) || dic == null)
            {
                Logger.Error("保存配置文件失败,路径或者内容为空");
                return;
            }
            //获取根节点对象
            XDocument document = new XDocument();
            XElement root = new XElement("AutoConfig");
            foreach (var item in dic)
            {
                root.SetElementValue(item.Key, item.Value);
            }
            root.Save(path);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="path">文件的路径</param>
        /// <param name="info"></param>
        public void SaveToXml(string path, List<UpdateInfo> info)
        {
            if (string.IsNullOrEmpty(path) || info == null)
            {
                Logger.Error("保存配置文件失败,路径或者内容为空");
                return;
            }
            //获取根节点对象
            XDocument document = new XDocument();
            XElement root = new XElement("AutoConfig");
            foreach (var item in info)
            {
                root.SetElementValue("Version", item.Version);
                root.SetElementValue("UpdateContent", item.UpdateContent);
                root.SetElementValue("DownLoadUrl", item.DownLoadUrl);
                root.SetElementValue("Time", item.Time);
                root.SetElementValue("Size", item.Size);
                root.SetElementValue("DownloadFileUnit", item.DownloadFileUnit);
                root.SetElementValue("StartName", item.StartName);
                root.SetElementValue("StopName", item.StopName);
                root.SetElementValue("AutoUpdateToolUrl", item.AutoUpdateToolUrl);
                root.SetElementValue("ToolVersion", item.ToolVersion);
                root.SetElementValue("VersionXml", item.VersionXml);
            }
            root.Save(path);
        }
        /// <summary>
        /// 读取xml,返回XDocument对象
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public XDocument ReadFromXml(string path)
        {
            try
            {
                if (!File.Exists(path))
                {
                    Logger.Error("读取配置文件失败,路径为空");
                    return null;
                }
                //将XML文件加载进来
                XDocument document = XDocument.Load(path);
                return document;
            }
            catch (Exception ex)
            {
                Logger.Error("读取配置文件失败,"+ex.Message);
            }
            return null;
        }
        /// <summary>
        /// 从XDocument读取并返回根元素
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public XElement ReadFromXml(XDocument document)
        {
            if (document == null)
            {
                Logger.Error("读取配置文件失败,XDocument为空");
                return null;
            }
            //获取到XML的根元素进行操作
            XElement root = document.Root;
            return root;
        }
        /// <summary>
        /// 从xml读取用户新建稿件配置
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public Dictionary<string, string> ReadConfigOfManuscript(string path)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>();
            XDocument doc = ReadFromXml(path);
            XElement root = ReadFromXml(doc);
            if (root == null)
            {
                Logger.Error("读取配置文件失败,xml文件内容为空");
                return null;
            }
            //获取根元素下的所有子元素
            IEnumerable<XElement> enumerable = root.Elements();
            foreach (XElement item in enumerable)
            {
                dic.Add(item.Name.ToString(), item.Value);
            }
            return dic;
        }
        /// <summary>
        /// 从流中读取版本信息
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public UpdateInfo ReadVersionConfig(Stream s)
        {
            XDocument document = XDocument.Load(s);
            XElement root = ReadFromXml(document);
            if (root == null)
            {
                Logger.Error("读取版本信息失败,配置文件内容为空");
                return null;
            }
            //获取根元素下的所有子元素
            IEnumerable<XElement> enumerable = root.Elements();
            UpdateInfo info = new UpdateInfo()
            {
                DownLoadUrl = enumerable.Where(p => p.Name == "DownLoadUrl").Select(x => x.Value).First(),
                Version = enumerable.Where(p => p.Name == "Version").Select(x => x.Value).First(),
                Size = enumerable.Where(p => p.Name == "Size").Select(x => x.Value).First(),
                Time = enumerable.Where(p => p.Name == "Time").Select(x => x.Value).First(),
                UpdateContent = enumerable.Where(p => p.Name == "UpdateContent").Select(x => x.Value).First(),
                DownloadFileUnit = enumerable.Where(p => p.Name == "DownloadFileUnit").Select(x => x.Value).First(),
                StartName = enumerable.Where(p => p.Name == "StartName").Select(x => x.Value).First(),
                StopName = enumerable.Where(p => p.Name == "StopName").Select(x => x.Value).First(),
                AutoUpdateToolUrl = enumerable.Where(p => p.Name == "AutoUpdateToolUrl").Select(x => x.Value).First(),
                ToolVersion = enumerable.Where(p => p.Name == "ToolVersion").Select(x => x.Value).First(),
                VersionXml = enumerable.Where(p => p.Name == "VersionXml").Select(x => x.Value).First()
            };
            return info;
        }
    }
}
3.文件下载、解压缩、拷贝相关操作
using System;
using System.Net;
using System.IO;
using AutoUpdateTool.Model;
using CL.IO.Zip;
using System.ComponentModel;

namespace AutoUpdateTool.Class
{
    public class DownloadHelper
    {
        private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

        public static AutoUpdate dispatcher;
        /// <summary>
        /// 获取版本信息
        /// </summary>
        /// <param name="url">版本信息文件的url</param>
        /// <returns></returns>
        public static Tuple<bool, UpdateInfo> GetLocalConfigInfo(string path)
        {
            try
            {
                if (string.IsNullOrEmpty(path))
                {
                    return new Tuple<bool, UpdateInfo>(false, null);
                }
                FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

                byte[] buffer = new byte[fs.Length];
                fs.Read(buffer, 0, buffer.Length);
                Stream s = new MemoryStream(buffer);
                UpdateInfo info = XmlHelper.Instance.ReadVersionConfig(s);
                s.Close();
                return new Tuple<bool, UpdateInfo>(true, info);
            }
            catch (Exception ex)
            {
                Logger.Error("获取本地版本信息失败:" + ex.Message);
                return new Tuple<bool, UpdateInfo>(false, null);
            }
        }
        /// <summary>
        /// 获取版本信息
        /// </summary>
        /// <param name="url">版本信息文件的url</param>
        /// <returns></returns>
        public static Tuple<bool, UpdateInfo> GetConfigInfo(string url)
        {
            try
            {
                if (string.IsNullOrEmpty(url))
                {
                    return new Tuple<bool, UpdateInfo>(false, null);
                }
                WebClient client = new WebClient();
                Stream s = client.OpenRead(new Uri(url));
                UpdateInfo info = XmlHelper.Instance.ReadVersionConfig(s);
                s.Close();
                return new Tuple<bool, UpdateInfo>(true, info);
            }
            catch (Exception ex)
            {
                Logger.Error("获取远端版本信息失败:" + ex.Message);
                return new Tuple<bool, UpdateInfo>(false, null);
            }
        }

        /// <summary>
        /// 解压缩,拷贝,删除
        /// </summary>
        /// <param name="sourcePath">zip的路径</param>
        /// <param name="targetPath">目的路径</param>
        /// <param name="pBar">ProgressBar显示进度</param>
        /// <returns></returns>
        public static bool UnZip(string sourcePath, string targetPath)
        {
            try
            {
                ZipHandler handler = ZipHandler.GetInstance();
                string zipFile = Path.Combine(sourcePath, "temp.zip");
                string extractPath = Path.Combine(targetPath, "temp");
                handler.UnpackAll(zipFile, extractPath, (num) =>
                {
                    dispatcher.UpdateUI(null, num);
                });
                //将临时文件夹下的文件复制到原程序路径中
                CopyDirectory(extractPath, sourcePath);//注意,此时临时文件夹为源地址,sourcePath为目标地址
                File.Delete(zipFile);//删除zip文件
                Directory.Delete(Path.Combine(targetPath, "temp"), true);
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("解压缩、拷贝、删除操作失败:"+ex.Message);
                return false;
            }
        }

        /// <summary>
        /// 下载zip文件
        /// </summary>
        /// <param name="zipUrl">zip的url</param>
        /// <param name="targetDirPath">目标文件夹路径</param>
        /// <returns></returns>
        public static bool DownloadZip(string zipUrl, string targetDirPath)
        {
            string zipFile = Path.Combine(targetDirPath, "temp.zip");
            if (!Directory.Exists(targetDirPath))
            {
                return false;
            }
            try
            {
                WebClient client = new WebClient();
                client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Client_DownloadProgressChanged);
                client.DownloadFileCompleted += new AsyncCompletedEventHandler(Client_DownloadFileCompleted);
                client.DownloadFileAsync(new Uri(zipUrl), zipFile);
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("文件下载失败:" + ex.Message);
                if (dispatcher != null)
                    dispatcher.HandleDownloadFail();
                return false;
            }
        }

        private static void Client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            if (dispatcher != null)
                dispatcher.HandleDownloadSucess();
        }

        private static void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            if (dispatcher != null)
            {
                dispatcher.UpdateUI(null, e.ProgressPercentage);
            }
        }

        /// <summary>
        /// 下载xml配置
        /// </summary>
        /// <param name="url"></param>
        /// <param name="targetPath"></param>
        /// <returns></returns>
        public static bool DownLoadXMLConfig(string url, string targetPath)
        {
            try
            {
                var xmlPath = Path.Combine(targetPath, "VersionConfig.xml");
                WebClient client = new WebClient();
                client.DownloadFile(new Uri(url), xmlPath);
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("配置文件下载失败:" + ex.Message);
                return false;
            }
        }
        /// <summary>
        /// 获取Zip的总大小
        /// </summary>
        /// <param name="zipUrl"></param>
        /// <returns></returns>
        public static double GetZipTotalSize(string zipUrl)
        {

            try
            {
                WebClient client = new WebClient();
                if (dispatcher != null)
                    dispatcher.UpdateUI("正在获取文件大小……", 0);
                byte[] sr = client.DownloadData(new Uri(zipUrl));
                return sr.Length;
            }
            catch (Exception ex)
            {
                Logger.Error("获取文件大小失败:" + ex.Message);
                return 0;
            }
        }
        /// <summary>
        /// 递归copy文件
        /// </summary>
        /// <param name="sourcePath"></param>
        /// <param name="targetPath"></param>
        private static void CopyDirectory(string sourcePath, string targetPath)
        {
            try
            {
                if (!Directory.Exists(targetPath))
                {
                    Directory.CreateDirectory(targetPath);
                }
                string[] files = Directory.GetFiles(sourcePath);//Copy文件
                foreach (string file in files)
                {
                    try
                    {
                        string pFilePath = targetPath + "\\" + Path.GetFileName(file);
                        File.Copy(file, pFilePath, true);
                    }
                    catch (Exception ex)
                    {
                        Logger.Error("文件拷贝失败:[" + Path.GetFileName(file) + "]" + ex.Message);
                        continue;
                    }
                }

                string[] dirs = Directory.GetDirectories(sourcePath);//Copy目录
                foreach (string dir in dirs)
                {
                    CopyDirectory(dir, targetPath + "\\" + Path.GetFileName(dir));
                }
            }
            catch (Exception ex)
            {
                Logger.Error("文件拷贝失败:" + ex.Message);
            }
        }
    }
}
4.主界面
<Window x:Class="AutoUpdateTool.AutoUpdate"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="软件更新(2.01.09)" Height="350" Width="525" Background="AntiqueWhite" Loaded="Window_Loaded">
    <Grid>
        <Grid.Resources>
            <Style TargetType="{x:Type ProgressBar}">
                <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
                <Setter Property="SnapsToDevicePixels" Value="True"/>
                <Setter Property="Height" Value="15"/>
                <Setter Property="Background" Value="#6fae5f"/>
                <Setter Property="FontSize" Value="10"/>
                <Setter Property="Padding" Value="5,0"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ProgressBar}">
                            <Grid Background="#00000000">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <VisualStateManager.VisualStateGroups>
                                    <VisualStateGroup x:Name="CommonStates">
                                        <VisualState x:Name="Determinate"/>
                                        <VisualState x:Name="Indeterminate">
                                            <Storyboard RepeatBehavior="Forever">
                                                <PointAnimationUsingKeyFrames Storyboard.TargetName="Animation" Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)">
                                                    <EasingPointKeyFrame KeyTime="0:0:0" Value="0.5,0.5"/>
                                                    <EasingPointKeyFrame KeyTime="0:0:1.5" Value="1.95,0.5"/>
                                                    <EasingPointKeyFrame KeyTime="0:0:3" Value="0.5,0.5"/>
                                                </PointAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                    </VisualStateGroup>
                                </VisualStateManager.VisualStateGroups>

                                <Grid Height="{TemplateBinding Height}">
                                    <Border Background="#000000" CornerRadius="7.5" Opacity="0.05"/>
                                    <Border BorderBrush="#000000" BorderThickness="1" CornerRadius="7.5" Opacity="0.1"/>
                                    <Grid Margin="{TemplateBinding BorderThickness}">
                                        <Border x:Name="PART_Track"/>
                                        <Grid x:Name="PART_Indicator" ClipToBounds="True" HorizontalAlignment="Left" >
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition x:Name="width1"/>
                                                <ColumnDefinition x:Name="width2" Width="0"/>
                                            </Grid.ColumnDefinitions>
                                            <Grid x:Name="Animation"  RenderTransformOrigin="0.5,0.5">
                                                <Grid.RenderTransform>
                                                    <TransformGroup>
                                                        <ScaleTransform ScaleY="-1" ScaleX="1"/>
                                                        <SkewTransform AngleY="0" AngleX="0"/>
                                                        <RotateTransform Angle="180"/>
                                                        <TranslateTransform/>
                                                    </TransformGroup>
                                                </Grid.RenderTransform>
                                                <Border Background="{TemplateBinding Background}" CornerRadius="7.5">
                                                    <Viewbox HorizontalAlignment="Left" StretchDirection="DownOnly" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="True">
                                                        <TextBlock Foreground="#ffffff" SnapsToDevicePixels="True" FontSize="{TemplateBinding FontSize}" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Value,StringFormat={}{0}%}" RenderTransformOrigin="0.5,0.5">
                                                            <TextBlock.RenderTransform>
                                                                <TransformGroup>
                                                                    <ScaleTransform ScaleY="1" ScaleX="-1"/>
                                                                    <SkewTransform AngleY="0" AngleX="0"/>
                                                                    <RotateTransform Angle="0"/>
                                                                    <TranslateTransform/>
                                                                </TransformGroup>
                                                            </TextBlock.RenderTransform>
                                                        </TextBlock>
                                                    </Viewbox>
                                                </Border>
                                                <Border BorderBrush="#000000" BorderThickness="1" CornerRadius="7.5" Opacity="0.1"/>
                                            </Grid>
                                        </Grid>
                                    </Grid>
                                </Grid>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsEnabled" Value="False">
                                    <Setter Property="Background" Value="#c5c5c5"/>
                                </Trigger>
                                <Trigger Property="IsIndeterminate" Value="true">
                                    <Setter TargetName="width1" Property="Width" Value="0.25*"/>
                                    <Setter TargetName="width2" Property="Width" Value="0.725*"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Grid.Resources>
        <StackPanel>
            <Label>当前进度:</Label>
            <ProgressBar Height="20" Margin="0,0" Name="proBar"></ProgressBar>
            <Label Name="lb_Status" HorizontalAlignment="Center"></Label>
            <Border BorderBrush="Aquamarine"  BorderThickness="1" CornerRadius="10" Margin="0,30">
                <TextBox BorderThickness="0" Name="tb_IntroduceContent"  Background="Transparent"  VerticalAlignment="Center" HorizontalContentAlignment="Left" Text="版本新功能:" TextWrapping="Wrap"></TextBox>
            </Border>
        </StackPanel>
    </Grid>
</Window>
using AutoUpdateTool.Class;
using AutoUpdateTool.Model;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace AutoUpdateTool
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class AutoUpdate : Window
    {
        private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
        Tuple<bool, UpdateInfo> configLocal;
        public AutoUpdate()
        {
            InitializeComponent();
        }

        #region Filed
        string sourceUrl = string.Empty;//资源url路径
        string zipUnit = "KB";//下载文件进度显示的计量单位
        string versonCode = "";//版本号
        double zipTotalSize = 0;
        #endregion

        #region private method

        /// <summary>
        /// 更新初始化
        /// </summary>
        private void Init()
        {
            try
            {
                Dispatcher.Invoke(new Action(() =>
                {
                    lb_Status.Content = "正在获取新版本信息,请耐心等待";
                }));
                configLocal = GetVersionInfo(AppDomain.CurrentDomain.BaseDirectory);//获取本地上的配置文件
                if (!configLocal.Item1)
                {
                    this.Dispatcher.Invoke(new Action(() =>
                    {
                        lb_Status.Content = "本地配置文件读取失败,请联系管理员!";
                    }));
                    return;
                }
                else
                {
                    sourceUrl = configLocal.Item2.DownLoadUrl;
                    zipUnit = configLocal.Item2.DownloadFileUnit;
                    versonCode = configLocal.Item2.Version;
                }
                this.Dispatcher.Invoke(new Action(() =>
                {
                    Title = "软件更新(" + versonCode + ")";
                }));
                var remoteXmlPath = configLocal.Item2.VersionXml;
                var resultRemote = GetVersionInfo(remoteXmlPath);
                if (!resultRemote.Item1)
                {
                    this.Dispatcher.Invoke(new Action(() =>
                    {
                        lb_Status.Content = "服务器端配置文件读取失败,请联系管理员!";
                    }));
                    return;
                }
                if (versonCode.Equals(resultRemote.Item2.Version))
                {
                    RefreshConfigAndRestart();
                    return;
                }
                if (resultRemote.Item1)
                {
                    this.Dispatcher.Invoke(new Action(() =>
                    {
                        tb_IntroduceContent.Text = resultRemote.Item2.UpdateContent;
                    }));
                }
                this.Dispatcher.Invoke(new Action(() =>
                {
                    Title = "软件更新(" + versonCode + "->" + resultRemote.Item2.Version + ")";
                    lb_Status.Content = "获取新版本信息成功,开始下载文件!";
                }));
                DownloadHelper.dispatcher = this;
                GetZipFileTotalSize();//获取zip文件总大小

                string dir = Directory.GetCurrentDirectory();

                //下载zip
                var flag = DownloadHelper.DownloadZip(sourceUrl, dir);
            }
            catch (Exception ex)
            {
                Logger.Error("更新初始化相关操作失败:" + ex.Message);
                MessageBox.Show(ex.StackTrace);
            }
        }
        /// <summary>
        /// 更新配置文件并重启应用
        /// </summary>
        private void RefreshConfigAndRestart()
        {
            var dirPath = AppDomain.CurrentDomain.BaseDirectory;
            var xmlConfig = DownloadHelper.GetLocalConfigInfo(dirPath + @"VersionConfig.xml");
            if (xmlConfig.Item1 && !string.IsNullOrEmpty(xmlConfig.Item2.StartName))
            {
                CallStop(Path.Combine(dirPath, xmlConfig.Item2.StopName));
                var appDomainName = xmlConfig.Item2.StartName;
                var localToolPath = Path.Combine(dirPath, appDomainName);
                if (StartMainProcess(localToolPath))
                {
                    this.Dispatcher.Invoke(new Action(() =>
                    {
                        Application.Current.Shutdown();
                    }));
                };
            }
        }
        private void CallStop(string scriptPath)
        {
            try
            {
                Process.Start(scriptPath);
                Thread.Sleep(1000);
            }
            catch (Exception ex)
            {
                Logger.Error("调用关闭脚本(" + scriptPath + ")失败!" + ex.Message);
            }
        }
        /// <summary>
        /// 启动主程序
        /// </summary>
        /// <param name="path"></param>
        private bool StartMainProcess(string path)
        {
            try
            {
                Logger.Info(path);
                Process.Start(path);
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("调用启动脚本(" + path + ")失败!" + ex.Message);
                return false;
            }
        }
        /// <summary>
        /// 获取版本信息
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private Tuple<bool, UpdateInfo> GetVersionInfo(string path)
        {
            var versionUrl = string.Empty;
            if (path.Contains("VersionConfig.xml"))
            {
                versionUrl = path;
            }
            else
            {
                versionUrl = path + "VersionConfig.xml";
            }
            if (path.StartsWith("http"))
                return DownloadHelper.GetConfigInfo(versionUrl);
            else
                return DownloadHelper.GetLocalConfigInfo(versionUrl);
        }
        /// <summary>
        /// 获取文件大小
        /// </summary>
        private void GetZipFileTotalSize()
        {
            zipTotalSize = DownloadHelper.GetZipTotalSize(sourceUrl);
            switch (zipUnit)
            {
                case "KB":
                    zipTotalSize = zipTotalSize / 1024;//KB计算
                    break;
                case "MB":
                    zipTotalSize = zipTotalSize / 1024 / 1024;//KB计算
                    break;
                case "GB":
                    zipTotalSize = zipTotalSize / 1024 / 1024 / 1024;//KB计算
                    break;
            }
            zipTotalSize = Math.Round(zipTotalSize, 2);
            this.Dispatcher.Invoke(new Action(() =>
            {
                lb_Status.Content = "0/" + zipTotalSize + zipUnit;
            }));
        }
        #endregion

        #region public method
        public void UpdateUI(string status, double process)
        {

            this.Dispatcher.Invoke(new Action(() =>
            {
                if (string.IsNullOrEmpty(status))
                    lb_Status.Content = status;
                if (process >= 0)
                    proBar.Value = process;

            }));
        }

        public void HandleDownloadFail()
        {
            this.Dispatcher.Invoke(new Action(() =>
            {
                lb_Status.Content = "下载新版本文件失败,请重试";
            }));
        }
        public void HandleDownloadSucess()
        {
            var dirPath = AppDomain.CurrentDomain.BaseDirectory;
            var xmlConfig = DownloadHelper.GetLocalConfigInfo(dirPath + @"VersionConfig.xml");
            if (xmlConfig.Item1 && !string.IsNullOrEmpty(xmlConfig.Item2.StopName))
            {
                CallStop(Path.Combine(dirPath, xmlConfig.Item2.StopName));
            }

            //解压zip
            bool unZipFlag = false;
            this.Dispatcher.Invoke(new Action(() =>
            {
                proBar.Value = 0;
                lb_Status.Content = "正在解压文件...";
            }));

            string dir = Directory.GetCurrentDirectory();

            Task unzipTask = new Task(() =>
            {
                try
                {
                    var flag = DownloadHelper.UnZip(dir, dir);
                    unZipFlag = flag;
                }
                catch (Exception ex)
                {
                    Logger.Error("解压缩失败!" + ex.Message);
                    unZipFlag = false;
                }
            });
            unzipTask.Start();
            Task.WaitAll(unzipTask);

            if (unZipFlag)
            {
                this.Dispatcher.Invoke(new Action(() =>
                {
                    lb_Status.Content = "解压文件完成!";
                }));
                bool xmlOk = DownloadHelper.DownLoadXMLConfig(configLocal.Item2.VersionXml, AppDomain.CurrentDomain.BaseDirectory);
                if (!xmlOk)
                {
                    MessageBox.Show("配置文件下载失败");
                    return;
                }
                this.Dispatcher.Invoke(new Action(() =>
                {
                    lb_Status.Content = "更新完成";
                }));

                var result = MessageBox.Show("是否重新启动应用?", "提示", MessageBoxButton.YesNo, MessageBoxImage.Information);
                if (result == MessageBoxResult.Yes)
                {
                    this.Dispatcher.Invoke(new Action(() =>
                    {
                        lb_Status.Content = "正在重启应用,请稍后!";
                    }));
                    RefreshConfigAndRestart();
                }
            }
            else
            {
                this.Dispatcher.Invoke(new Action(() =>
                {
                    lb_Status.Content = "更新失败,请重试!";
                }));
            }
        }
        #endregion
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            Task task = new Task(() =>
            {
                Init();
            });
            task.Start();
        }
    }
}
5.NLog配置
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

        <targets>
                <!--此部分中的所有目标将自动异步-->
                <target name="asyncFile" xsi:type="AsyncWrapper">
                        <!--项目日志保存文件路径说明fileName="${basedir}/保存目录,以年月日的格式创建/${shortdate}/${记录器名称}-${单级记录}-${shortdate}.txt"-->
                        <target name="log_file" xsi:type="File"
                                        fileName="${basedir}/UpdateLogs/ProjectLogs/${shortdate}/${logger}-${level}-${shortdate}.txt"
                                        layout="${longdate} | ${message} ${onexception:${exception:format=message} ${newline} ${stacktrace} ${newline}"
                                        archiveFileName="${basedir}/UpdateLogs/archives/${logger}-${level}-${shortdate}-{#####}.txt"
                                        archiveAboveSize="102400"
                                        archiveNumbering="Sequence"
                                        concurrentWrites="true"
                                        keepFileOpen="false" />
                </target>
                <!--使用可自定义的着色将日志消息写入控制台-->
                <target name="colorConsole" xsi:type="ColoredConsole" layout="[${date:format=HH\:mm\:ss}]:${message} ${exception:format=message}" />
        </targets>

        <!--规则配置,final - 最终规则匹配后不处理任何规则-->
        <rules>
                <logger name="Microsoft.*" minlevel="Info" writeTo="asyncFile" final="true" />
                <logger name="*" minlevel="Info" writeTo="asyncFile" />
                <logger name="*" minlevel="Warn" writeTo="colorConsole" />
        </rules>
</nlog>6.版本配置信息
<?xml version="1.0" encoding="utf-8" ?>
<root>
        <Version>0.9</Version>
        <ToolVersion>0.9</ToolVersion>
        <AutoUpdateToolUrl>http://xxxx/xxx.zip</AutoUpdateToolUrl>
        <VersionXml>http://xxxx/VersionConfig.xml</VersionXml>
        <UpdateContent>第一个版本哦</UpdateContent>
        <DownLoadUrl>http://xxx/xxx.zip</DownLoadUrl>
        <StartName>xxx.exe</StartName>
        <StopName>stop.bat</StopName>
        <DownloadFileUnit>KB</DownloadFileUnit>
        <Time>2021-06-30 11:30:00</Time>
        <Size>1000</Size>
</root>其中:
1.StartName是启动主程序的脚本,可设为主程序的exe或者自行编写bat脚本;
2.StopName为关闭主程序的脚本,需自行编写脚本,以下是参考:
@echo off
taskkill /f /t /im Trans.exe
exit
代码完毕,祝实验顺利。
一个遗留的问题:更新软件自身怎么更新?


更多内容请访问
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|天恒办公

Copyright © 2001-2013 Comsenz Inc.Template by Comsenz Inc.All Rights Reserved.

Powered by Discuz!X3.4

快速回复 返回顶部 返回列表