如何做好软件自动更新

这里说的是C S客户端更新,其他情况不做讨论1 更新程序减少依赖之前做过一次更相信需要更新Newtonsoft Json dll 的版本 但是我的更新程序引用

这里说的是C/S客户端更新,其他情况不做讨论

  • 1.更新程序减少依赖

之前做过一次更相信需要更新Newtonsoft.Json.dll 的版本

但是我的更新程序引用了这个文件 也就是说执行更新程序的时候这个文件是占用状态,更新不了!

所以更新程序最好是零引用,只用系统自带的dll类库就可以,尽可能【小】

  • 2.更新的脚本

软件更新有时候需要对系统设置进行一些更改,例如注册表,防火墙之类的,这个时候就需要执行一些脚本来写入注册表或开启防火墙端口

这里可以设定一个执行脚本,需要注意的是和更新程序一样,脚本的引用支持程序要简单,你不能为了执行脚本就要用户装一个脚本解析器。

额外说一句虽然vbs脚本系统默认都是认的,但是我也有遇到过【没有找到文件扩展VBS的脚本引擎】这样的错误,虽然可以修复这个错误但是也需要尽可能避免这个问题,用户不会在乎错误原因是什么,只会认为:“我用了你的系统报了一个错误”

  • 3.容灾

更新不一定能够绝对成功的,就是你的代码再完美,用户操作可能不合法,可能被杀软拦截,可能操作系统本身就有问题,等等意外,这个时候首先尽可能保证更新失败也可以正常使用系统,如果旧版本确实放弃了,无法使用,可以给出友好提示,联系管理员之类的。

  • 4.版本检查

如果不是最新版本不允许使用软件系统,软件有一个BUG不修复的话会导致系统异常,也就是不更新软件没法使用,这个时候需要检查版本,如果版本不对则直接禁止使用。

这个功能比容灾更复杂一些,首先为了保证设定有效控制,需要将限制放在服务端,然后客户端使用的时候将自己的版本号告诉服务端,服务端检查后决定是否放行。

版本号分为主程序版本号以及引用文件版本号,可能主程序版本号没有问题,但是某一个功能版本号没有更新,则这个功能无法使用,其他功能不受影响

  • 5.方案

这里记录一下我正在使用的方案,有不少不足之处,先记下来,后面优化

首先服务器上放一个txt文件,更新时读取这个txt文件

txt里面包含软件最新版本,软件每个需要用到的文件的版本,用于比较最新版本然后下载有更新的文件

为了生成这样的txt文件我还写了一个生成的小工具

然后时vbs脚本放在服务器端,更新时下载到客户端执行(如上文描述,有部分客户端操作系统提示错误【没有找到文件扩展VBS的脚本引擎】,后面准备用bat或exe)

vbs里面是一个延时执行的代码,比如【更新程序】时a.exe 如果要更新【更新程序】本身则将新版本的【更新程序】重命名为a2.exe放在服务端,然后下载到客户端,因为延时程序设定在a.exe退出后删除a.exe然后将a2.exe重命名为a.exe,以实现更新自身

同时vbs里面也有删除函数,用来清除多余的文件

容灾方案正在设计中……

设想:将旧版本重命名,追加【.old】,然后将新版本文件下载追加【.new】,所有下载完成之后再统一删除重命名,如果出错则把【.old】恢复

还没有实践,因为加了这个功能本质上违反了【简单】的原则,功能越多越容易发生问题,先将方案实现看看效果

 

-----------------------正在使用的方案的代码

/*******************************************************************************
 * Copyright © 2010-2020  陈恩点版权所有
 * Author: 陈恩点
 * First Create: 2012/8/21 11:49:53
 * Contact: 18115503914
 * Description: MyRapid快速开发框架
*********************************************************************************/
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
namespace MyRapid.Launcher
{
    public partial class MyWait : Form
    {
        Process process;
        string ProcessName = "";
        string ScriptName = "";
        string ImagePath = "";
        string FormName = "主窗体";
        string urlPath = "";
        int WaitSecond = 30;
        Stopwatch sw = new Stopwatch();

