zoukankan      html  css  js  c++  java
  • 如何做好软件自动更新

    这里说的是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
  • 相关阅读:
    CentOS7.4安装和配置Zabbix4.0
    CentOS7.4安装jdk1.8.0_201、Tomcat-8.5.38环境
    基于腾讯云CentOS7.4+MySQL5.7+Python3+uWSGI+Nginx的Django项目部署
    Windows Server 2008 R2环境下Apache2.4+Tomcat8配置
    Django2.1.5使用xadmin打造适合国人的后台管理系统
    Django2.1.3 smtp 邮件 553报警
    GitHub的Windows客户端的使用教程
    Ubuntu18.04的网络配置(静态IP和动态IP)
    CentOS6.X、7.X下Jenkins的安装及使用
    CentOS7下搭建Nginx+PHP7的安装配置
  • 原文地址:https://www.cnblogs.com/myrapid/p/11410722.html
Copyright © 2011-2022 走看看