BS系统自动更新的实现

背景:

我公司做的考试系统(基于java开发的BS系统)是卖给学校的,随着客户数量增多,日常版本升级、远程维护工作占了程序员很多时间,遂考虑实现系统自动化更新。


要解决的问题及解决方案:

1.什么时候检查更新及更新策略。

由于系统随时有可能处于使用状态,全自动更新容易导致问题,因此是否执行自动更新由系统管理员决定,当系统管理员登录到后台管理界面时系统会自动检查更新并提示。管理员点击触发执行更新。由于更新的执行依托系统本身的程序,因此不能在更新前关停掉tomcat。也就是只能执行热更新,但是热更新也有些问题需要解决,java类的class需要动态装载,而spring、struts的配置文件由于依赖关系、文件较多,难以实现动态加载,就算实现了,系统session也会被重新初始化,这样很容易产生错误数据,且学生们也必须重新登录。与其这样,倒不如更新完直接提示管理员必须重启服务器后才可继续使用。

2.要支持三种类型的更新:程序补丁包、数据库更新、特殊数据更新

程序补丁包:日常bug解决,新增或修改的功能,按运行环境目录结构组织更新文件,打包成zip文件,客户端下载后解压覆盖实现更新。

数据库更新:将对数据库的任何操作,制作成sql脚本,客户端下载sql脚本并调用执行脚本方法实现更新。

特殊数据更新:我们系统是考试系统,有题库,题数据保存在数据库中,但也可以导入导出成文件,我们给学校更新题库时,是给文件进行导入。自动更新就需要实现自动导入。

3.客户端不应重复更新,而且应确保准确无误的的执行完整的更新。

有两个概念要区分:每次更新与每个更新。每次更新是系统管理员不定期的检查和执行整体更新,每个更新是一次更新中的每个具体更新,每次更新包含多个“每个更新”,因为每次更新可能会多次下载多个文件执行更新。

客户端每执行完一个更新时,会将本更新的文件单独留存到客户端的一个叫last-updated-files的目录下,下次系统请求远程服务器检查更新时会带上这个文件名,得到大于这个文件名的文件列表(也就是认为还没更新的),这样可以防止重复更新,保证按顺序更新。

因此,更新包必须加入时间概念来确保更新顺序,我采用的是更新包的命名上入手,每个更新包文件命名必须包含日期时间,同类更新包,客户端按日期时间排序逐个逐个下载并执行更新。

4.如何应对更新失败及客户端版本回滚。

基于第3点的机制,更新失败不会在本地留存更新包文件,因此检查更新时服务器会传回所有文件,这样就可以重复执行这个更新。本方案不考虑直接的版本回滚,实现起来有些麻烦,通过另外的方式解决回滚问题,即如果某次更新包有问题,那么就推出新的更新包,用后来的更新覆盖之前的更新来解决问题。


程序构成:

整个自动化更新系统由服务端、客户端构成。服务端即一个提供更新包的远程服务器,客户端即学校安装的BS系统。

基本原理:下载并解压zip替换文件,下载执行sql,下载dat文件并更新题库.

服务端主要包含:

一个包文件夹,更新包以时间命名后都放到包目录下,名称规则:201710201433.zip(年月日时分.zip)
一个文件上传及管理jsp


一个更新检查jsp
客户端:
后台主界面添加代码,管理员登录后自动ajax请求远程服务器,带上本地最后一次更新的文件名(文件名根据不同类型可能是多个),
获得未更新的文件名列表,如果有新的更新,则提示某类型发现N个新更新,是否立即更新,点更新后,前端挨个挨个请求后台下载更新包,执行更新.
程序文件更新(zip),下载后解压并覆盖旧文件;数据库更新,下载sql文件并执行;试题更新,下载试题执行试题导入功能.
更新时在页面上显示更新进度。更新结束要求重启系统。


关键程序代码:

本功能是在一个Struts2、Spring、Hibernate项目中实现的。

服务端:

服务端是一个web应用,主要功能有管理员登录、文件管理、文件上传、更新检查(接收客户端请求,根据客户端的参数)。

此处只贴出更新检查的代码。

  1 <%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%>
  2 <%
  3 String path = request.getContextPath();
  4 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
  5 
  6 
  7 /***
  8  *
  9  *
 10  * 私有云自动更新
 11  *
 12  *
 13  * 自动更新原理:
 14  * 客户端(私有云)admin登录到后台系统后,后台系统会自动从本web获取还未下载的更新包名称列表。
 15  * 更细包包含程序更新包(.zip文件)、数据库更新文件(.sql文件)和题库更新包(.dat文件),
 16  *
 17  * 补丁包(zip)和数据库更新(sql)有可能根据时间推移会产生多个版本,他们的文件名前缀直接以时间命名即可。
 18  * 试题比较特殊,同一套试卷原则上只应该有一个对应的更新文件,如果有新的改动,删除旧的包,取新名上传。
 19  * 同套试卷也支持上传多个包,但是更新检查时只会获得最新那个包名(按文件名中的日期部分排序)。
 20  * 不同的试题试卷号不同,他们是针对具体内容的更新,因此试卷的命名以“试卷编号_加密狗权限识别码_年月日时分.dat”格式,
 21  * 举例:666_yyn_201712051422.dat,
 22  * 释意:
 23  * 666:试卷编号
 24  * yyn:该题属于财务核算实训云平台版及财务管理实训云平台版,不属于管理信息化实训云平台,y=yes,n=no,三个字符分别表示三个版本权限
 25  * 201712051422:本更新包时间点是2017年12月5日14点22分,私有云检查更新时同一套试卷会根据此部分值排序得到最大的。
 26  *
 27  * 客户端(私有云平台)请求检查更新时,默认传来最后一次更新的程序补丁文件名(zip或sql)以及最后一次更新的试题名(多个),
 28  * 本程序根据名称(日期命名的)与本程序包目录下的文件进行对比,得出大于该日期的程序文件名(也就是还未被它下载更新过的)。
 29  * 更新后将最后一次更新的包存放到last-update-file目录,便于下次更新时传给服务器获取新列表。
 30  * 同一时间的程序补丁包和数据库更新文件作为一个整体补丁业务处理,因为它们是相互依存的,同一时间,如果程序和数据库同时发生了变化,
 31  * 那么它们本来就是一个整体更新,更新包必须保持顺序执行,因此它们的区别只是后缀名不同。
 32  *
 33  * 试题的更新,客户端传来最后一次更新的试卷名称(不同的试卷有自己的最后包名)与服务器最新试卷包名(每个试卷都有自己的)进行对比,
 34  * 只要所属试卷的最后一次更新名称(日期命名的)小于服务器返回的,就是要下载安装的。
 35  * 试题更新结束将当前试卷最后一次更新的包放到last-updated-file目录里,便于下次更新对比。
 36  *
 37  * 更新检查时服务端按.zip、.sql、.dat分组排序返回,客户端下载更新时也按分组列表顺序。
 38  *
 39  * 注意,如果sql文件内容包含中文,那么文件和内容必须是UTF-8编码的,否则更新到数据库会形成乱码。
 40  *
 41  * 程序组成:
 42  * 分服务端与客户端,不设计更新前备份与回滚机制,如果某次更新有问题,再出新的更新包执行替换来解决。
 43  * 基本原理:下载并解压zip替换文件,下载执行sql,下载dat文件并更新题库.
 44  * 服务端主要包含:
 45  * 一个包文件夹,更新包以时间命名后都放到包目录下,名称规则:201710201433.zip(年月日时分.zip)
 46  * 一个文件上传及管理jsp
 47  * 一个更新检查jsp
 48  * 客户端:
 49  * 后台主界面添加代码,管理员登录后自动ajax请求远程服务器,带上本地最后一次更新的文件名(文件名根据不同类型可能是多个),
 50  * 获得未更新的文件名列表,如果有新的更新,则提示某类型发现N个新更新,是否立即更新,点更新后,前端挨个挨个请求后台下载更新包,执行更新.
 51  * 程序文件更新(zip),下载后解压并覆盖旧文件;数据库更新,下载sql文件并执行;试题更新,下载试题执行试题导入功能.
 52  * 更新时在页面上显示更新进度。更新结束要求重启系统。
 53  *
 54  *
 55  */
 56 
 57 
 58 
 59 //得到加密狗信息
 60 String encrypt = request.getParameter("encrypt");
 61 if(encrypt==null || "".equals(encrypt) || "0".equals(encrypt)){
 62     
 63     Integer.parseInt("加密狗参数错误!");
 64     return;
 65 }
 66 
 67 //取得传来的最后一次更新的程序文件的名,参数不能带.zip后缀,因为下面要直接用于比较数字大小。
 68 String lastUpdateFileNames = request.getParameter("lastUpdateFileNames");
 69 
 70 System.out.println("lastUpdateFileNames:"+lastUpdateFileNames);
 71 
 72 String[] lastUpdateFileNamesArray = null;//被以逗号分隔的传递来的最后更新的文件名数组。
 73 ArrayList<String> paperNames = new ArrayList<String>();//最后更新的试卷更新包名称集合
 74 
 75 String lastZipName = "";//最后更新的补丁名
 76 String lastSqlName = "";//最后更新的sql脚本名
 77 if(lastUpdateFileNames!=null && !"".equals(lastUpdateFileNames)){
 78     
 79     if(lastUpdateFileNames.indexOf(",")!=-1){
 80         lastUpdateFileNamesArray = lastUpdateFileNames.split(",");
 81     }else{
 82 
 83         lastUpdateFileNamesArray = new String[]{lastUpdateFileNames};
 84     }
 85     
 86     
 87     for(String ss:lastUpdateFileNamesArray){
 88         String fileName = ss.split("\.")[0];
 89         String extendName = ss.split("\.")[1];
 90         if("dat".equals(extendName)){
 91             //将试卷的最后更新的文件名称添加到集合中,便于下面对比
 92             paperNames.add(ss);
 93         }else if("zip".equals(extendName)){
 94             //最后更新的补丁名称
 95             lastZipName = ss;
 96         }else if("sql".equals(extendName)){
 97             //最后更新的补丁名称
 98             lastSqlName = ss;
 99         }
100     }
101 }
102 
103 //本web路径,用于读取包文件列表
104 String realPath = request.getRealPath("/files");
105 
106 
107 String result = "";
108 
109 //读取包文件列表
110 File file = new File(realPath);
111 File[] files=file.listFiles();
112 if(files.length>0){
113     
114     ArrayList<File> fileList = new ArrayList<File>();
115     ArrayList<File> datFileList = new ArrayList<File>();
116     
117     for(File temp:files){
118         String extName = temp.getName().substring(temp.getName().indexOf('.')+1);
119         if("zip".equals(extName) || "sql".equals(extName)){
120             fileList.add(temp);
121         }else if("dat".equals(extName)){
122             //此处还需要根据加密狗过滤无权的试卷(加密狗权限识别码)。
123             //实训云平台加密狗版本号、标识及权限:
124             //0=ER=加密狗加载错误
125             //104=HN=财务核算实训云平台
126             //105=HO=财务管理实训云平台
127             //106=HP=管理信息化实训云平台
128             String permissionsDescribedString = temp.getName().split("_")[1];
129             char[] pd = permissionsDescribedString.toCharArray();
130 
131             //财务核算实训云平台104对应第一个元素
132             if("104".equals(encrypt) && pd[0]=='y'){
133                 datFileList.add(temp);
134             }else
135 
136             //财务管理实训云平台105对应第二个元素
137             if("105".equals(encrypt) && pd[1]=='y'){
138                 datFileList.add(temp);
139             }else
140 
141             //管理信息化实训云平台106对应第三个元素
142             if("106".equals(encrypt) && pd[2]=='y'){
143                 datFileList.add(temp);
144             }
145             
146         } 
147     }
148 
149     //对zip和sql文件名按从小到大排序
150     Collections.sort(fileList, new Comparator<File>() {
151         @Override
152         public int compare(File o1, File o2) {
153             if (o1.isDirectory() && o2.isFile())
154                 return -1;
155             if (o1.isFile() && o2.isDirectory())
156                 return 1;
157             return o1.getName().compareTo(o2.getName());
158         }
159     });
160 
161     
162     
163     
164     //对比补丁包和sql文件,找出文件名(时间)大于传来的名称
165     if(fileList.size()>0){
166 
167         
168         for(int i=0;i<fileList.size();i++){
169             
170             String tempFileName = ((File) fileList.get(i)).getName();
171             
172             //System.out.println(tempFileName);
173             
174             String qzName = tempFileName.split("\.")[0];//取前缀
175             String hzName = tempFileName.split("\.")[1];//取后缀
176 
177             
178             
179             //if(tempName)
180             
181             if("zip".equals(hzName)){
182 
183                 //传来的最后一次更新名有可能为空
184                 if(!"".equals(lastZipName)){
185             
186                     //大于传来的文件名的就是客户端还未更新的文件,将文件名记入list作为结果返回
187                     long lastUpdatepatchNameInt = Long.parseLong(lastZipName.split("\.")[0]);
188                     long tempNameInt = Long.parseLong(qzName);
189                     if(tempNameInt>lastUpdatepatchNameInt){
190 
191                         result += ","+tempFileName;
192                     }
193                 }else{
194 
195                     result += ","+tempFileName;
196                 }
197             }else if("sql".equals(hzName)){
198 
199                 //传来的最后一次更新名有可能为空
200                 if(!"".equals(lastSqlName)){
201             
202                     //大于传来的文件名的就是客户端还未更新的文件,将文件名记入list作为结果返回
203                     long lastUpdatepatchNameInt = Long.parseLong(lastSqlName.split("\.")[0]);
204                     long tempNameInt = Long.parseLong(qzName);
205                     if(tempNameInt>lastUpdatepatchNameInt){
206 
207                         result += ","+tempFileName;
208                     }
209                 }else{
210 
211                     result += ","+tempFileName;
212                 }
213             }
214                 
215             
216             
217         }
218 
219         
220     }
221     
222     
223     //对dat文件进行分组排序,同套试卷的找出最新包,返回最新包名
224     if(datFileList.size()>0){
225 
226         
227         //创建map,对试卷更新包按试卷编码分组存储。
228         HashMap<String,ArrayList<File>> paperMap = new HashMap<String,ArrayList<File>>();
229         for(int i=0;i<datFileList.size();i++){
230             File datFile = (File)datFileList.get(i);//当前文件
231             String paperNumber = datFile.getName().split("_")[0];//取得试卷号(作为map的key)
232             ArrayList<File> tempList = paperMap.get(paperNumber);//从map中取该试卷的所有更新(一个ArrayList集合)
233             if(tempList==null){
234                 tempList = new ArrayList<File>();//如果为空,说明是第一次,则创建一个。
235             }
236             tempList.add(datFile);//将当前文件添加进list
237             paperMap.put(paperNumber,tempList);//将list放回map
238             
239         }
240         
241         //遍历map,对各试卷分组的更新包进行按日期(明细)排序,取最后一个(最新包)名称。
242         for (String key : paperMap.keySet()) {
243             ArrayList<File> tempList = paperMap.get(key);
244 
245             //文件名按从小到大排序
246             Collections.sort(tempList, new Comparator<File>() {
247                 @Override
248                 public int compare(File o1, File o2) {
249                     if (o1.isDirectory() && o2.isFile())
250                         return -1;
251                     if (o1.isFile() && o2.isDirectory())
252                         return 1;
253                     return o1.getName().compareTo(o2.getName());
254                 }
255             });
256 
257             
258             
259             //取排序后的最后一个的名称
260             String lastPaperVersion = tempList.get(tempList.size()-1).getName();
261             //遍历最后更新的试卷包名集合,检查更新,如果传来的试题包名中找不到lastPaperVersion,则说明试卷有新版本,则返回给客户端。
262             if(paperNames!=null && paperNames.size()>0){
263                 boolean flag = false;
264                 for(String temp : paperNames){
265                     if(temp.equals(lastPaperVersion)){
266                         flag = true;
267                         break;
268                     }
269                 }
270                 
271                 if(flag==false){
272 
273                     result += ","+lastPaperVersion;            
274                 }
275             }else{
276                 result += ","+lastPaperVersion;//如果没有传来任何试卷信息,服务端却又试卷更新包,那么说明有新的更新,客户端初次安装就是这种情况。
277             }
278             
279         }
280         
281     }
282     
283     
284     
285     
286     
287     if(!"".equals(result)){
288         result = result.substring(1);
289     }
290     
291 }
292 
293 
294 out.print(result);
295 
296 %>