        public MyWait()
        {
            sw.Restart();
            InitializeComponent();
            //用参数把4个变量传入
            //分别为
            //1 ProcessName 程序路径
            //2 ImagePath 动画路径
            //3 ScriptName 预更新脚本
            //4 FormName 启动程序的标识,用于判断程序是否加在完毕
            ProcessName = GetVal("exePath");
            ScriptName = GetVal("vbsPath");
            ImagePath = GetVal("imgPath");
            FormName = GetVal("exeTitle");

            MoveHandle(label1);
            MoveHandle(this);
            MoveHandle(progressBar1);
            if (File.Exists("tips.txt"))
            {
                tips = File.ReadAllLines("tips.txt");
            }
        }

        private void UpdateFile()
        {
            try
            {

                DateTime ver = DateTime.Parse(GetVal("version"));
                WebClient wc = new WebClient();
                wc.Encoding = Encoding.UTF8;
                label1.Text = "正在检查更新...";
                string updateFile = "update.txt";
                urlPath = GetVal("urlPath");
                wc.DownloadFile(urlPath, updateFile);
                if (!File.Exists(updateFile)) return;
                string[] verString = File.ReadAllLines(updateFile);
                if (verString.Length == 0) return;
                if (DateTime.Parse(verString[0]) <= ver) return;

                string fName = Path.GetFileNameWithoutExtension(ProcessName);
                fName = Path.GetFileName(ProcessName);
                var prcList = Process.GetProcesses().Where(pr => pr.ProcessName == Path.GetFileNameWithoutExtension(ProcessName) || pr.ProcessName == Path.GetFileName(ProcessName));
                if (prcList.Count() > 0)
                {
                    label1.Text = "主程序运行中,无法更新...";
                    return;
                }

                progressBar1.Style = ProgressBarStyle.Continuous;
                progressBar1.Maximum = verString.Length;
                progressBar1.Value = 0;
                foreach (string sf in verString)
                {
                    //Tips
                    if (tips != null && tips.Length > 0 && sw.Elapsed.TotalSeconds % 5 == 1)
                    {
                        int i = DateTime.Now.Millisecond % (tips.Length - 1);
                        label3.Text = string.Format(tip, tips[i]);
                        this.Refresh();
                    }

                    if (progressBar1.Value < verString.Length)
                        progressBar1.Value += 1;
                    if (!sf.Contains("|")) continue;
                    string[] ups = sf.Split('|');

                    if (DateTime.Parse(ups[0]) <= ver) continue;


                    //如果目录不存在这创建
                    string fDir = Path.GetDirectoryName(ups[1]);
                    if (!string.IsNullOrEmpty(fDir) && !Directory.Exists(fDir))
                    {
                        Directory.CreateDirectory(fDir);
                    }
                    //下载文件
                    label1.Text = ups[1];
                    this.Refresh();
                    wc.DownloadFile(ups[2], ups[1]);
                }

                SetVal("version", DateTime.Now.ToString());

                label1.Text = "更新结束:正在启动主程序...";
                progressBar1.Style = ProgressBarStyle.Marquee;
            }
            catch (Exception ex)
            {
                label1.Text += "更新失败,请重试或联系管理员协助处理:" + ex.Message;
            }
        }

