失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > C/S架构系统的自动更新功能设计与实现(四)

C/S架构系统的自动更新功能设计与实现(四)

时间:2021-02-24 02:33:03

相关推荐

C/S架构系统的自动更新功能设计与实现(四)

文章目录

客户端配置管理配置管理类功能入口管理界面设计更新功能实现成员变量主窗口升级过程其他方法集成方式升级功能界面

客户端配置管理

基于WPF开发客户端。配置文件写在config文件中。

<?xml version="1.0" encoding="utf-8" ?><configuration><appSettings><!--升级服务器的IP地址--><add key="ServerIP" value="127.0.0.1" /><!--升级服务器的端口号--><add key="ServerPort" value="20000" /><!--升级后要调起的应用程序名(与本升级组件同级目录)--><add key="Callback" value="XXXX.XXXX.exe" /></appSettings><startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /></startup></configuration>

配置管理类

/// <summary>/// 配置项的关键字(名称)/// </summary>public class ConfigKey{/// <summary>/// 服务器IP/// </summary>public static string m_sKeyServerIP = "ServerIP";/// <summary>/// 服务器端口号/// </summary>public static string m_sKeyServerPort = "ServerPort";/// <summary>/// 更新后启动的应用程序名/// </summary>public static string m_sKeyCallback = "Callback";}/// <summary>/// /// </summary>public class ConfigManager{Configuration config = null;/// <summary>/// /// </summary>public ConfigManager(){config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);}/// <summary>/// /// </summary>/// <returns></returns>public Dictionary<string, string> GetAppSettings(){var dict = new Dictionary<string, string>();config.AppSettings.Settings.AllKeys.ToList().ForEach(p => dict.Add(p, config.AppSettings.Settings[p].Value));return dict;}/// <summary>/// //添加键值/// </summary>/// <param name="key"></param>/// <param name="value"></param>public void AddAppSetting(string key, object value){config.AppSettings.Settings.Add(key, value?.ToString());config.Save();}/// <summary>/// //修改键值/// </summary>/// <param name="key"></param>/// <param name="value"></param>public void SaveAppSetting(string key, object value){config.AppSettings.Settings.Remove(key);config.AppSettings.Settings.Add(key, value?.ToString());config.Save();}/// <summary>/// //获得键值/// </summary>/// <param name="key"></param>/// <returns></returns>public string GetAppSetting(string key){return config.AppSettings.Settings[key]?.Value;}/// <summary>/// //移除键值/// </summary>/// <param name="key"></param>public void DeleteAppSetting(string key){config.AppSettings.Settings.Remove(key);config.Save();}}

功能入口管理

添加一个MainApp,并设置其为启动对象:

添加参数传递以支持应用程序内部调用该更新程序:

/// <summary>/// 实际的启动对象/// </summary>public class MainApp{/// <summary>/// 主函数入口/// </summary>/// <param name="args">附加参数(要自动关闭的进程名列表)</param>[STAThread]public static void Main(string[] args){try{#region 关闭传递过来的进程args.ToList().ForEach(ar =>{Process.GetProcessesByName(ar.ToLower().Replace(".exe", "")).ToList().ForEach(p => p.Kill());});#endregion#region 关闭要回调启动的进程var sCallback = new ConfigManager().GetAppSetting(ConfigKey.m_sKeyCallback);if (!string.IsNullOrEmpty(sCallback)){var sCallbackName = System.bine(AppDomain.CurrentDomain.BaseDirectory, sCallback);Process.GetProcessesByName(sCallback.ToLower().Replace(".exe", "")).ToList().ForEach(p =>{if (0 == pare(p.MainModule.FileName, sCallbackName, StringComparison.OrdinalIgnoreCase)){p.Kill(); //仅关闭当前目录下的进程(防止错误关闭其他进程)}});}#endregion}catch (Exception){}#region 启动应用程序var app = new App();app.InitializeComponent();app.Run();#endregion}}

界面设计

添加一个简单的WPF界面。