客户端:

请求检查更新js(用到了jquery):

 1 var global_system_update_files=null;
 2 
 3 $(document).ready(function(){
 4 
 5 
 6 
 7 
 8     /**
 9      *  自动更新流程:
10      *  1.先得到本地最后一次更新的包名,请求远程服务器,将包名上报,得到还未更新的文件列表
11      *  2.判断返回的列表,如果有记录,则弹出自动更新提示,管理员点立即更新后,循环遍历文件列表,并请求后台执行更新.
12      *  3.更新完一个包就显示一个进度.
13      */
14 
15     $.ajax({
16         url:"exam_admin/common/updater!checkUpdate",
17         type:"post",
18         async:true,
19         cache:false,
20         success:function(data,status){
21             var systemFiles = data.names;
22 
23             var tips = "";
24             tips += "<div id='updateTips' style='position:absolute;auto; display:inline-block !important; display:inline; top:4px;right:4px;line-height:20px;border:1px solid orangered;background-color:orange;text-align:center;padding:4px;'>";
25 
26 
27             if(systemFiles!=null && systemFiles!="" && systemFiles!="error"){
28                 systemFiles = systemFiles.split(",");
29                 global_system_update_files = systemFiles;
30 
31                 var zipCount = 0;
32                 var sqlCount = 0;
33                 var datCount = 0;
34                 for(var i=0;i<systemFiles.length;i++){
35                     var extendName = systemFiles[i].split(".")[1];
36                     if(extendName=="zip"){
37                         zipCount++;
38                     }
39                     else if(extendName=="sql"){
40                         sqlCount++;
41                     }
42                     else if(extendName=="dat"){
43                         datCount++;
44                     }
45 
46                 }
47 
48 
49 
50 
51                tips += "发现";
52                 if(zipCount>0){
53                     tips += zipCount+"个程序更新,";
54                 }
55                 if(sqlCount>0){
56                     tips += sqlCount+"个数据库更新,";
57                 }
58                 if(datCount>0){
59                     tips += datCount+"个试题更新,";
60                 }
61 
62                 tips += "<a href='javascript:updateSystem()'>立即更新</a>";
63                 tips += "</div>";
64                 $(document.body).append(tips);
65 
66             }else if(systemFiles=="error"){
67 
68                 tips += "更新检查失败,请检查网络或加密狗是否正常.";
69                 tips += "</div>";
70                 $(document.body).append(tips);
71             }
72 
73 
74         }
75     });
76 
77 
78 
79     //
80 
81 
82 
83 });