        private void StartMain()
        {
            //执行预更新脚本
            if (File.Exists(ScriptName))
            {
                if (ScriptName.EndsWith(".vbs"))
                {
                    ProcessStartInfo startInfo = new ProcessStartInfo();
                    startInfo.FileName = "wscript.exe";
                    startInfo.Arguments = ScriptName;
                    Process.Start(startInfo);
                }
                else
                {
                    Process script = Process.Start(ScriptName);
                }

            }
            if (File.Exists(ProcessName))
            {
                //启动程序
                ProcessStartInfo processStartInfo = new ProcessStartInfo();
                processStartInfo.FileName = ProcessName;
                processStartInfo.WorkingDirectory = Path.GetDirectoryName(ProcessName);
                process = Process.Start(processStartInfo);
            }
            else
            {
                Environment.Exit(0);
            }
            if (File.Exists(ImagePath))
            {
                //加载动画
                Image image = Image.FromFile(ImagePath);
                this.BackgroundImage = image;
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            //这里是用于判断结束 分两种  超时  或  已完成
            if (sw.Elapsed.TotalSeconds > WaitSecond)
                Environment.Exit(0);
            if (process == null)
                Environment.Exit(0);
            if (process.HasExited)
                Environment.Exit(0);
            process.Refresh();
            if (process.MainWindowTitle.Equals(FormName))
                Environment.Exit(0);



            //Console.WriteLine(process.MainWindowTitle);
            //Console.WriteLine(sw.Elapsed.TotalMilliseconds);
        }

        private void MyWait_Shown(object sender, EventArgs e)
        {
            this.Refresh();
            UpdateFile();
            if (File.Exists("update.txt"))
                File.Delete("update.txt");
            sw.Restart();
            timer1.Enabled = true;
            StartMain();
        }

        #region Tips
        string[] tips;
        string tip = "小贴士:{0}";

        #endregion

        #region Function

        public string GetVal(string key)
        {
            try
            {
                return ConfigurationManager.AppSettings.Get(key);
            }
            catch
            {
                throw;
            }
        }

        public void SetVal(string key, string value)
        {
            try
            {
                Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                configuration.AppSettings.Settings[key].Value = value;
                configuration.Save(ConfigurationSaveMode.Modified);
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// 为控件添加移动功能
        /// </summary>
        /// <param name="ctrl">鼠标按下的控件</param>
        /// <param name="who">移动的控件(若不存在则为控件自己) 
        /// 1:控件所在窗体; 
        /// 2:控件的父级; 
        /// 3:控件自己. </param>
        public void MoveHandle(Control ctrl, int who = 1)
        {
            try
            {
                Control mCtrl;//= ctrl.FindForm();
                switch (who)
                {
                    case 1:
                        mCtrl = ctrl.FindForm();
                        break;
                    case 2:
                        mCtrl = ctrl.Parent;
                        break;
                    case 3:
                        mCtrl = ctrl;
                        break;
                    default:
                        mCtrl = ctrl.FindForm();
                        break;
                }
                if (mCtrl == null)
                {
                    mCtrl = ctrl;
                }

                Point sourcePoint = new Point(0, 0);
                bool isMove = false;
                ctrl.MouseDown += delegate (object sender, MouseEventArgs e)
                {
                    sourcePoint = e.Location;
                    isMove = true;
                };
                ctrl.MouseMove += delegate (object sender, MouseEventArgs e)
                {
                    if (isMove)
                        mCtrl.Location = new Point(mCtrl.Location.X + e.X - sourcePoint.X, mCtrl.Location.Y + e.Y - sourcePoint.Y);
                };
                ctrl.MouseUp += delegate (object sender, MouseEventArgs e)
                {
                    isMove = false;
                };
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion


    }




}

 

----------配置文件

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="exePath" value="MyRapid.Client.exe"/>
    <add key="vbsPath" value="update.vbs"/>
    <add key="imgPath" value=""/>
    <add key="exeTitle" value="用户登录"/>
    <add key="urlPath" value="http://127.0.0.1:4824/update.txt"/>
    <add key="version" value="2019/9/17 9:12:23" />
  </appSettings>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

 

 

----------服务端update.txt的格式

2019/8/23 16:48:18
2019/8/23 16:48:17|MyRapid.Client.exe|http://myerp.oicp.io:4824/cBest/MyRapid.Client.exe
2019/8/23 13:17:09|MyRapid.Launcher2.exe|http://myerp.oicp.io:4824/cBest/MyRapid.Launcher2.exe
2019/8/19 11:37:20|tips.txt|http://myerp.oicp.io:4824/cBest/tips.txt
2019/8/19 11:28:07|update.rar|http://myerp.oicp.io:4824/cBest/update.rar
2049/8/19 11:27:43|update.vbs|http://myerp.oicp.io:4824/cBest/update.vbs

 

您可能有感兴趣的文章
winform(C#)程序如何实现在线更新软件

软件更新原理

Android软件更新安装。

【Linux 基础】apt 更新软件源 以及 apt update与upgrade 区别

linux系统软件更新