簡介
WebUploader是由Baidu WebFE(FEX)團隊開發的一個簡單的以HTML5為主,FLASH為輔的現代文件上傳組件。在現代的瀏覽器里面能充分發揮HTML5的優勢,同時又不摒棄主流IE瀏覽器,沿用原來的FLASH運行時,兼容IE6+,iOS 6+, android 4+。兩套運行時,同樣的調用方式,可供用戶任意選用。 采用大文件分片並發上傳,極大的提高了文件上傳效率。
特點
分片、並發
分片與並發結合,將一個大文件分割成多塊,並發上傳,極大地提高大文件的上傳速度。當網絡問題導致傳輸錯誤時,只需要重傳出錯分片,而不是整個文件。另外分片傳輸能夠更加實時的跟蹤上傳進度。
預覽、壓縮
支持常用圖片格式jpg,jpeg,gif,bmp,png預覽與壓縮,節省網絡數據傳輸。解析jpeg中的meta信息,對於各種orientation做了正確的處理,同時壓縮后上傳保留圖片的所有原始meta數據。
多途徑添加文件
支持文件多選,類型過濾,拖拽(文件&文件夾),圖片粘貼功能。粘貼功能主要體現在當有圖片數據在剪切板中時(截屏工具如QQ(Ctrl + ALT + A), 網頁中右擊圖片點擊復制),Ctrl + V便可添加此圖片文件。
HTML5 & FLASH
兼容主流瀏覽器,接口一致,實現了兩套運行時支持,用戶無需關心內部用了什么內核。同時Flash部分沒有做任何UI相關的工作,方便不關心Flash的用戶擴展和自定義業務需求。
MD5秒傳
當文件體積大、量比較多時,支持上傳前做文件md5值驗證,一致則可直接跳過。如果服務端與前端統一修改算法,取段md5,可大大提升驗證性能,耗時在20ms左右。
易擴展、可拆分
采用可拆分機制,將各個功能獨立成了小組件,可自由搭配。采用AMD規范組織代碼,清晰明了,方便高級玩家擴展。
下載
下載地址:https://github.com/fex-team/webuploader/releases
可以選擇最新版進行下載。下載的壓縮包中有如下文件。
├── Uploader.swf // SWF文件,當使用Flash運行時需要引入。 ├── webuploader.js // 完全版本。 ├── webuploader.min.js // min版本 ├── webuploader.nolog.js // 完全版本不帶日志功能。 ├── webuploader.nolog.min.js // min版本 ├── webuploader.flashonly.js // 只有Flash實現的版本。 ├── webuploader.flashonly.min.js // min版本 ├── webuploader.html5only.js // 只有Html5實現的版本。 ├── webuploader.html5only.min.js // min版本 ├── webuploader.withoutimage.js // 去除圖片處理的版本,包括HTML5和FLASH. └── webuploader.withoutimage.min.js // min版本
示例
具體文檔可以直接參考官方網站的說明,這個使用說明還是很詳細的,這里就不重復搬運了。
官網地址:http://fex.baidu.com/webuploader/doc/index.html
下面看一下我們項目中的實際應用。
前端代碼
先引入相關js文件:
<script type="text/javascript" src="js/jquery-1.11.3.min.js"></script> <script type="text/javascript" src="js/webuploader.nolog.js"></script>
頁面引入相關控件:
<div id="uploadBtn" class="btn btn-upload">上傳文件</div> <div id="list" class="upload-box clearfix"></div>
前端js代碼:
1 //驗證數組方法 2 Array.prototype.contains = function (obj) { 3 var i = this.length; 4 while (i--) { 5 if (this[i] === obj) { 6 return true; 7 } 8 } 9 return false; 10 } 11 12 var arr_md5 = [];//加載頁面時,將已上傳成功的分片數組化 13 var chunks = 0;//分片總數量,用來上傳成功以后校驗分片文件總數 14 var repart_chunks = 0; 15 var upload_success = false; 16 var times = 0; 17 var interval = 0; 18 19 //注冊組件 20 WebUploader.Uploader.register({ 21 'before-send': 'preupload' 22 }, { 23 preupload: function( block ) { 24 var me = this, 25 owner = this.owner, 26 deferred = WebUploader.Deferred(); 27 chunks = block.chunks; 28 owner.md5File(block.blob) 29 //及時顯示進度 30 .progress(function(percentage) { 31 console.log('Percentage:', percentage); 32 }) 33 //如果讀取出錯了,則通過reject告訴webuploader文件上傳出錯。 34 .fail(function() { 35 deferred.reject(); 36 console.log("==1=="); 37 }) 38 //md5值計算完成 39 .then(function( md5 ) { 40 if(arr_md5.contains(md5)){//數組中包含+(block.chunk+1) 41 deferred.reject(); 42 console.log("分片已上傳"+block.file.id); 43 $("#speed_"+block.file.id).text("正在斷點續傳中..."); 44 if(block.end == block.total){ 45 $("#speed_"+block.file.id).text("上傳完畢"); 46 $("#pro_"+block.file.id).css( 'width', '100%' ); 47 }else{ 48 $("#pro_"+block.file.id).css( 'width',(block.end/block.total) * 100 + '%' ); 49 } 50 }else{ 51 deferred.resolve(); 52 console.log("開始上傳分片:"+md5); 53 } 54 }); 55 return deferred.promise(); 56 } 57 }); 58 59 //初始化WebUploader 60 var uploader = WebUploader.create({ 61 //swf文件路徑 62 //swf: 'http://www.ssss.com.cn/js/webuploader/Uploader.swf', 63 //文件接收服務端。 64 server: '/upload/UploadPhotoSlt?methodName=fileupload&tokenid='+$("#tokenid").val(), 65 //選擇文件的按鈕,可選。內部根據當前運行是創建,可能是input元素,也可能是flash. 66 pick: '#uploadBtn', 67 //不壓縮image, 默認如果是jpeg,文件上傳前會壓縮一把再上傳! 68 resize: false, 69 auto:true, 70 //是否分片 71 chunked :true, 72 //分片大小 73 chunkSize :1024*1024*2, 74 chunkRetry :3, 75 threads :3,//最大並發 76 fileNumLimit :1, 77 fileSizeLimit :1024*1024*1024*1024, 78 fileSingleSizeLimit: 1024*1024*1024, 79 duplicate :true, 80 accept: { 81 title: 'file', 82 extensions: 'jpg,png,ai,zip,rar,psd,pdf,cdr,psd,tif', 83 mimeTypes: '*/*' 84 } 85 }); 86 87 //當文件被加入隊列之前觸發,此事件的handler返回值為false,則此文件不會被添加進入隊列。 88 uploader.on('beforeFileQueued',function(file){ 89 if(",jpg,png,ai,zip,rar,psd,pdf,cdr,psd,tif,".indexOf(","+file.ext+",")<0){ 90 alert("不支持的文件格式"); 91 } 92 }); 93 94 //當有文件添加進來的時候 95 uploader.on( 'fileQueued', function( file ) { 96 times = 1; 97 var src = "http://www.wodexiangce.cn/images/type/JPG.jpg"; 98 if(file.ext == 'jpg'){ 99 src = "http://www.wodexiangce.cn/images/type/JPG.jpg"; 100 }else if(file.ext == 'png'){ 101 src = "http://www.wodexiangce.cn/images/type/PNG.jpg"; 102 }else if(file.ext == 'ai'){ 103 src = "http://www.wodexiangce.cn/images/type/AI.jpg"; 104 }else if(file.ext == 'zip'){ 105 src = "http://www.wodexiangce.cn/images/type/ZIP.jpg"; 106 }else if(file.ext == 'rar'){ 107 src = "http://www.wodexiangce.cn/images/type/RAR.jpg"; 108 }else if(file.ext == 'psd'){ 109 src = "http://www.wodexiangce.cn/images/type/PSD.jpg"; 110 }else if(file.ext == 'pdf'){ 111 src = "http://www.wodexiangce.cn/images/type/PDF.jpg"; 112 }else if(file.ext == 'cdr'){ 113 src = "http://www.wodexiangce.cn/images/type/CDR.jpg"; 114 }else if(file.ext == 'tif'){ 115 src = "http://www.wodexiangce.cn/images/type/TIF.jpg"; 116 } 117 upload_success = false; 118 $("#list").html( 119 '<div class="clearfix"><img src='+src+' width="50px" class="icon-file" ></img>'+ 120 '<div class="fl" style="margin-left: 70px;">'+ 121 '<p class="speed font-12" id="speed_'+file.id+'">校驗文件...</p>'+ 122 '<div class="progress">'+ 123 '<span id="pro_'+file.id+'" class="progressing"></span>'+ 124 '</div>'+ 125 '<span class="file-size">'+(file.size/1024/1024).toFixed(2)+'MB</span>'+ 126 '<a href="javascript:void(0);" id="stopOretry_'+file.id+'" onclick="stop(\''+file.id+'\');" class="a-pause">暫停</a>'+ 127 '</div></div><span class="file-name">'+file.name+'</span><br/>' ); 128 //文件開始上傳后開始計時,計算實時上傳速度 129 interval = setInterval(function(){times++;},1000); 130 131 }); 132 133 //上傳過程中觸發,攜帶上傳進度。文件上傳過程中創建進度條實時顯示。 134 uploader.on( 'uploadProgress', function( file, percentage ) { 135 var status_pre = file.size*percentage-arr_md5.length*2*1024*1024; 136 if(status_pre<=0){ 137 return; 138 } 139 $("#pro_"+file.id).css( 'width', percentage * 100 + '%' ); 140 var speed = ((status_pre/1024)/times).toFixed(0); 141 $("#speed_"+file.id).text(speed +"KB/S"); 142 if(percentage == 1){ 143 $("#speed_"+file.id).text("上傳完畢"); 144 } 145 }); 146 147 //文件上傳成功時觸發 148 uploader.on( 'uploadSuccess', function( file,response) { 149 console.log("成功上傳"+file.name+" res="+response); 150 $.ajax({ 151 type:'get', 152 url:'/upload/UploadPhotoSlt', 153 dataType: 'json', 154 data: { 155 methodName:'composeFile', 156 name:file.name, 157 chunks:chunks, 158 tokenid:$("#tokenid").val() 159 }, 160 async:false, 161 success: function(data) { 162 console.log("==compose=="+data.status); 163 if(data.status == "success"){ 164 upload_success = true; 165 $("#url").val(data.url); 166 console.log(data.url); 167 }else{ 168 upload_success = false; 169 alert(data.errstr); 170 } 171 } 172 }); 173 }); 174 175 //文件上傳異常失敗觸發 176 uploader.on( 'uploadError', function( file,reason ) { 177 console.log("失敗"+reason ); 178 }); 179 180 //某個文件開始上傳前觸發,一個文件只會觸發一次。 181 uploader.on( 'uploadStart', function(file) { 182 console.info("file="+file.name); 183 //分片文件上傳之前 184 $.ajax({ 185 type:'get', 186 url:'/upload/UploadPhotoSlt', 187 dataType: 'json', 188 data: { 189 methodName:'md5Validation', 190 name:file.name, 191 tokenid:$("#tokenid").val() 192 }, 193 async:false, 194 success: function(data) { 195 if(data.md5_arr != ""){ 196 arr_md5 = data.md5_arr.split(",") 197 }else{ 198 arr_md5 = new Array(); 199 } 200 } 201 }); 202 }); 203 204 //當所有文件上傳結束時觸發。 205 uploader.on( 'uploadFinished', function() { 206 207 }); 208 209 function stop(id){ 210 uploader.stop(true); 211 $("#stopOretry_"+id).attr("onclick","retry('"+id+"')"); 212 $("#stopOretry_"+id).text("恢復"); 213 clearInterval(interval); 214 } 215 function retry(id){ 216 uploader.retry(); 217 $("#stopOretry_"+id).attr("onclick","stop('"+id+"')"); 218 $("#stopOretry_"+id).text("暫停"); 219 interval = setInterval(function(){times++;},1000); 220 }
后端代碼
后台Java代碼,使用到commons-fileupload-1.1.1.jar
可以參考:Apache Commons FileUpload 實現文件上傳
servlet:HttpServletBasic
1 package com.wodexiangce.web.servlet; 2 3 import java.lang.reflect.InvocationTargetException; 4 import java.lang.reflect.Method; 5 6 import javax.servlet.ServletContext; 7 import javax.servlet.ServletException; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 12 import org.springframework.web.context.WebApplicationContext; 13 import org.springframework.web.context.support.WebApplicationContextUtils; 14 15 public class HttpServletBasic extends HttpServlet{ 16 private static final long serialVersionUID = 1L; 17 18 protected WebApplicationContext wac = null; 19 20 public void init() throws ServletException { 21 super.init(); 22 ServletContext sc = this.getServletContext(); 23 wac = WebApplicationContextUtils.getRequiredWebApplicationContext(sc); 24 } 25 26 /** 27 * 方法轉發器,通過方法名調用相應的方法 。 28 * 可以看出,不能分出方法同名而參數不同的方法 。 29 */ 30 protected void service(HttpServletRequest request,HttpServletResponse response){ 31 32 String methodName = request.getParameter("methodName") ; 33 34 //當前類所有的方法名稱 35 Method[] methods = this.getClass().getDeclaredMethods() ; 36 for(Method m:methods){ 37 if(m.getName().equals(methodName)){ 38 try { 39 m.invoke(this, new Object[]{request,response}) ; 40 } catch (IllegalArgumentException e) { 41 e.printStackTrace(); 42 } catch (IllegalAccessException e) { 43 e.printStackTrace(); 44 } catch (InvocationTargetException e) { 45 e.printStackTrace(); 46 } 47 break ; 48 } 49 } 50 } 51 52 public void destroy() { 53 wac = null; 54 } 55 56 }
servlet:UploadPhotoSlt
1 package com.wodexiangce.web.servlet; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.util.Date; 6 import java.util.HashMap; 7 import java.util.List; 8 import java.util.Map; 9 10 import javax.servlet.http.HttpServletRequest; 11 import javax.servlet.http.HttpServletResponse; 12 13 import net.sf.json.JSONObject; 14 15 import org.apache.commons.fileupload.FileItem; 16 import org.apache.commons.fileupload.FileItemFactory; 17 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 18 import org.apache.commons.fileupload.servlet.ServletFileUpload; 19 import org.apache.commons.lang.math.NumberUtils; 20 21 import com.wodexiangce.persistence.model.WdxcQywFiles; 22 import com.wodexiangce.services.QywFileService; 23 import com.wodexiangce.util.FileUtils; 24 25 26 public class UploadPhotoSlt extends HttpServletBasic{ 27 28 private static final long serialVersionUID = 1L; 29 30 /** 31 * 文件上傳 32 * @param request 33 * @param response 34 * @throws IOException 35 */ 36 @SuppressWarnings({ "unchecked", "deprecation" }) 37 public void fileupload(HttpServletRequest request, HttpServletResponse response)throws IOException{ 38 try { 39 System.out.println("=================fileupload==================="); 40 response.addHeader("Access-Control-Allow-Origin", "*"); 41 String useridStr = request.getParameter("tokenid"); 42 if(useridStr==null||"".equals(useridStr)||useridStr.length()<3){ 43 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'token校驗錯誤'}").toString()); 44 return; 45 } 46 long userid = Long.parseLong(useridStr); 47 boolean isMultipart = ServletFileUpload.isMultipartContent(request); 48 if (isMultipart) { 49 FileItemFactory factory = new DiskFileItemFactory(); 50 ServletFileUpload upload = new ServletFileUpload(factory); 51 52 // 得到所有的表單域,它們目前都被當作FileItem 53 List<FileItem> fileItems = upload.parseRequest(request); 54 55 String id = ""; 56 String fileName = ""; 57 // 如果大於1說明是分片處理 58 int chunks = 1; 59 int chunk = 0; 60 FileItem tempFileItem = null; 61 for (FileItem fileItem : fileItems) { 62 if (fileItem.getFieldName().equals("id")) { 63 id = fileItem.getString(); 64 } else if (fileItem.getFieldName().equals("name")) { 65 fileName = new String(fileItem.getString().getBytes("ISO-8859-1"), "UTF-8"); 66 } else if (fileItem.getFieldName().equals("chunks")) { 67 chunks = NumberUtils.toInt(fileItem.getString()); 68 } else if (fileItem.getFieldName().equals("chunk")) { 69 chunk = NumberUtils.toInt(fileItem.getString()); 70 } else if (fileItem.getFieldName().equals("file")) { 71 tempFileItem = fileItem; 72 } 73 } 74 System.out.println("id="+id+" filename="+fileName+" chunks="+chunks+" chunk="+chunk+" size="+tempFileItem.getSize()); 75 //臨時目錄用來存放所有分片文件 76 String tempFileDir = getTempFilePath()+ File.separator + userid; 77 File parentFileDir = new File(tempFileDir); 78 if (!parentFileDir.exists()) { 79 parentFileDir.mkdirs(); 80 } 81 //分片處理時,前台會多次調用上傳接口,每次都會上傳文件的一部分到后台 82 File tempPartFile = new File(parentFileDir, fileName + "_" + chunk+ ".part"); 83 84 String MD5 = FileUtils.copyInputStreamToFile(tempFileItem.getInputStream(),tempPartFile); 85 int count = 0; 86 while(MD5==null&&count<3){ 87 MD5 = FileUtils.copyInputStreamToFile(tempFileItem.getInputStream(),tempPartFile); 88 count++; 89 } 90 if(MD5==null){ 91 throw new Exception("分片文件:"+chunk+"上傳失敗"); 92 } 93 //分片文件上傳成功以后,重命名分片文件,規則:原名之前拼接MD5碼 94 tempPartFile.renameTo(new File(parentFileDir, fileName + "_" + chunk+"_"+MD5+ ".part")); 95 System.out.println("MD5="+MD5); 96 response.getWriter().write(JSONObject.fromObject("{\"md5\":\""+MD5+"\"}").toString()); 97 } 98 } catch (Exception e) { 99 e.printStackTrace(); 100 } 101 } 102 103 /** 104 * 重復驗證 105 * @param request 106 * @param response 107 * @throws IOException 108 */ 109 public void md5Validation(HttpServletRequest request, HttpServletResponse response)throws IOException{ 110 try { 111 System.out.println("=================md5Validation==================="); 112 response.addHeader("Access-Control-Allow-Origin", "*"); 113 response.setCharacterEncoding("utf-8"); 114 String useridStr = request.getParameter("tokenid"); 115 if(useridStr==null||"".equals(useridStr)||useridStr.length()<3){ 116 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'token校驗錯誤'}").toString()); 117 return; 118 } 119 long userid = Long.parseLong(useridStr); 120 String tempFileDir = getTempFilePath()+ File.separator + userid; 121 File parentFileDir = new File(tempFileDir); 122 if (!parentFileDir.exists()) { 123 response.getWriter().write(JSONObject.fromObject("{\"md5_arr\":\"\"}").toString()); 124 return; 125 } 126 String fileName = request.getParameter("name"); 127 fileName = new String(fileName.getBytes("ISO-8859-1"),"UTF-8"); 128 System.out.println("fileName="+fileName); 129 130 StringBuffer sb = new StringBuffer(); 131 for(File file:parentFileDir.listFiles()){ 132 String name = file.getName(); 133 if(name.endsWith(".part") && name.startsWith(fileName)){ 134 //此文件有上傳記錄,解析MD5 135 name = name.substring(name.lastIndexOf("_")+1,name.lastIndexOf(".part")); 136 if(name.length()==32){ 137 if(sb.length()>0){ 138 sb.append(","); 139 } 140 sb.append(name); 141 } 142 } 143 } 144 response.getWriter().write(JSONObject.fromObject("{\"md5_arr\":\""+sb.toString()+"\"}").toString()); 145 } catch (Exception e) { 146 e.printStackTrace(); 147 } 148 } 149 150 /** 151 * 文件所有分片上傳完畢之后,保存文件數據到數據庫 152 * @param request 153 * @param response 154 * @throws IOException 155 */ 156 public void composeFile(HttpServletRequest request, HttpServletResponse response)throws IOException{ 157 try { 158 System.out.println("=================composeFile==================="); 159 response.addHeader("Access-Control-Allow-Origin", "*"); 160 response.setCharacterEncoding("utf-8"); 161 String useridStr = request.getParameter("tokenid"); 162 if(useridStr==null||"".equals(useridStr)||useridStr.length()<3){ 163 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'token校驗錯誤'}").toString()); 164 return; 165 } 166 long userid = Long.parseLong(useridStr); 167 String tempFileDir = getTempFilePath()+ File.separator + userid; 168 File parentFileDir = new File(tempFileDir); 169 if (!parentFileDir.exists()) { 170 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'目錄不存在'}").toString()); 171 return; 172 } 173 String fileName = request.getParameter("name"); 174 fileName = new String(fileName.getBytes("ISO-8859-1"),"UTF-8"); 175 String chunks = request.getParameter("chunks"); 176 System.out.println("fileName="+fileName); 177 Map<String,File> chunkFileMap = new HashMap<String, File>(); 178 179 String md5 = null; 180 for(File file:parentFileDir.listFiles()){ 181 String name = file.getName(); 182 if(name.endsWith(".part") && name.startsWith(fileName)){ 183 md5 = name.substring(name.lastIndexOf("_")+1,name.lastIndexOf(".part")); 184 System.out.println("md5="+md5); 185 if(md5.length()==32){//完整的分片文件 186 String index = name.replace(fileName, "").replace("_"+md5+".part", "").replace("_", ""); 187 chunkFileMap.put(index, file); 188 } 189 } 190 } 191 //分片總數和分片文件數一致,則證明分片文件已完整上傳,可以持久化數據 192 if(chunkFileMap.size() == Integer.parseInt(chunks.trim())){ 193 System.out.println("===========開始合並文件分片=========="); 194 WdxcQywFiles QywFile = new WdxcQywFiles(); 195 QywFile.setFilename(fileName); 196 QywFile.setCreatetime(new Date()); 197 QywFile.setFilepath("/site/photos/file/"+userid+"/"+fileName); 198 QywFile.setFiledownurl("http://www.sssss.cn/file/"+userid+"/"+fileName); 199 200 201 //AI、PDF、EPS、CDR、PSD、JPG、TIFF 202 if(fileName.toLowerCase().endsWith(".tif")){ 203 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/TIF.jpg"); 204 }else if(fileName.toLowerCase().endsWith(".jpg")){ 205 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/JPG.jpg"); 206 }else if(fileName.toLowerCase().endsWith(".psd")){ 207 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/PSD.jpg"); 208 }else if(fileName.toLowerCase().endsWith(".ai")){ 209 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/AI.jpg"); 210 }else if(fileName.toLowerCase().endsWith(".cdr")){ 211 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/CDR.jpg"); 212 }else if(fileName.toLowerCase().endsWith(".zip")){ 213 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/ZIP.jpg"); 214 }else if(fileName.toLowerCase().endsWith(".pdf")){ 215 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/PDF.jpg"); 216 }else if(fileName.toLowerCase().endsWith(".png")){ 217 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/PNG.jpg"); 218 }else if(fileName.toLowerCase().endsWith(".rar")){ 219 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/RAR.jpg"); 220 } 221 QywFile.setUserid(userid); 222 FileUtils.fileCompose(QywFile, chunkFileMap);//合並文件 223 File file = new File(QywFile.getFilepath()); 224 if(!file.exists()){ 225 System.out.println("文件合並失敗:"+QywFile.getFilepath()); 226 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'文件合並失敗'}").toString()); 227 return; 228 } 229 230 //把文件路徑保存到數據庫 231 QywFileService fileService = (QywFileService)wac.getBean("qywFileService"); 232 Long fileid = (Long)fileService.saveQywFile(QywFile); 233 System.out.println("文件保存成功:"+fileid); 234 235 response.getWriter().write(JSONObject.fromObject("{\"status\":\"success\",'url':'"+QywFile.getFiledownurl()+"'}").toString()); 236 }else{ 237 System.out.println("分片數量不正確,實際分片數量:"+chunkFileMap.size()+" 總分片數量:"+chunks); 238 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'分片數量不正確'}").toString()); 239 } 240 } catch (Exception e) { 241 e.printStackTrace(); 242 System.err.println("文件合並失敗:"+e.getMessage()); 243 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'文件合並失敗'}").toString()); 244 } 245 } 246 247 /** 248 * 指定臨時目錄 249 * @return 250 */ 251 private String getTempFilePath(){ 252 return "/site/xxxxxx/temp"; 253 } 254 255 }
補充:涉及到的實體類和工具類
1 package com.wodexiangce.persistence.model; 2 3 import java.io.Serializable; 4 import java.math.BigDecimal; 5 import java.util.Date; 6 7 8 public class WdxcQywFiles implements Serializable{ 9 10 private static final long serialVersionUID = 1L; 11 12 private long id; 13 14 private long userid; 15 16 private String filename; 17 18 private String filepreview; 19 20 private String filepath; 21 22 private int deleted; 23 24 private Date deletedtime; 25 26 private Date createtime; 27 28 private String filedownurl; 29 30 private BigDecimal filesize; 31 32 public long getId() { 33 return id; 34 } 35 public void setId(long id) { 36 this.id = id; 37 } 38 public long getUserid() { 39 return userid; 40 } 41 public void setUserid(long userid) { 42 this.userid = userid; 43 } 44 public String getFilename() { 45 return filename; 46 } 47 public void setFilename(String filename) { 48 this.filename = filename; 49 } 50 public String getFilepreview() { 51 return filepreview; 52 } 53 public void setFilepreview(String filepreview) { 54 this.filepreview = filepreview; 55 } 56 public String getFilepath() { 57 return filepath; 58 } 59 public void setFilepath(String filepath) { 60 this.filepath = filepath; 61 } 62 /** 63 * 刪除狀態 0:正常 1:已刪除 64 * @return 65 */ 66 public int getDeleted() { 67 return deleted; 68 } 69 public void setDeleted(int deleted) { 70 this.deleted = deleted; 71 } 72 public Date getDeletedtime() { 73 return deletedtime; 74 } 75 public void setDeletedtime(Date deletedtime) { 76 this.deletedtime = deletedtime; 77 } 78 public Date getCreatetime() { 79 return createtime; 80 } 81 public void setCreatetime(Date createtime) { 82 this.createtime = createtime; 83 } 84 /** 85 * 文件下載URL前綴(需拼寫分片名稱) 86 * @return 87 */ 88 public String getFiledownurl() { 89 return filedownurl; 90 } 91 public void setFiledownurl(String filedownurl) { 92 this.filedownurl = filedownurl; 93 } 94 /** 95 * 文件大小 96 * @return 97 */ 98 public BigDecimal getFilesize() { 99 return filesize; 100 } 101 public void setFilesize(BigDecimal bigDecimal) { 102 this.filesize = bigDecimal; 103 } 104 }
1 package com.wodexiangce.util; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileOutputStream; 6 import java.io.InputStream; 7 import java.lang.reflect.Method; 8 import java.math.BigDecimal; 9 import java.nio.MappedByteBuffer; 10 import java.nio.channels.FileChannel; 11 import java.security.AccessController; 12 import java.security.MessageDigest; 13 import java.security.PrivilegedAction; 14 import java.util.Map; 15 16 import com.wodexiangce.persistence.model.WdxcQywFiles; 17 18 /** 19 * 文件處理工具類 20 */ 21 public class FileUtils { 22 /** 23 * 保存文件流到文件,同時返回文件MD5 24 * @param inputStream 25 * @param file 26 */ 27 public static String copyInputStreamToFile(InputStream inputStream,File file){ 28 MessageDigest md = null; 29 try { 30 if(inputStream == null || inputStream == null) { 31 return null; 32 } 33 md = MessageDigest.getInstance("MD5"); 34 byte[] b = new byte[102400];//set b 100Kb byte. 35 int n = inputStream.read(b); 36 int totalBytes = n; 37 FileOutputStream fos = new FileOutputStream(file); 38 while (n > -1) { 39 fos.write(b, 0, n); 40 fos.flush(); 41 n = inputStream.read(b); 42 totalBytes += n; 43 } 44 fos.close(); 45 inputStream.close(); 46 System.out.println("文件總大小:"+totalBytes); 47 //生成文件MD5值 48 FileInputStream in = new FileInputStream(file); 49 //文件內存映射,提高讀寫超大文件可能和速度,但會造成文件鎖定不可操作。 50 MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()); 51 md.update(byteBuffer); 52 clean(byteBuffer); 53 54 byte[] encrypt = md.digest(); 55 StringBuffer sb = new StringBuffer(); 56 for (int i = 0; i < encrypt.length; i++) { 57 String hex = Integer.toHexString(0xff & encrypt[i]); 58 if (hex.length() == 1) 59 sb.append('0'); 60 sb.append(hex); 61 } 62 in.close(); 63 return sb.toString(); 64 } catch (Exception e) { 65 e.printStackTrace(); 66 return null; 67 } 68 } 69 70 /** 71 * 文件合並 72 * @param QywFile 73 * @param chunkFileMap 74 */ 75 public static void fileCompose(WdxcQywFiles QywFile,Map<String,File> chunkFileMap){ 76 String path = QywFile.getFilepath(); 77 File mainFile = new File(path); 78 if(!mainFile.getParentFile().exists()){ 79 mainFile.getParentFile().mkdirs(); 80 } 81 try { 82 FileChannel out = new FileOutputStream(mainFile).getChannel(); 83 FileChannel in = null; 84 long start = System.currentTimeMillis(); 85 for(int i=0;i<chunkFileMap.size();i++){ 86 File file = chunkFileMap.get(String.valueOf(i)); 87 System.out.println("file="+file.getName()); 88 in = new FileInputStream(file).getChannel(); 89 MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); 90 out.write(buf); 91 in.close(); 92 FileUtils.clean(buf); 93 } 94 System.out.println("文件大小:"+mainFile.length()/1024/1024+" M"); 95 QywFile.setFilesize(new BigDecimal(mainFile.length())); 96 long end = System.currentTimeMillis(); 97 System.out.println("常規方法合並耗時:"+(end-start)/1000+" 秒"); 98 }catch (Exception e) { 99 e.printStackTrace(); 100 } 101 } 102 103 104 @SuppressWarnings("unchecked") 105 public static void clean(final Object buffer) throws Exception { 106 AccessController.doPrivileged(new PrivilegedAction() { 107 public Object run() { 108 try { 109 Method getCleanerMethod = buffer.getClass().getMethod("cleaner",new Class[0]); 110 getCleanerMethod.setAccessible(true); 111 sun.misc.Cleaner cleaner =(sun.misc.Cleaner)getCleanerMethod.invoke(buffer,new Object[0]); 112 cleaner.clean(); 113 } catch(Exception e) { 114 e.printStackTrace(); 115 } 116 return null;} 117 }); 118 } 119 }