<Window x:Class="XXXX.AutoUpgrade.MainWindow"xmlns="/winfx//xaml/presentation"xmlns:x="/winfx//xaml"xmlns:d="/expression/blend/"xmlns:mc="/markup-compatibility/"mc:Ignorable="d"Title=" 系统升级" Height="396" Width="640" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" ShowInTaskbar="False" Background="White" WindowStyle="None" Closed="Window_Closed"><Grid Height="396" VerticalAlignment="Top" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"><Image x:Name="image" HorizontalAlignment="Left" VerticalAlignment="Top" Width="640" Source="Resources/autoupgrade.jpg"/><Label x:Name="labelLocalVersion" Content="当前版本: 1.0.9.0 更新时间: 0000/0/00 00:00:00" Margin="10,10,331,0" VerticalAlignment="Top" RenderTransformOrigin="0.512,-1.28" Height="26" Background="Transparent" FontWeight="Bold"><Label.Foreground><SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}"/></Label.Foreground></Label><Label x:Name="labelNewVersion" Content="最新版本: 0.0.0.0 发布时间: 0000/0/00 00:00:00" HorizontalAlignment="Right" Margin="0,10,10,0" VerticalAlignment="Top" Width="290" Height="26" Background="Transparent" FontWeight="Bold"><Label.Foreground><SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}"/></Label.Foreground></Label><ProgressBar x:Name="progressInfo" Margin="10,360,10,0" Background="#FFE6E6E6" BorderThickness="1" Height="4" VerticalAlignment="Top"/><Label x:Name="labelProgressTip" Content="等待中..." Margin="0,330,126,0" RenderTransformOrigin="0.512,-1.28" HorizontalContentAlignment="Left" Height="26" VerticalAlignment="Top" HorizontalAlignment="Right" Width="435" FontWeight="Bold"><Label.Foreground><SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ActiveCaptionTextColorKey}}"/></Label.Foreground></Label><Label x:Name="labelProgressFile" Content="等待中..." Margin="0,368,10,3" RenderTransformOrigin="0.512,-1.28" HorizontalContentAlignment="Left" HorizontalAlignment="Right" Width="551" FontWeight="Bold"><Label.Foreground><SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ActiveCaptionTextColorKey}}"/></Label.Foreground></Label><Label x:Name="label" Content="更新进度: " HorizontalAlignment="Left" Margin="10,330,0,0" VerticalAlignment="Top" Height="26" Width="64" FontWeight="Bold"><Label.Foreground><SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ActiveCaptionTextColorKey}}"/></Label.Foreground></Label><Label x:Name="labelOperation" Content="更新文件: " HorizontalAlignment="Left" Margin="10,368,0,0" VerticalAlignment="Top" Height="26" Width="64" FontWeight="Bold"><Label.Foreground><SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ActiveCaptionTextColorKey}}"/></Label.Foreground></Label><Label x:Name="labelProgressValue" Content="0%" HorizontalAlignment="Left" Margin="566,330,0,0" VerticalAlignment="Top" Height="26" Width="64" FontWeight="Bold" RenderTransformOrigin="8.655,0.678" HorizontalContentAlignment="Right"><Label.Foreground><SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ActiveCaptionTextColorKey}}"/></Label.Foreground></Label></Grid></Window>

更新功能实现

成员变量

private ConfigManager m_config = new ConfigManager();private string m_sValueServerIP = "10.0.1.26";private int m_nValueServerPort = 20000;private string m_sValueCallback = "";private string m_sMainFolder = AppDomain.CurrentDomain.BaseDirectory; //主程序目录private string m_sCacheFolder = bine(AppDomain.CurrentDomain.BaseDirectory, "UpgradeCache"); //升级文件缓存目录

主窗口

