我的服装DRP之在线升级
半年前,我辞掉朝八晚十的工作,告别研发部的兄弟和前台MM,意气风发地着手开发自己的服装ERP。之所以这么有魄力,是因为我对当前市场上几个主流服装软件颇不以为然,掂量着在服装企业干过的这几年,心说再不疯狂就太对不起当初放弃写字楼选择进厂房的自己了。
于是开始没日没夜地敲键盘,经历无数困惑、失望、愤怒、迷茫、愉悦、兴奋,多次大规模项目重构,0次的异性约会之后,到如今产品的分销部分终于基本成型。这两天在梳理代码的过程中,觉得有必要将一些心得体会记录下来。该记录会形成一个系列,但并不会系统,属于拣哪说哪(话说第一篇我原本想写点关于打印方面的知识)。
一两年前,或更早以前,Ajax风靡全球,历时长久的BS/CS之争似乎可以盖棺定论,当时我遇到的几乎所有程序员都在孜孜不倦地谈论着浏览器上的那档子事。时至今日,BS应用仍然比CS更能迎合程序员的口味。不过主模块架构我依然选择CS模式,理由我就不赘述了。什么?非得给个说法?那我就陈列若干理由如下:
- 浏览器不是操作系统。微软可以将HTML5和JS移入操作系统,却不能将C#移入浏览器。互联网发展将出现越来越多的应用,总有一天臃肿的浏览器会不堪重负,新的应用将只能依靠更多样的其它技术平台。你说Silverlight?这玩意我一直不看好,虽然我用WPF好久,虽然WPF程序转成Silverlight应用号称非常简单,但是我从来没去研究过Silverlight。Silverlight的前景也确实不甚光明。
- 随着网速的提高,我估计CS中Client的概念也将模糊。未来的应用对于客户端来说,也许就是一个快捷图标,而指向的地址是服务器,应用程序不需要安装,只是在需要的时候实时下载到客户端。
- 上述两条太空泛,也很容易被喷。如果站在客户的角度,CS更有可能实现他们众多的“无理要求”。对于服装系统来说,BS适合数据展现(现在的领导都喜欢拿个IPAD在那算利润,咱对IOS是外行,只能从浏览器上下手)。
- ……
我认为CS的缺点主要在于安装和升级,前者只能寄希望于上述第2条,咱们可以努力解决的就是版本升级。好的升级功能需要包含以下几点:
- 版本发布工具
- 在线自动升级
- 运行时手动升级(可选择升级版本)
- 不同客户不同版本
- 可以设置是否强制升级
- 版本列表查看
- 版本还原(最多只能还原至最近强制升级版本)
- 升级失败后回滚,并让用户选择直接运行程序or重新升级
- 删除过期文件
- ……
上述红字表示暂时未开发。本着通用的原则,我建了几个产品无关的类。
这几个类简单明了,无需多做解释,我们需要的是一个工具来维护它们,下面是其中一个界面的截图,由于开发仓促,该工具并不完善(我自己用用足矣)。
不完善的其中一个点我已经在图中注明,该点产生的原因是由于待更新文件列表支持文件夹,如<filelist><file name="fileA.dll"><directory path="dirA\childDirA"></filelist>,由于涉及到文件夹,路径问题就出现了,完善该需求需要提供软件发布根目录信息和选择文件和文件夹的树形结构选择器。另外这工具并不能说是真正意义上的版本发布,因为它没有提供上传文件到服务器的功能。
当用户运行软件时,升级程序(另外一个小程序,专门用于处理版本升级)启动,检查软件配置文件记录的当前版本,并与数据库中的版本记录作一比较,若有强制升级的新版本则自动升级。需要注意的是可能新发布了多个版本,那么我们就要合并重复的文件。以下为主要代码:
1 /// <summary> 2 /// 获取需要升级的文件和目录 3 /// </summary> 4 /// <param name="softPath">待升级软件的路径</param> 5 private FilesNeedUpdate GetFilesNeedUpdate() 6 { 7 if (UpdateSection == null) 8 return null; 9 DataSet ds = null; 10 using (ChannelFactory<IVersionService> channelFactory = new ChannelFactory<IVersionService>("VersionSVC")) 11 { 12 IVersionService service = channelFactory.CreateChannel(); 13 //ds = service.GetFilesNeedUpdate(Path.GetFileName(softPath), GetNowVersion(softPath)); 14 ds = service.GetFilesNeedUpdate(UpdateSection.CustomerKey, UpdateSection.SoftKey, UpdateSection.Version); 15 } 16 DataTable dt = ds.Tables[0]; 17 if (dt.Rows.Count == 0) 18 return null; 19 if (!IsCoerciveUpdate(dt)) 20 return null; 21 UpdateSection.Version = dt.Rows[0]["VersionCode"].ToString(); 22 List<FileNeedUpdate> files = new List<FileNeedUpdate>(); 23 List<FileNeedUpdate> directories = new List<FileNeedUpdate>(); 24 XmlDocument doc = new XmlDocument(); 25 foreach (DataRow row in dt.Rows) 26 { 27 doc.LoadXml(row["UpdatedFileList"].ToString()); 28 var tempFiles = GetNodeNameList(doc.GetElementsByTagName("file")).ToList(); 29 var tempDires = GetNodeNameList(doc.GetElementsByTagName("directory")).ToList(); 30 files = Coverforward(tempFiles, files); 31 directories = Coverforward(tempDires, directories); 32 } 33 return new FilesNeedUpdate { Files = files.ToArray(), Directories = directories.ToArray() }; 34 } 35 36 private IEnumerable<FileNeedUpdate> GetNodeNameList(XmlNodeList nodes) 37 { 38 foreach (XmlNode node in nodes) 39 { 40 var name = node.Attributes["name"].Value; 41 FileNeedUpdate item = new FileNeedUpdate { Name = name }; 42 var dnode = node.Attributes["isDelete"]; 43 if (dnode != null) 44 item.IsDelete = Convert.ToBoolean(dnode.Value); 45 yield return item; 46 } 47 } 48 49 /// <summary> 50 /// 前向覆盖 51 /// </summary> 52 private List<FileNeedUpdate> Coverforward(List<FileNeedUpdate> filesFormer, List<FileNeedUpdate> filesAfter) 53 { 54 var diff = filesFormer.Except(filesAfter); 55 filesAfter.AddRange(diff); 56 return filesAfter; 57 } 58 59 /// <summary> 60 /// 是否强制更新 61 /// </summary> 62 private bool IsCoerciveUpdate(DataTable dt) 63 { 64 foreach (var row in dt.Rows) 65 { 66 if (Convert.ToBoolean(dt.Rows[0]["IsCoerciveUpdate"])) 67 return true; 68 } 69 return false; 70 }
获取待更新的文件集合后,就可以去服务器端下载了(文件事先上传到服务器)。为了节省带宽(用户可不想浪费太多干正事的时间),先将这些文件在服务器端压缩后再下载,下载完毕后在客户端解压,并将服务器端的压缩文件删除。这块我使用了ICSharpCode.SharpZipLib.dll,挺好用的,就不做赘述了。让人头大的是在升级过程中的消息提示需要各种异步,特别在WPF中,由于WPF没有提供强制刷新界面的方法(有间接方式,但并不推荐),在某些方面令人牙疼。关于WPF中的“异步”编程,我会在以后做一总结。
1 public partial class MainWindow : Window 2 { 3 private WebClient _client; 4 private string _zipFileName, _mainApp, _bkZipFilePath; 5 private Dispatcher _dispatcher; 6 private FilesNeedUpdate _files; 7 private UpdateHelper _helper; 8 9 public MainWindow() 10 { 11 InitializeComponent(); 12 _dispatcher = this.Dispatcher; 13 _client = new WebClient(); 14 _client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged); 15 _client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted); 16 _client.Proxy = WebRequest.DefaultWebProxy; 17 _client.Proxy.Credentials = new NetworkCredential(); 18 } 19 20 public MainWindow(UpdateHelper helper) 21 : this() 22 { 23 _mainApp = helper.SoftPath; 24 _files = helper.Files; 25 _helper = helper; 26 this.Loaded += new RoutedEventHandler(MainWindow_Loaded); 27 } 28 29 void MainWindow_Loaded(object sender, RoutedEventArgs e) 30 { 31 try 32 { 33 LoadingLabel.Text = "有新版本发布,正在备份当前文件,请稍候……"; 34 Action bkaction = () => BackUpFiles(); 35 bkaction.BeginInvoke(new AsyncCallback(HandleFilesToUpdate), bkaction); 36 } 37 catch (Exception ex) 38 { 39 HandleException(ex); 40 } 41 } 42 43 private void HandleException(Exception e) 44 { 45 _dispatcher.Invoke(new Action(() => 46 { 47 tbError.Text = "系统升级出错,错误原因:" + e.Message; 48 pnlError.Visibility = Visibility.Visible; 49 })); 50 } 51 52 void Init() 53 { 54 pnlError.Visibility = Visibility.Collapsed; 55 this.RadProgressBar1.Value = 0; 56 PercentageLabel.Text = ""; 57 if (!string.IsNullOrEmpty(_bkZipFilePath) && File.Exists(_bkZipFilePath)) 58 File.Delete(_bkZipFilePath); 59 _zipFileName = _bkZipFilePath = ""; 60 } 61 62 private void HandleFilesToUpdate(IAsyncResult res) 63 { 64 Action action = new Action(() => 65 { 66 try 67 { 68 DeleteAbateFiles(); 69 var filesNeedDownload = new FilesNeedUpdate 70 { 71 Files = _files.Files.ToList().FindAll(o => !o.IsDelete).ToArray(), 72 Directories = _files.Directories.ToList().FindAll(o => !o.IsDelete).ToArray() 73 }; 74 if (!filesNeedDownload.IsEmpty) 75 StartDownload(filesNeedDownload); 76 else 77 ReStartMainApp(); 78 _helper.SaveNewVersion(); 79 } 80 catch (Exception ex) 81 { 82 HandleException(ex); 83 } 84 }); 85 action.BeginInvoke(null, null); 86 } 87 88 private bool ArrayIsEmpty(Array array) 89 { 90 return array == null || array.Length == 0; 91 } 92 93 private void StartDownload(FilesNeedUpdate files) 94 { 95 //var section = _helper.UpdateSection; 96 //if (section == null || string.IsNullOrEmpty(section.SoftKey)) 97 //{ 98 // ReStartMainApp(); 99 //} 100 _dispatcher.Invoke(new Action(() => 101 { 102 LoadingLabel.Text = "新版本文件远程压缩中……"; 103 }));//DispatcherPriority.SystemIdle:先绘制完界面再执行这段逻辑 104 string url; 105 using (ChannelFactory<IVersionService> channelFactory = new ChannelFactory<IVersionService>("VersionSVC")) 106 { 107 IVersionService service = channelFactory.CreateChannel(); 108 _zipFileName = service.CompressFilesNeedUpdate(files); 109 url = service.GetFilesUpdateUrl(_helper.UpdateSection.SoftKey); 110 } 111 //var url = ConfigurationManager.AppSettings["VersionFileUrl"]; 112 if (!url.EndsWith("/")) 113 url += "/"; 114 url += _zipFileName; 115 _dispatcher.Invoke(new Action(() => 116 { 117 //将压缩文件下载到临时文件夹 118 LoadingLabel.Text = "新版本文件下载中……"; 119 })); 120 _client.DownloadFileAsync(new Uri(url), GetTempFolder() + "\\" + _zipFileName); 121 } 122 123 /// <summary> 124 /// 获取下载文件夹地址及解压文件存放地址 125 /// 此地址默认为C:\Documents and Settings\当前用户名\Local Settings\Temp 文件夹 126 /// </summary> 127 private string GetTempFolder() 128 { 129 string folder = System.Environment.GetEnvironmentVariable("TEMP"); 130 return new DirectoryInfo(folder).FullName; 131 } 132 133 void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) 134 { 135 _dispatcher.BeginInvoke(new Action(() => 136 { 137 this.RadProgressBar1.Value = e.ProgressPercentage; 138 PercentageLabel.Text = e.ProgressPercentage.ToString() + " %"; 139 })); 140 } 141 142 void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) 143 { 144 _dispatcher.Invoke(new Action(() => 145 { 146 LoadingLabel.Text = PercentageLabel.Text = ""; 147 CompleteLabel.Text = "文件接收完成,正在更新……"; 148 })); 149 HandleUploadedFiles(); 150 } 151 152 private void HandleUploadedFiles() 153 { 154 FilesHandler.UnpackFiles(GetTempFolder() + "\\" + _zipFileName, this.GetAppRootPath()); 155 156 using (ChannelFactory<IVersionService> channelFactory = new ChannelFactory<IVersionService>("VersionSVC")) 157 { 158 IVersionService service = channelFactory.CreateChannel(); 159 service.DeleteCompressedFile(_zipFileName); 160 } 161 ReStartMainApp(); 162 } 163 164 /// <summary> 165 /// 删除已过期的文件 166 /// </summary> 167 private void DeleteAbateFiles() 168 { 169 var filesNeedDelete = new FilesNeedUpdate 170 { 171 Files = _files.Files.ToList().FindAll(o => o.IsDelete).ToArray(), 172 Directories = _files.Directories.ToList().FindAll(o => o.IsDelete).ToArray() 173 }; 174 if (!filesNeedDelete.IsEmpty) 175 { 176 _dispatcher.Invoke(new Action(() => 177 { 178 CompleteLabel.Text = "正在删除已过期文件……"; 179 }), DispatcherPriority.Normal); 180 FilesHandler.DeleteFiles(filesNeedDelete.Files.Select(o => o.Name).ToArray(), filesNeedDelete.Directories.Select(o => o.Name).ToArray(), _mainApp); 181 } 182 183 } 184 185 private void ReStartMainApp(IAsyncResult res = null) 186 { 187 _dispatcher.Invoke(new Action(() => 188 { 189 CompleteLabel.Text = "正在重启应用程序,请稍候……"; 190 })); 191 192 _dispatcher.BeginInvoke(new Action(() => 193 { 194 Process.Start(_mainApp); 195 this.Init(); 196 Process.GetCurrentProcess().Kill(); 197 }), DispatcherPriority.SystemIdle); 198 } 199 200 private string GetAppRootPath() 201 { 202 var rootPath = System.IO.Path.GetDirectoryName(_mainApp); 203 if (!rootPath.EndsWith("\\")) 204 rootPath += "\\"; 205 return rootPath; 206 } 207 208 //更新前备份文件 209 private void BackUpFiles() 210 { 211 var rootPath = GetAppRootPath(); 212 _bkZipFilePath = rootPath + Guid.NewGuid().ToString() + ".zip"; 213 FilesHandler.CompressFiles(_files, rootPath, _bkZipFilePath); 214 } 215 }
升级完毕后不要忘记保存新版本编号到配置文件。
1 internal void SaveNewVersion() 2 { 3 UpdateSection.CurrentConfiguration.Save(ConfigurationSaveMode.Modified); 4 }
这里的UpdateSection定义如下:
1 public class UpdateOnlineSection : ConfigurationSection 2 { 3 [ConfigurationProperty("CustomerKey", DefaultValue = "")] 4 public string CustomerKey 5 { 6 get { return (string)base["CustomerKey"]; } 7 set { base["CustomerKey"] = value; } 8 } 9 10 [ConfigurationProperty("SoftKey", DefaultValue = "")] 11 public string SoftKey 12 { 13 get { return (string)base["SoftKey"]; } 14 set { base["SoftKey"] = value; } 15 } 16 17 [ConfigurationProperty("Version", DefaultValue = "")] 18 public string Version 19 { 20 get { return (string)base["Version"]; } 21 set { base["Version"] = value; } 22 } 23 }
对应的是主程序的版本节点。自定义配置节点有两种方式,继承IConfigurationSectionHandler或继承自ConfigurationSection,由于我们要从外部程序(此处是升级程序)访问主程序的配置,必须继承自ConfigurationSection方可。
主程序使用WCF获取版本信息。代码就不贴了,下面给个界面截图。
改进点:需要在该界面上增加手动升级的按钮,以及当前运行版本标示。
文至此,想到尚有一些业务需求未完成,再无下笔欲望,若有朋友感兴趣,我会在空闲时间将该功能涉及到的几个工具完善后剥离出来提供下载。
转载本文请注明出处:http://www.cnblogs.com/newton/archive/2013/01/12/2857722.html
转载于:https://www.cnblogs.com/newton/archive/2013/01/13/2857722.html
我的服装DRP之在线升级相关推荐
- 我的服装DRP之即时通讯——为WCF增加UDP绑定(应用篇)
发个牢骚,博客园发博文竟然不能写副标题.这篇既为我的服装DRP系列第二篇,也给为WCF增加UDP绑定系列收个尾.原本我打算记录开发过程中遇到的一些问题和个人见解,不过写到一半发现要写的东西实在太多,有 ...
- 「真实案例」数字化商品企划让服装零售企业数字化升级事半功倍
从商品时代进入新零售时代.核心在于如何建立以消费者为中心的品牌的用户生态.利用算法更加精准地推荐商品.产品是消蒉者对品牌认知及购买决策的核心要素.而合理"的商品结构.精准的销售预测.供应链能 ...
- 软件包管理 之 软件在线升级更新yum 图形工具介绍
作者:北南南北 来自:LinuxSir.Org 提要:yum 是Fedora/Redhat 软件包管理工具,包括文本命令行模式和图形模式:图形模式的yum也是基于文本模式的:目前yum图形前端程序主要 ...
- C#做的在线升级小程序
转自原文C#做的在线升级小程序 日前收到一个小任务,要做一个通用的在线升级程序.更新的内容包括一些dll或exe或.配置文件.升级的大致流程是这样的,从服务器获取一个更新的配置文件,经过核对后如有新的 ...
- IE9最终版透露IE10信息 或将自动在线升级
IE9才刚刚发布几天,互联网上已开始了对IE下一个版本的猜测. 今天,在IE9最终版里发现的一些隐藏资源显示,微软已有了对IE10的预先计划.俄罗斯网站TheVista.ru披露了一个提及IE10的对 ...
- Stm 32 IAP 在线 升级IAP 的 操作
(扩展-IAP主要用于产品出厂后应用程序的更新作用,考虑到出厂时要先烧写IAP 再烧写APP应用程序要烧写2次增加工人劳动力基础上写了"STM32 IAP+APP ==>双剑合一&q ...
- PIC在线升级源码分析
1:概述 最近两周都在做PIC在线升级的功能,最终看到升级成功的提示,难以掩盖成功的喜悦.决定把我两周中遇到的问题和大家分享一下,希望能给正在做升级功能的人一些帮助.有理解错误的地方请大家给以指正. ...
- CRC校验原理及STM32 IAP在线升级程序
CRC校验原理: 什么是CRC校验? CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据传输检错功能,对数据 ...
- 用C#实现C/S模式下软件自动在线升级[转载]
摘要: 本文针对目前C/S模式下编写的应用程序可维护性差的特点,提出了一套自动在线升级的解决方案,分析了在线升级的困难及实现原理,并给出了实现升级的部分代码,具有实际参考价值和现实意义.本文程序代码均 ...
最新文章
- C#3.0入门系列(五)-之Where操作
- string类型加减_测试人员应该知道的Redis知识(四) String
- 中国剩余定理matlab非互质,中国剩余定理模板(互质版和非互质版)
- 组件通信 Provideinject
- EMNLP 2020 | 通过Contrast Set评估模型的局部决策边界
- Maven引入依赖后自动下载并关联源码(Source)
- 九种跨域方式实现原理
- 电脑格式化后需要重装系统吗_重装系统后c盘文件丢失,电脑重装系统后c盘文件能恢复吗...
- Oracle设置和修改system和scott的口令,并且如何连接到system和scott模式下
- akka java_java – Akka和Spring集成
- iOS - UIRefreshControl		刷新数据
- 建行优盾制单重要还是复核重要_想秒批建行5万+白金信用卡,你得满足这些条件!...
- 互联网35岁中年危机的来龙去脉
- Ajax.Responders
- PDF解密去水印工具
- 巨波公第3子登国公后裔在荆州(巨波公6子的后裔,全部水落石出)
- 技术揭秘QQ空间”自动转发不良信息
- white-space 与换行和空格的控制?
- 数据结构--创建并输出二叉树的c语言实现(超详细注释/实验报告)
- longest-common-prefix[最长公共子序列]