C# Winform下一个热插拔的MIS/MRP/ERP框架14(自动更新)
对于软件来说,启用自动更新是非常必要的。
根据软件的应用场景,我们可以设计不同的更新模型。
目前,IMES框架运行在.Net framework 4.0下面,使用的Win系统版本在Win7,域内管控,平时业务调整也不是很频繁。
所以,我的更新很粗放,就是删除旧文件,拷贝新文件:
1、更新文件放置在文件服务器一个公共目录下:\\SV001\Public\Update ;
2、仅在用户登录时检测更新(或者在系统界面点击“更新”按钮手动更新);
3、根据业务变更可以指定更新某一个文件、一个文件夹、或者所有文件;
4、软件是否要更新,简单的由一个文本文件的最后修改时间来判断;
5、可以保留某些本地生成和下载的配置/文件;
完整代码:
public partial class Fm19Update : Form { /// <summary> /// 这个程序会单独运行,因此不要引用其他DLL /// </summary> public Fm19Update() { InitializeComponent(); FormBorderStyle = FormBorderStyle.None; StartPosition = FormStartPosition.CenterScreen; } #region 声明 [DllImport("user32.dll")] public static extern bool ReleaseCapture();//拖动无窗体的控件 [DllImport("user32.dll")] public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam); public const int WM_SYSCOMMAND = 0x0112; public const int SC_MOVE = 0xF010; public const int HTCAPTION = 0x0002; /// <summary> /// 更新程序来源路径(更新文件一般放在所有客户端可访问的网络服务器上) /// </summary> private string UpdatePath = string.Empty; /// <summary> /// 更新内容(可使用\\,*.*,0;dir1\*.dll,0分割要更新的区块,所有文件全部更新使用[\\,*.*,1] /// </summary> private string UpdateContent = string.Empty; /// <summary> /// 更新内容清单,由UpdateContent转换而来. /// </summary> private string[] UpdateList; /// <summary> /// 要更新的文件个数,由各更新区域加总 /// </summary> private static int UpdateFilesCount = 0; #endregion /// <summary> /// 延时函数, 如使用Thread.Sleep()会停止响应 /// </summary> /// <param name="delayTime"></param> /// <returns></returns> private static bool Delay(int delayTime) { DateTime now = DateTime.Now; int s; do { TimeSpan spand = DateTime.Now - now; s = spand.Seconds; Application.DoEvents(); } while (s < delayTime); return true; } /// <summary> /// 显示当前作业状态 /// </summary> /// <param name="tStatus"></param> private void SetUpdateStatus(string tStatus) { LabCurStatus.Text = tStatus; Refresh(); } private void CmdQuit_Click(object sender, EventArgs e) { Application.Exit(); } private void Fm19Update_MouseDown(object sender, MouseEventArgs e) { ReleaseCapture(); //调用移动无窗体控件函数 SendMessage(Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0); } private void Fm19Update_Shown(object sender, EventArgs e) { SetUpdateStatus("Checking progress status..."); Refresh(); Delay(2); //这里的检测其实是一个假象,仅仅延时了2秒,以等待主程序结束运行。 SetUpdateStatus("Checking progress status...Finished"); Refresh(); DoUpdate(); //开始进行更新 } #region 更新作业区域 /// <summary> /// 当本地更新文件的最后修改时间与更新路径下的更新文件不同,则认为需要更新。 /// </summary> private void DoUpdate() { #region 判断来源与本地文件夹状态 string localPath = AppDomain.CurrentDomain.BaseDirectory; localPath = localPath.Substring(0, localPath.Length - 1); //从本地更新历史文件读取远程更新路径 string[] allLines = File.ReadAllLines(localPath + @"\UpdateLog.txt"); string remotePath = string.Empty; foreach (string line in allLines) { if (line.IndexOf("UpdatePath=") >= 0) { remotePath = line.Substring(line.IndexOf("UpdatePath=") + 11).Trim(); break; } } if (localPath.ToUpper() == remotePath.ToUpper()) { MessageBox.Show("更新路径不能与来源路径相同!", "警告...", MessageBoxButtons.OK, MessageBoxIcon.Stop); return; } FileInfo fiUL = new FileInfo(localPath + @"\UpdateLog.txt"); FileInfo fiUR = new FileInfo(remotePath + @"\UpdateLog.txt"); if (fiUL.LastWriteTime == fiUR.LastWriteTime) { SetUpdateStatus("系统没有更新,操作终止!"); return; } SetUpdateStatus("正在连接到目标文件夹......"); if (!Directory.Exists(remotePath)) { SetUpdateStatus("目标文件夹:" + remotePath + " 不存在,请联络系统管理员!"); return; } SetUpdateStatus("正在连接到目标文件夹......成功!"); //从远程读取本次更新内容 string[] allLinesRmt = File.ReadAllLines(remotePath + @"\UpdateLog.txt"); foreach (string line in allLinesRmt) { if (line.IndexOf("UpdateContent=") >= 0) { UpdateContent = line.Substring(line.IndexOf("UpdateContent=") + 14); break; } } #endregion #region 计算文件数量并设置百分比 if (!string.IsNullOrEmpty(UpdateContent)) { UpdateList = UpdateContent.Split(';'); } else { UpdateList = (@"\\,*.*,1").Split(';'); //更新内容设置不正确,则设置为更新所有文件. } UpdateFilesCount = 0; foreach (string ul in UpdateList) { string[] updContent = ul.Split(','); string updDirPath = string.Empty; string updFilesPattern = "*.*"; int updWithSubDir = 0; if (updContent.Length > 0) { updDirPath = updContent[0]; updDirPath = remotePath + @"\" + updDirPath.Replace("\\", ""); } if (updContent.Length > 1) { updFilesPattern = updContent[1]; } if (updContent.Length > 2) { updWithSubDir = Convert.ToInt16(updContent[2]); } bool withSubDir = updWithSubDir > 0; if (!string.IsNullOrEmpty(updContent[0].Trim())) { UpdateFilesCount += CountAllFiles(updDirPath, updFilesPattern, withSubDir); } } //所有更新进度根据更新区域切割百分比 ProgMain.Value = 0; ProgMain.Step = 1; ProgMain.Maximum = UpdateFilesCount + 5; for (int i = 0; i < 6; i++) { ProgMain.PerformStep(); LabProgStep.Text = (ProgMain.Value * 100 / ProgMain.Maximum).ToString() + "%"; } #endregion #region 开始删除和复制文件(会删除目录下的所有文件,再重新复制,这样可以清除掉某些过期的文件) foreach (string ul in UpdateList) { string[] updContent = ul.Split(','); string localUpdDirPath = string.Empty; string remoteUpdDirPath = string.Empty; string updFilesPattern = "*.*"; int updWithSubDir = 0; if (updContent.Length > 0) { localUpdDirPath = localPath + @"\" + updContent[0].Replace("\\", ""); remoteUpdDirPath = remotePath + @"\" + updContent[0].Replace("\\", ""); } if (updContent.Length > 1) { updFilesPattern = updContent[1]; } if (updContent.Length > 2) { updWithSubDir = Convert.ToInt16(updContent[2]); } bool withSubDir = updWithSubDir > 0; SetUpdateStatus("正在更新" + (updContent[0] == @"\\" ? "根目录" : updContent[0]) + "下的文件......"); if (!string.IsNullOrEmpty(updContent[0].Trim())) { CopyFilesTo(remoteUpdDirPath, updFilesPattern, localUpdDirPath, withSubDir); } } #endregion //更新完成后重新启动主程序 string mainEXE = AppDomain.CurrentDomain.BaseDirectory + @"B20.exe"; Process.Start(mainEXE); Application.Exit(); } /// <summary> /// 统计符合筛选条件的所有文件数量。(已排除A19.exe和LocalFile目录) /// </summary> /// <param name="fullPath">完整目录名称</param> /// <param name="sPattern">筛选条件(如*.xls)</param> /// <param name="withSubDir">是否包含子目录</param> /// <returns></returns> private int CountAllFiles(string fullPath, string sPattern, bool withSubDir = false) { SearchOption searchOption = withSubDir ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var files = Directory.EnumerateFiles(fullPath, sPattern, searchOption) .Where(s => !s.Contains("A19.exe") && !s.Contains("Custom.cfg")); return files.Count(); } /// <summary> /// 从A文件夹拷贝文件到B文件夹下面 /// </summary> /// <param name="remoteSourcePath">文件所在目录(@"C:\A\A")</param> /// <param name="localSavePath">保存的目标目录(@"C:\B\B")</param> /// <returns>返回:true-拷贝成功;false:拷贝失败</returns> public bool CopyFilesTo(string remoteSourcePath, string sPattern, string localSavePath, bool withSubDir = false) { try { SearchOption searchOption = withSubDir ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var files = Directory.EnumerateFiles(remoteSourcePath, sPattern, searchOption) .Where(s => !s.Contains("A19.exe")); if (withSubDir) { //包含子目录更新,先删除当前目录下的所有文件,再重新复制本目录,这样可以清除某些过期文件 SetUpdateStatus("正在清除旧文件......"); DelectDir(localSavePath, withSubDir); //如果包含子目录 ,则先根据远端的目录结构全部检查/创建好. var dirs = Directory.EnumerateDirectories(remoteSourcePath, "*.*", searchOption); foreach (string dir in dirs) { string locTagDir = dir.Replace(remoteSourcePath, localSavePath); if (!Directory.Exists(locTagDir)) { Directory.CreateDirectory(locTagDir); } } } if (files.Count() > 0) { foreach (string fileName in files) { //采用覆盖模式,但要保留一些本地自定义文件 if (fileName == (remoteSourcePath + @"Config\Custom.cfg")) { if (!File.Exists(fileName.Replace(remoteSourcePath, localSavePath))) { File.Copy(fileName, fileName.Replace(remoteSourcePath, localSavePath), true); } } else { File.Copy(fileName, fileName.Replace(remoteSourcePath, localSavePath), true); } ProgMain.PerformStep(); LabProgStep.Text = (ProgMain.Value * 100 / ProgMain.Maximum).ToString() + "%"; } } LabCurStatus.Text = "正在复制文件......完成!"; Refresh(); } catch (Exception ex) { MessageBox.Show(ex.Message); return false; } return true; } /// <summary> /// 删除本目录下除更新程序及LocalFile目录(存储本地生成的文件)以外的所有文件 /// </summary> /// <param name="tagPath"></param> public static void DelectDir(string tagPath, bool delSub = false) { try { DirectoryInfo dir = new DirectoryInfo(tagPath); FileSystemInfo[] fileinfo = dir.GetFileSystemInfos(); //返回目录中所有文件和子目录 foreach (FileSystemInfo i in fileinfo) { if (i is DirectoryInfo) //判断是否文件夹 { if (delSub) { if ((i.FullName != (tagPath + "LocalFiles")) && (i.FullName != (tagPath + "Config"))) { DirectoryInfo subdir = new DirectoryInfo(i.FullName); subdir.Delete(true); //删除子目录和文件 } } } else { if ((i.Name != "A19.exe") && (i.FullName != tagPath + @"Config\Custom.cfg")) { File.Delete(i.FullName); //删除指定文件 } } } } catch (Exception e) { throw e; } } #endregion }
界面设计:
当然,也可以扩展为从网站下载或者轮循新版本。