背景:
我公司做的考試系統(基於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;width: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 += ("\r\n" + 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 }