后台请求检查更新方法(java struts2 action):

 1     /***
 2      * 检查更新
 3      * 请求服务器,获得需要更新的补丁包或sql文件列表以及需要对比的最新试题包名.
 4      *
 5      * 补丁包和sql作为程序更新,与试题包的处理不同,本方法会获得:
 6      * 还未更新的补丁包(包括zip和sql文件),所有最新试题包文件名
 7      * 还未更新的补丁包可以直接进行安装,而试题更新包名需要与本地相关试卷最后一次更新进对比后确认是否执行更新.
 8      *
 9      * @return
10      */
11     public String checkUpdate(){
12 
13         names = "error";
14 
15         String dirPath = ServletActionContext.getServletContext().getRealPath("/");//得到目标目录的绝对路径
16         ///Users/jianyuchen/IdeaProjects/git_project/chanjetedu-exam-standard/exam-admin-mvc/target/exam-admin-mvc-prod-1.0-SNAPSHOT/
17         //路径为webroot,取更新包名应该用../,也就是到exam/webapps下
18 
19         String lastUpdateNames = "";
20 
21         //定位文件,如果找不到,也说明是初次使用自动更新功能,则创建一个包目录,用于存放下载的文件
22         File file = new File(dirPath+"../"+lastUpdateFileDirName+"/");
23         if(!file.exists()){
24             file.mkdir();
25             lastUpdateNames = "";
26         }else{
27 
28             File[] files=file.listFiles();
29 
30             String namesparameter = "";
31             if(files.length>0){
32                 for(File temp:files){
33                     namesparameter += ","+temp.getName();
34                 }
35                 lastUpdateNames = namesparameter.substring(1);
36             }else{
37                 lastUpdateNames = "";
38             }
39 
40         }
41 
42 
43         //根据最后一次更新文件名查询远程服务器还未更新的文件名串,返回的数据格式:201710201741.sql,201710201741.zip,201710201744.zip,201710201745.zip
44         try {
45 
46             Integer encrypt = 106;//moduleEncryption.getVersion();//将加密狗信息传给服务器,服务器返回有权限更新的文件列表(不同的加密狗试题权限是不同的).
47             String url = server+"check_update.jsp?lastUpdateFileNames="+lastUpdateNames+"&encrypt="+encrypt;
48             HttpURLConnection huc = (HttpURLConnection) new URL(url).openConnection();
49             //创建输入流读取器对象
50             BufferedReader br = new BufferedReader(new InputStreamReader(huc.getInputStream(), "utf-8"));
51             huc.connect();
52             String data = "";
53             String line = null;
54             //循环按行读取文本流
55             while ((line = br.readLine()) != null) {
56                 data += ("
" + line);
57             }
58             data = data.trim();
59 
60             names = data;
61             huc.disconnect();
62         }catch (Exception e){
63             e.printStackTrace();
64         }
65 
66 
67         return "checkUpdate";
68     }

前端通过上面的方法得到需要更新的文件列表字符串,遍历文件名请求后台执行更新的js

 1 /***
 2  * 执行更新函数
 3  * 函数将遍历文件名列表,挨个挨个请求后台进行下载安装.
 4  */
 5 function updateSystem(){
 6     if(global_system_update_files!=null && global_system_update_files.length>0){
 7 
 8         var index = 0;
 9 
10 
11 
12 
13         //创建一个匿名函数去执行请求
14         (function(){
15 
16             var name = global_system_update_files[index];
17 
18 
19 
20             var funarg = arguments;//得到匿名函数本身引用
21 
22             $("#updateTips").html("正在安装第"+(index+1)+"个更新:"+name);
23 
24             //请求后台执行更新
25             $.ajax({
26                 url:"exam_admin/common/updater!executeUpdate?name="+name,
27                 type:"post",
28                 async:true,
29                 cache:false,
30                 success:function(data,status){
31 
32                     //所有的更新完后则不再递归执行.
33                     if(index==global_system_update_files.length-1){
34 
35                         $("#updateTips").css("border","1px solid #00aa00").css("background-color","#66ff66");
36                         $("#updateTips").html("更新完成,请重启服务器后再使用!");
37                         return;
38                     }
39 
40                     index++;
41 
42                     //匿名函数自我递归调用,执行下一个更新
43                     setTimeout(function(){
44                         funarg.callee();
45                     },2000);
46 
47 
48 
49                 },
50                 error:function(){
51                     $("#updateTips").css("border","1px solid orangered").css("background-color","lightcoral");
52                     $("#updateTips").html("第"+(index+1)+"个更新:"+name+"安装失败.");
53 
54                 }
55             });
56 
57 
58 
59         })();
60 
61 
62 
63 
64 
65 
66 
67     }
68 
69 }

后台下载执行更新

  1     /***
  2      * 执行更新
  3      * 下载文件,判断类型,执行解压或调用sql脚本执行器或执行试题的更新
  4      * @return
  5      */
  6     public String executeUpdate(){
  7 
  8         System.out.println("正在安装:"+name);
  9 
 10 
 11         String webapps = ServletActionContext.getServletContext().getRealPath("/")+"../";//得到目标目录的绝对路径
 12 
 13         String ext = name.substring(name.length()-3);
 14 
 15         //删除上一次更新的文件
 16         File f=new File(webapps+""+lastUpdateFileDirName+"/");
 17         File[] fs=f.listFiles();
 18         for(File t:fs){
 19 
 20             //不同后缀的文件只会有一个,所以后缀相等就找到了上次更新的文件,然后删掉
 21             if(!"dat".equals(ext)){
 22                 if(t.getName().split("\.")[1].equals(ext)){
 23                     t.delete();
 24                 }
 25             }else{//由于试卷更新包dat文件组织和命名比较特殊,需要特殊处理
 26                 //试题更新包名称格式:666_yyn_20171205.dat
 27                 //删除试题更新包时,应对比试卷号,不能删除掉其他试卷包.
 28 
 29                 String tempName1 = t.getName().split("_")[0];
 30                 String tempName2 = name.split("_")[0];
 31                 if(tempName1.equals(tempName2)){
 32                     t.delete();
 33                 }
 34 
 35             }
 36 
 37         }
 38 
 39 
 40         //1.根据文件名从远程服务器下载文件
 41         String url = server+"files/"+name;
 42 
 43         String tempfilename = url.substring(url.lastIndexOf("/")+1,url.lastIndexOf("."));
 44         File download=new File(webapps+""+lastUpdateFileDirName+"/"+name);
 45         byte[] tempfile = null;
 46         try {
 47             URL u = new URL(url);
 48             InputStream is = u.openStream();
 49 
 50             //取得文件真实类型(不通过后缀和content-type)
 51             //BufferedInputStream bis = new BufferedInputStream(is);
 52             //String fileType = HttpURLConnection.guessContentTypeFromStream(bis);
 53             //System.out.println(fileType);
 54 
 55             FileOutputStream os = new FileOutputStream(download);
 56             byte[] buffer = new byte[1024];
 57             int length = -1;
 58             while((length = is.read(buffer))!=-1){
 59                 os.write(buffer,0,length);
 60             }
 61             is.close();
 62             os.flush();
 63             os.close();
 64         } catch (Exception e) {
 65             e.printStackTrace();
 66         }
 67 
 68         //2.判断文件类型,如果是zip执行解压覆盖,如果是sql,调用sql脚本执行,如果是dat(试题)文件,则执行导入.
 69         if(("zip").equals(ext)){
 70             //程序更新
 71 
 72             System.out.println("正在解压"+name);
 73 
 74             ZipFile zip=null;
 75             InputStream in=null;
 76             OutputStream out=null;
 77 
 78             try {
 79                 zip=new ZipFile(download);
 80                 Enumeration<?> entries=zip.entries();
 81                 while(entries.hasMoreElements()){
 82                     ZipEntry entry=(ZipEntry) entries.nextElement();
 83                     String zipEntryName=entry.getName();
 84                     in=zip.getInputStream(entry);
 85 
 86                     String outPath=(webapps+zipEntryName).replace("\*", "/");
 87                     //判断路径是否存在,不存在则创建文件路径
 88                     File file=new File(outPath.substring(0, outPath.lastIndexOf('/')));
 89                     if(!file.exists()){
 90                         file.mkdirs();
 91                     }
 92                     //判断文件全路径是否为文件夹,如果是上面已经创建,不需要解压
 93                     if(new File(outPath).isDirectory()){
 94                         continue;
 95                     }
 96                     out=new FileOutputStream(outPath);
 97 
 98                     byte[] buf=new byte[4*1024];
 99                     int len;
100                     while((len=in.read(buf))>=0){
101                         out.write(buf, 0, len);
102                     }
103                     in.close();
104 
105                     //System.out.println("==================解压完毕==================");
106                 }
107             } catch (Exception e) {
108                 //System.out.println("==================解压失败==================");
109                 //删除执行失败的更新包,以便下次再次更新.
110                 download.delete();
111                 e.printStackTrace();
112 
113             }finally{
114                 try {
115                     if(zip!=null){
116                         zip.close();
117                     }
118                     if(in!=null){
119                         in.close();
120                     }
121                     if(out!=null){
122                         out.close();
123                     }
124                 } catch (IOException e) {
125                     e.printStackTrace();
126                 }
127             }
128 
129         }else if(("sql").equals(ext)){
130             //sql脚本
131 
132 
133             System.out.println("正在执行"+name);
134 
135             StringBuffer sb = new StringBuffer();
136             try{
137                 BufferedReader br = new BufferedReader(new FileReader(download));//构造一个BufferedReader类来读取文件
138                 String s = null;
139                 while((s = br.readLine())!=null){//使用readLine方法,一次读一行
140                     sb.append(System.lineSeparator()+s);
141                 }
142                 br.close();
143                 updaterService.ddl_executeSqlScript(sb);
144             }catch(Exception e){
145                 //删除执行失败的更新包,以便下次再次更新.
146                 download.delete();
147                 e.printStackTrace();
148             }
149 
150         }else if(("dat").equals(ext)){
151             //试卷更新
152 
153             //调用试卷包处理方法
154             HashMap<String,Object> paperDetailMap;//创建题数据map
155             try {
156                 // 解密,云平台导出题是用1进行加的密,解密时也用1解密,云平台TblpaperAction的DEFAULT_PASS变量.
157                 byte[] buffer = Files.toByteArray(download);
158                 paperDetailMap = Encryptor.i("1").decrypt(HashMap.class, buffer);
159 
160                 if(paperDetailMap != null && !paperDetailMap.isEmpty()){
161                     paperDetailMap.put("orgcode",getCookieValue(Constant.SESSION_ADMIN_ORGCODE));
162                     paperDetailMap.put("adminUserid",getCookieValue(Constant.SESSION_ADMINUSERID));
163 
164                     /***
165                      * 获取map对象中所有图片的byte[],并读出到相应路径的文件中
166                      * 1、确定图片路径
167                      * 2、将图片读出到指定路径下
168                      * 3、删除map中的图片信息
169                      */
170                     paperDetailMap = tblpaperService.exportPictureToLocalPath(getRequest(),paperDetailMap);
171                     //新添加的方法,2017.08.10 by wangweiaw  end
172 
173                     //将上传的试卷信息保存到数据库,此方法会判断是否已存在,已存在会删除,再新增.
174                     tblpaperService.ddl_savePaperDetailByMap(paperDetailMap);
175                 }else{
176 
177                 }
178 
179             }catch(Exception ex){
180                 //删除执行失败的更新包,以便下次再次更新.
181                 download.delete();
182                 ex.printStackTrace();
183             }
184 
185 
186         }
187 
188 
189         //执行完等待200毫秒,防止网速太快,执行太快,前端进度一闪而过,搞慢点,显示个进度效果.
190         try {
191             Thread.currentThread().sleep(200);
192         } catch (InterruptedException e) {
193             e.printStackTrace();
194         }
195 
196         return "executeUpdate";
197     }
原文地址:https://www.cnblogs.com/jsper/p/7994167.html