public MainWindow(){InitializeComponent();try{#region 获取配置项中的服务器端IP和端口Portvar sValueServerIP = m_config.GetAppSetting(ConfigKey.m_sKeyServerIP);if (!string.IsNullOrEmpty(sValueServerIP)){if (IPAddress.TryParse(sValueServerIP, out IPAddress address)){m_sValueServerIP = sValueServerIP;}else{ShowError("config中配置的ServerIP项非法!");}}m_config.SaveAppSetting(ConfigKey.m_sKeyServerIP, m_sValueServerIP);var sValueServerPort = m_config.GetAppSetting(ConfigKey.m_sKeyServerPort);if (!string.IsNullOrEmpty(sValueServerPort)){if (int.TryParse(sValueServerPort, out int nPort)){m_nValueServerPort = nPort;}else{ShowError("config中配置的ServerPort项非法!");}}m_config.SaveAppSetting(ConfigKey.m_sKeyServerPort, m_nValueServerPort);#endregion#region 获取配置项中的回调程序名(位于本自动更新程序同级目录中,如:XXXX.XXXX.exe)var sValueCallback = m_config.GetAppSetting(ConfigKey.m_sKeyCallback);if (!string.IsNullOrEmpty(sValueCallback)){m_sValueCallback = sValueCallback;}m_config.SaveAppSetting(ConfigKey.m_sKeyCallback, m_sValueCallback);#endregion#region 获取服务器端最新版本摘要var sAbstract = RequestWeb("api/Upgrade/Abstract");if (!ValidJson.IsJson(sAbstract)){throw new Exception("获取服务器端最新版本摘要信息失败,请检查升级文件配置及网络状态!");}var responseInfoAbstract = JsonConvert.DeserializeObject<ResponseInfo>(sAbstract);if (ErrorCode.Ok != responseInfoAbstract.Code){ShowWarning("未能获取升级信息,请检查升级文件配置及网络状态!");Close();}#endregion#region 获取本地版本,与最新版本进行比较var localUpgradeInfo = new UpgradeInfo();localUpgradeInfo.Load();var upgradeInfoAbstract = JsonConvert.DeserializeObject<UpgradeInfoAbstract>(responseInfoAbstract.Data.ToString());if (upgradeInfoAbstract.Equals(new UpgradeInfoAbstract(localUpgradeInfo))){//ShowInfo("当前版本已是最新版,不需要升级!");Close();}#endregion#region 获取升级文件列表var sFiles = RequestWeb("api/Upgrade/Files");if (!ValidJson.IsJson(sFiles)){throw new Exception("获取服务器端升级文件信息失败,请检查升级文件配置及网络状态!");}var responseInfoFilesInfo = JsonConvert.DeserializeObject<ResponseInfo>(sFiles);if (ErrorCode.Ok != responseInfoFilesInfo.Code){ShowWarning("未能获取升级文件列表,请检查升级文件配置及网络状态!");Close();}var filesInfo = JsonConvert.DeserializeObject<UpgradeFilesInfo>(responseInfoFilesInfo.Data.ToString());if (null == filesInfo || null == filesInfo.FileUnits || 0 >= filesInfo.FileUnits.Count){ShowWarning("升级文件列表为空,不需要升级!");Close();}#endregion#region 升级前先清空缓存,重建缓存目录if (Directory.Exists(m_sCacheFolder))Directory.Delete(m_sCacheFolder, true);Directory.CreateDirectory(m_sCacheFolder);#endregion#region 执行版本升级Upgrade(localUpgradeInfo, upgradeInfoAbstract, filesInfo);#endregion}catch (Exception ex){ShowError(ex.Message);Close();}}

升级过程

/// <summary>/// 升级过程/// </summary>/// <param name="localUpgradeInfo"></param>/// <param name="upgradeInfoAbstract"></param>/// <param name="filesInfo"></param>private void Upgrade(UpgradeInfo localUpgradeInfo, UpgradeInfoAbstract upgradeInfoAbstract, UpgradeFilesInfo filesInfo){Task.Factory.StartNew(() =>{#region 显示升级信息var sLocalVersion = $"当前版本: {(string.IsNullOrEmpty(localUpgradeInfo.MainVersion) ? "N/A" : localUpgradeInfo.MainVersion)} 升级时间: {localUpgradeInfo.LastUpgradeTime.ToString()}";new Action(() =>{Dispatcher.BeginInvoke(new Action(() =>{labelLocalVersion.Content = sLocalVersion;}), System.Windows.Threading.DispatcherPriority.SystemIdle, null);}).BeginInvoke(null, null);var sNewVersion = $"最新版本: {upgradeInfoAbstract.MainVersion} 发布时间: {upgradeInfoAbstract.LastUpgradeTime.ToString()}";new Action(() =>{Dispatcher.BeginInvoke(new Action(() =>{labelNewVersion.Content = sNewVersion;}), System.Windows.Threading.DispatcherPriority.SystemIdle, null);}).BeginInvoke(null, null);#endregion#region 下载升级文件到缓存目录var nTotalCount = upgradeInfoAbstract.TotalCount;var dTotalSize = Math.Round(upgradeInfoAbstract.TotalSize * 0.001, 2); //单位从KB转为MB(粗略计算,除以1000。保留两位小数)var sTotalSize = string.Format("{0:N2}", dTotalSize);new Action(() =>{Dispatcher.BeginInvoke(new Action(() =>{progressInfo.Minimum = 0;progressInfo.Maximum = nTotalCount;}), System.Windows.Threading.DispatcherPriority.SystemIdle, null);}).BeginInvoke(null, null);double dCurrentSize = 0;var lstFailedFile = new List<string>(); //升级失败的文件列表for (int i = 0; i < nTotalCount; i++){var bSkip = false;var fileInfo = filesInfo.FileUnits[i];dCurrentSize += fileInfo.FileSize;var sLocalPath = bine(m_sMainFolder, fileInfo.RelativePath);if (File.Exists(sLocalPath)){bSkip = new FileUnit(m_sMainFolder, sLocalPath).Equals(fileInfo);}var sCurrentFile = "";if (fileInfo.FileSize < 100) //小于100KB的,按KB显示,否则按MB显示{sCurrentFile = $"{fileInfo.RelativePath} ({fileInfo.FileSize} KB)";}else{sCurrentFile = $"{fileInfo.RelativePath} ({string.Format("{0:N2}", Math.Round(fileInfo.FileSize * 0.001, 2))} MB)";}if (!bSkip){if (!RequestWebFile("api/Upgrade/Stream", fileInfo.RelativePath) && lstFailedFile.Count < 11){lstFailedFile.Add(fileInfo.RelativePath);}}new Action(() =>{Dispatcher.BeginInvoke(new Action(() =>{var sProgressInfo = $"{(i).ToString("N0")}/{nTotalCount.ToString("N0")}个, {string.Format("{0:N2}", dCurrentSize * 0.001)}/{sTotalSize} MB";labelProgressFile.Content = sCurrentFile;labelProgressTip.Content = sProgressInfo;labelProgressValue.Content = $"{ Math.Round((double)((100 * i) / nTotalCount), 2)} %";progressInfo.Value = i + 1;}), System.Windows.Threading.DispatcherPriority.SystemIdle, null);}).BeginInvoke(null, null);}#endregion#region 判断是否有下载失败的文件var sFaileFileList = "";var nFailedCount = lstFailedFile.Count > 10 ? 10 : lstFailedFile.Count;if (0 < nFailedCount){for (int i = 0; i < nFailedCount; i++){if (0 == i){sFaileFileList += $"[{i + 1}] {lstFailedFile[i]}";}else{sFaileFileList += $"{Environment.NewLine}[{i + 1}] {lstFailedFile[i]}";}}if (lstFailedFile.Count > 10){sFaileFileList += $"{Environment.NewLine}...";}}#endregion#region 文件全部下载后,判断是否更新成功if (!string.IsNullOrEmpty(sFaileFileList)){Dispatcher.Invoke(new Action(delegate{labelProgressValue.Content = "100%";ShowError($"升级失败!以下文件未能正确更新:{Environment.NewLine}{sFaileFileList}");Directory.Delete(m_sCacheFolder, true); //删除缓存目录Close();}));}else{new Action(() =>{Dispatcher.BeginInvoke(new Action(() =>{labelOperation.Content = "拷贝文件: ";progressInfo.Value = 0;}), System.Windows.Threading.DispatcherPriority.SystemIdle, null);}).BeginInvoke(null, null);for (int i = 0; i < nTotalCount; i++) //拷贝文件{var fileInfo = filesInfo.FileUnits[i];var sNewPath = bine(m_sCacheFolder, fileInfo.RelativePath);var sOldPath = bine(m_sMainFolder, fileInfo.RelativePath);new Action(() =>{Dispatcher.BeginInvoke(new Action(() =>{var sProgressInfo = $"{i.ToString("N0")}/{nTotalCount.ToString("N0")}";labelProgressFile.Content = fileInfo.RelativePath;labelProgressTip.Content = sProgressInfo;labelProgressValue.Content = $"{ Math.Round((double)((100 * i) / nTotalCount), 2)} %";progressInfo.Value = i + 1;}), System.Windows.Threading.DispatcherPriority.SystemIdle, null);}).BeginInvoke(null, null);if (File.Exists(sNewPath)){if (File.Exists(sOldPath)){File.Delete(sOldPath);}var sParentDir = new FileInfo(sOldPath).Directory.FullName;if (!Directory.Exists(sParentDir))Directory.CreateDirectory(sParentDir);File.Move(sNewPath, sOldPath);}}Directory.Delete(m_sCacheFolder, true); //删除缓存目录//获取完整版升级信息并保存到本地配置文件var sInfo = RequestWeb("api/Upgrade/Info");if (ValidJson.IsJson(sInfo)){var responseInfo = JsonConvert.DeserializeObject<ResponseInfo>(sInfo);if (ErrorCode.Ok == responseInfo.Code){JsonConvert.DeserializeObject<UpgradeInfo>(responseInfo.Data?.ToString()).Save();}}Dispatcher.Invoke(new Action(delegate{labelProgressValue.Content = "100%";//ShowInfo("升级成功!");Close();}));}#endregion});}

其他方法

/// <summary>/// 发起网络请求/// </summary>/// <param name="sRequestAddress">请求地址(不包括IP、端口)</param>/// <param name="sParam">附加参数</param>/// <returns>返回结果</returns>private string RequestWeb(string sRequestAddress, string sParam = null){string sResponse;try{var sServerIP = m_sValueServerIP;if (0 == pare(sServerIP, "127.0.0.1", StringComparison.OrdinalIgnoreCase))sServerIP = "localhost";var nServerPort = m_nValueServerPort;var sUrl = $"http://{sServerIP}:{nServerPort}/{sRequestAddress}";if (!string.IsNullOrEmpty(sParam)){sUrl = $"{sUrl}?param={sParam}";}var request = (HttpWebRequest)WebRequest.Create(sUrl);request.Method = "GET";request.ContentType = "application/json";var responseStream = request.GetResponse();var response = (HttpWebResponse)request.GetResponse();var streamReader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);sResponse = streamReader.ReadToEnd();streamReader.Close();if (response != null){response.Close();}if (request != null){request.Abort();}}catch (Exception ex){sResponse = ex.Message;}return sResponse;}/// <summary>/// 发起网络请求以下载文件/// </summary>/// <param name="sRequestAddress">请求地址(不包括IP、端口)</param>/// <param name="sFileName">文件名(相对路径)</param>/// <returns>是否成功</returns>private bool RequestWebFile(string sRequestAddress, string sFileName){var bResult = false;try{var sServerIP = m_sValueServerIP;if (0 == pare(sServerIP, "127.0.0.1", StringComparison.OrdinalIgnoreCase))sServerIP = "localhost";var nServerPort = m_nValueServerPort;var sUrl = $"http://{sServerIP}:{nServerPort}/{sRequestAddress}";if (!string.IsNullOrEmpty(sFileName)){sUrl = $"{sUrl}?param={sFileName}";}var request = (HttpWebRequest)WebRequest.Create(sUrl);request.Method = "GET";var response = (HttpWebResponse)request.GetResponse();if (HttpStatusCode.OK == response.StatusCode){var sFile = bine(m_sCacheFolder, sFileName);var sParentDir = new FileInfo(sFile).Directory.FullName;if (!Directory.Exists(sParentDir))Directory.CreateDirectory(sParentDir);if (File.Exists(sFile))File.Delete(sFile);using (var fileStream = File.Create(sFile)){using (var stream = response.GetResponseStream()){stream.CopyTo(fileStream);bResult = true;}}}if (response != null){response.Close();}if (request != null){request.Abort();}}catch{bResult = false;}return bResult;}/// <summary>/// 窗口关闭后的回调处理/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void Window_Closed(object sender, EventArgs e){if (string.IsNullOrEmpty(m_sValueCallback)){return;}var sCallback = bine(AppDomain.CurrentDomain.BaseDirectory, m_sValueCallback);if (!File.Exists(sCallback)){ShowError($"启动失败,原因:文件不存在!路径:{Environment.NewLine}{sCallback}");return;}System.Diagnostics.Process.Start(sCallback);}

集成方式

0代码集成!!!

将升级功能组件修改为实际要启动的应用程序名,并将回调应用程序设置为真正的启动对象即可。

比如,我原来的应用程序叫"abc.exe",则将此升级组件改名为“我的ABC.exe”,其回调的应用程序指向原来的"abc.exe"即可。

升级功能界面

如果觉得《C/S架构系统的自动更新功能设计与实现(四)》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。