JavaWeb文件上傳下載實(shí)例講解(酷炫的文件上傳技術(shù))
一、課程概述
在Web應(yīng)用系統(tǒng)開發(fā)中,文件上傳功能是非常常用的功能,今天來主要講講JavaWeb中的文件上傳功能的相關(guān)技術(shù)實(shí)現(xiàn),并且隨著互聯(lián)網(wǎng)技術(shù)的飛速發(fā)展,用戶對(duì)網(wǎng)站的體驗(yàn)要求越來越高,在文件上傳功能的技術(shù)上也出現(xiàn)許多創(chuàng)新點(diǎn),例如異步上傳文件,拖拽式上傳,黏貼上傳,上傳進(jìn)度監(jiān)控,文件縮略圖,大文件斷點(diǎn)續(xù)傳,大文件秒傳等等。
本課程需要的基礎(chǔ)知識(shí):
了解基本的Http協(xié)議內(nèi)容
基本IO流操作技術(shù)
Servlet基礎(chǔ)知識(shí)
javascript/jQuery技術(shù)基礎(chǔ)知識(shí)
二、文件上傳的基礎(chǔ)
對(duì)于文件上傳,瀏覽器在上傳的過程中是將文件以流的形式提交到服務(wù)器端的,并且所有流數(shù)據(jù)都會(huì)隨著Http請(qǐng)求攜帶到服務(wù)器端。所以,文件上傳時(shí)的請(qǐng)求內(nèi)容格式要能夠基本看懂。
文件上傳頁面:
<form action="/itheimaUpload/UploadServlet" method="post" enctype="multipart/form-data"> 請(qǐng)選擇上傳的文件:<input type="file" name="attach"/><br/> <input type="submit" value="提交"/> </form>
Http請(qǐng)求內(nèi)容:
三、Java后臺(tái)使用Servlet接收文件
如果使用Servlet獲取上傳文件的輸入流然后再解析里面的請(qǐng)求參數(shù)是比較麻煩,所以一般后臺(tái)選擇采用Apache的開源工具common-fileupload這個(gè)文件上傳組件。
//Java后臺(tái)代碼:Commons-fileUpload組件上傳文件 public class UploadServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.配置緩存 DiskFileItemFactory factory = new DiskFileItemFactory(1*1024*1024,new File("c:/tempFiles/")); //2.創(chuàng)建ServleFileUpload對(duì)象 ServletFileUpload sfu = new ServletFileUpload(factory); //解決文件名稱中文問題 sfu.setHeaderEncoding("utf-8"); //3.解析 try { List<FileItem> list = sfu.parseRequest(request); //解析所有內(nèi)容 if(list!=null){ for(FileItem item:list){ //判斷是否為普通表單參數(shù) if(item.isFormField()){ //普通表單參數(shù) //獲取表單的name屬性名稱 String fieldName = item.getFieldName(); //獲取表單參數(shù)值 String value = item.getString("utf-8"); }else{ //文件 if(item.getName()!=null && !item.getName().equals("")) { //保存到服務(wù)器硬盤 FileUtils.copyInputStreamToFile(item.getInputStream(), new File("c:/targetFiles/"+item.getName())); item.delete(); } } } } } catch (FileUploadException e) { e.printStackTrace(); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
四、使用WebUploader上傳組件
文件上傳頁面的前端我們可以選擇使用一些比較好用的上傳組件,例如百度的開源組件WebUploader,這個(gè)組件基本能滿足文件上傳的一些日常所需功能,如異步上傳文件,拖拽式上傳,黏貼上傳,上傳進(jìn)度監(jiān)控,文件縮略圖,甚至是大文件斷點(diǎn)續(xù)傳,大文件秒傳。
下載WebUpload組件
http://fex.baidu.com/webuploader/ 到WebUpload官網(wǎng)下載WebUpload包
WebUpload目錄結(jié)構(gòu):
基本文件上傳Demo(包含上傳進(jìn)度)
前端
1.1 在頁面導(dǎo)入所需css,js
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/webuploader.css"> <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath }/js/webuploader.js"></script>
1.2 編寫上傳頁面標(biāo)簽
<!-- 上傳div --> <div id="uploader"> <!-- 顯示文件列表信息 --> <ul id="fileList"></ul> <!-- 選擇文件區(qū)域 --> <div id="filePicker">點(diǎn)擊選擇文件</div> </div>
1.3 編寫webupload代碼
<script type="text/javascript"> //1.初始化WebUpload,以及配置全局的參數(shù) var uploader = WebUploader.create( { //flashk控件的地址 swf: "${pageContext.request.contextPath}/js/Uploader.swf", //后臺(tái)提交地址 server:"${pageContext.request.contextPath}/UploadServlet", //選擇文件控件的標(biāo)簽 pick:"#filePicker", //自動(dòng)上傳文件 auto:true, } ); //2.選擇文件后,文件信息隊(duì)列展示 // 注冊(cè)fileQueued事件:當(dāng)文件加入隊(duì)列后觸發(fā) // file: 代表當(dāng)前選擇的文件 uploader.on("fileQueued",function(file){ //追加文件信息div $("#fileList").append("<div id='"+file.id+"' class='fileInfo'><span>"+file.name+"</span><div class='state'>等待上傳...</div><span class='text'></span></div>"); }); //3.注冊(cè)上傳進(jìn)度監(jiān)聽 //file: 正在上傳的文件 //percentage: 當(dāng)前進(jìn)度的比例。最大為1.例如:0.2 uploader.on("uploadProgress",function(file,percentage){ var id = $("#"+file.id); //更新狀態(tài)信息 id.find("div.state").text("上傳中..."); //更新上傳百分比 id.find("span.text").text(Math.round(percentage*100)+"%"); }); //4.注冊(cè)上傳完畢監(jiān)聽 //file:上傳完畢的文件 //response:后臺(tái)回送的數(shù)據(jù),以json格式返回 uploader.on("uploadSuccess",function(file,response){ //更新狀態(tài)信息 $("#"+file.id).find("div.state").text("上傳完畢"); });
2)后端Servlet代碼
DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload sfu = new ServletFileUpload(factory); sfu.setHeaderEncoding("utf-8"); try { List<FileItem> items = sfu.parseRequest(request); for(FileItem item:items){ if(item.isFormField()){ //普通信息 }else{ //文件信息 //判斷只有文件才需要進(jìn)行保存處理 System.out.println("接收的文件名稱:"+item.getName()); //拷貝文件到后臺(tái)的硬盤 FileUtils.copyInputStreamToFile(item.getInputStream(), new File(serverPath+"/"+item.getName())); System.out.println("文件保存成功"); } } } catch (FileUploadException e) { e.printStackTrace(); }
生成圖片縮略圖
關(guān)鍵點(diǎn):調(diào)用uploader.makeThumb()方法生成縮略圖
uploader.on("fileQueued",function(file){ //追加文件信息div $("#fileList").append("<div id='"+file.id+"' class='fileInfo'><img/><span>"+file.name+"</span><div class='state'>等待上傳...</div><span class='text'></span></div>"); //制造圖片縮略圖:調(diào)用makeThumb()方法 //error: 制造縮略圖失敗 //src: 縮略圖的路徑 uploader.makeThumb(file,function(error,src){ var id = $("#"+file.id); //如果失敗,則顯示“不能預(yù)覽” if(error){ id.find("img").replaceWith("不能預(yù)覽"); } //成功,則顯示縮略圖到指定位置 id.find("img").attr("src",src); }); });
拖拽,黏貼上傳
1)頁面添加拖拽區(qū)域的div
<!-- 上傳div --> <div id="uploader"> <!-- 文件拖拽區(qū)域 --> <div id="dndArea"> <p>將文件直接拖拽到這里即可自動(dòng)上傳</p> </div> <!-- 顯示文件列表信息 --> <ul id="fileList"></ul> <!-- 選擇文件區(qū)域 --> <div id="filePicker">點(diǎn)擊選擇文件</div> </div>
2)在webuploader的全局配置參數(shù)添加拖拽功能的參數(shù)
//1.初始化WebUpload,以及配置全局的參數(shù) var uploader = WebUploader.create( { //flashk控件的地址 swf: "${pageContext.request.contextPath}/js/Uploader.swf", //后臺(tái)提交地址 server:"${pageContext.request.contextPath}/UploadServlet", //選擇文件控件的標(biāo)簽 pick:"#filePicker", //自動(dòng)上傳文件 auto:true, //開啟拖拽功能,指定拖拽區(qū)域 dnd:"#dndArea", //禁用頁面其他地方的拖拽功能,防止頁面直接打開文件 disableGlobalDnd:true //開啟黏貼功能 paste:"#uploader" } );
大文件分塊上傳
1)在webuploader全局參數(shù)中添加分塊上傳參數(shù)
//1.初始化WebUpload,以及配置全局的參數(shù) var uploader = WebUploader.create( { //flashk控件的地址 swf: "${pageContext.request.contextPath}/js/Uploader.swf", //后臺(tái)提交地址 server:"${pageContext.request.contextPath}/UploadServlet", //選擇文件控件的標(biāo)簽 pick:"#filePicker", //自動(dòng)上傳文件 auto:true, //開啟拖拽功能,指定拖拽區(qū)域 dnd:"#dndArea", //禁用頁面其他地方的拖拽功能,防止頁面直接打開文件 disableGlobalDnd:true, //開啟黏貼功能 paste:"#uploader", //分塊上傳設(shè)置 //是否分塊上傳 chunked:true, //每塊文件大?。J(rèn)5M) chunkSize:5*1024*1024, //開啟幾個(gè)并發(fā)線程(默認(rèn)3個(gè)) threads:3, //在上傳當(dāng)前文件時(shí),準(zhǔn)備好下一個(gè)文件 prepareNextFile:true } );
2)監(jiān)控上傳文件的三個(gè)時(shí)間點(diǎn)
添加以上三個(gè)配置后,會(huì)發(fā)現(xiàn)當(dāng)文件超過5M時(shí),webuploader自動(dòng)把文件會(huì)分幾個(gè)請(qǐng)求發(fā)送給后臺(tái)
每個(gè)分塊請(qǐng)求,包含的信息:
可以監(jiān)聽文件分塊上傳的三個(gè)重要的時(shí)間點(diǎn)。
before-send-file : 在所有分塊發(fā)送之前調(diào)用 before-send: 如果有分塊,在每個(gè)分塊發(fā)送之前調(diào)用 after-send-file: 在所有分塊發(fā)送完成之后調(diào)用 //5.監(jiān)控文件上傳的三個(gè)時(shí)間點(diǎn)(注意:該段代碼必須放在WebUploader.create之前) //時(shí)間點(diǎn)1::所有分塊進(jìn)行上傳之前(1.可以計(jì)算文件的唯一標(biāo)記;2.可以判斷是否秒傳) //時(shí)間點(diǎn)2: 如果分塊上傳,每個(gè)分塊上傳之前(1.詢問后臺(tái)該分塊是否已經(jīng)保存成功,用于斷點(diǎn)續(xù)傳) //時(shí)間點(diǎn)3:所有分塊上傳成功之后(1.通知后臺(tái)進(jìn)行分塊文件的合并工作) WebUploader.Uploader.register({ "before-send-file":"beforeSendFile", "before-send":"beforeSend", "after-send-file":"afterSendFile" },{ //時(shí)間點(diǎn)1::所有分塊進(jìn)行上傳之前調(diào)用此函數(shù) beforeSendFile:function(){ //1.計(jì)算文件的唯一標(biāo)記,用于斷點(diǎn)續(xù)傳和秒傳 //2.請(qǐng)求后臺(tái)是否保存過該文件,如果存在,則跳過該文件,實(shí)現(xiàn)秒傳功能 }, //時(shí)間點(diǎn)2:如果有分塊上傳,則 每個(gè)分塊上傳之前調(diào)用此函數(shù) beforeSend:function(){ //1.請(qǐng)求后臺(tái)是否保存過當(dāng)前分塊,如果存在,則跳過該分塊文件,實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能 }, //時(shí)間點(diǎn)3:所有分塊上傳成功之后調(diào)用此函數(shù) afterSendFile:function(){ //1.如果分塊上傳,則通過后臺(tái)合并所有分塊文件 } });
before-send-file邏輯:
//利用md5File()方法計(jì)算文件的唯一標(biāo)記符 //該函數(shù)接收一個(gè)deferred beforeSendFile:function(file){ //創(chuàng)建一個(gè)deffered var deferred = WebUploader.Deferred(); //1.計(jì)算文件的唯一標(biāo)記,用于斷點(diǎn)續(xù)傳和秒傳 (new WebUploader.Uploader()).md5File(file,0,5*1024*1024) .progress(function(percentage){ $("#"+file.id).find("div.state").text("正在獲取文件信息..."); }) .then(function(val){ uniqueFileTag = val; $("#"+file.id).find("div.state").text("成功獲取文件信息"); //只有文件信息獲取成功,才進(jìn)行下一步操作 deferred.resolve(); }); //alert(uniqueFileTag); //2.請(qǐng)求后臺(tái)是否保存過該文件,如果存在,則跳過該文件,實(shí)現(xiàn)秒傳功能 //返回deffered return deferred.promise(); }
before-send邏輯:
//向后臺(tái)發(fā)送當(dāng)前文件的唯一標(biāo)記,用于后臺(tái)創(chuàng)建保存分塊文件的目錄 beforeSend:function(){ //攜帶當(dāng)前文件的唯一標(biāo)記到后臺(tái),用于讓后臺(tái)創(chuàng)建保存該文件分塊的目錄 this.owner.options.formData.fileMd5 = fileMd5; }
3)后臺(tái)需要保存所有分塊文件
//為每個(gè)文件創(chuàng)建一個(gè)目錄,并保存這個(gè)文件的所有分塊文件 //判斷是否已經(jīng)分塊上傳 if(chunks!=null){ System.out.println("分塊處理..."); //進(jìn)行分塊上傳了 //建立一個(gè)臨時(shí)目錄,用于保存所有分塊文件 File chunksDir = new File(serverPath+"/"+fileMd5); if(!chunksDir.exists()){ chunksDir.mkdir(); } if(chunk!=null){ //保存分塊文件 File chunkFile = new File(chunksDir.getPath()+"/"+chunk); FileUtils.copyInputStreamToFile(item.getInputStream(), chunkFile); }
4)前臺(tái)通知后臺(tái)合并所有分塊文件
//前臺(tái)通知后臺(tái)合并文件 after-send-file邏輯: afterSendFile:function(file){ //1.如果分塊上傳,則通過后臺(tái)合并所有分塊文件 //請(qǐng)求后臺(tái)合并文件 $.ajax( { type:"POST", url:"${pageContext.request.contextPath}/UploadCheckServlet?action=mergeChunks", data:{ //文件唯一標(biāo)記 fileMd5:fileMd5, //文件名稱 fileName:file.name }, dataType:"json", success:function(response){ alert(response.msg); } } ); } //后臺(tái)合并所有分塊文件 if("mergeChunks".equals(action)){ System.out.println("開始合并文件..."); //合并文件 String fileMd5 = request.getParameter("fileMd5"); String fileName = request.getParameter("fileName"); //讀取目錄里面的所有文件 File f = new File(serverPath+"/"+fileMd5); File[] fileArray = f.listFiles(new FileFilter(){ //排除目錄,只要文件 public boolean accept(File pathname) { if(pathname.isDirectory()){ return false; } return true; } }); //轉(zhuǎn)成集合,便于排序 List<File> fileList = new ArrayList<File>(Arrays.asList(fileArray)); //從小到大排序 Collections.sort(fileList, new Comparator<File>() { public int compare(File o1, File o2) { if(Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())){ return -1; } return 1; } }); File outputFile = new File(serverPath+"/"+fileName); //創(chuàng)建文件 outputFile.createNewFile(); //輸出流 FileChannel outChannel = new FileOutputStream(outputFile).getChannel(); //合并 FileChannel inChannel; for(File file : fileList){ inChannel = new FileInputStream(file).getChannel(); inChannel.transferTo(0, inChannel.size(), outChannel); inChannel.close(); //刪除分片 file.delete(); } //清除文件夾 File tempFile = new File(serverPath+"/"+fileMd5); if(tempFile.isDirectory() && tempFile.exists()){ tempFile.delete(); } //關(guān)閉流 outChannel.close(); response.setContentType("text/html;charset=utf-8"); response.getWriter().write("{\"msg\":\"合并成功\"}"); }
大文件斷點(diǎn)續(xù)傳
在實(shí)現(xiàn)了分塊上傳的基礎(chǔ)上,實(shí)現(xiàn)斷點(diǎn)續(xù)傳就非常簡(jiǎn)單了?。。?/p>
前端:
//時(shí)間點(diǎn)2:如果有分塊上傳,則 每個(gè)分塊上傳之前調(diào)用此函數(shù) //block:代表當(dāng)前分塊對(duì)象 beforeSend:function(block){ //1.請(qǐng)求后臺(tái)是否保存過當(dāng)前分塊,如果存在,則跳過該分塊文件,實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能 var deferred = WebUploader.Deferred(); //請(qǐng)求后臺(tái)是否保存完成該文件信息,如果保存過,則跳過,如果沒有,則發(fā)送該分塊內(nèi)容 $.ajax( { type:"POST", url:"${pageContext.request.contextPath}/UploadCheckServlet?action=checkChunk", data:{ //文件唯一標(biāo)記 fileMd5:fileMd5, //當(dāng)前分塊下標(biāo) chunk:block.chunk, //當(dāng)前分塊大小 chunkSize:block.end-block.start }, dataType:"json", success:function(response){ if(response.ifExist){ //分塊存在,跳過該分塊 deferred.reject(); }else{ //分塊不存在或者不完整,重新發(fā)送該分塊內(nèi)容 deferred.resolve(); } } } ); //攜帶當(dāng)前文件的唯一標(biāo)記到后臺(tái),用于讓后臺(tái)創(chuàng)建保存該文件分塊的目錄 this.owner.options.formData.fileMd5 = fileMd5; return deferred.promise(); },
后臺(tái):
//檢查該分塊是否存在或者完整保存 private void checkChunk(HttpServletRequest request, HttpServletResponse response) throws IOException, FileNotFoundException { System.out.println("checkChunk..."); String fileMd5 = request.getParameter("fileMd5"); String chunk = request.getParameter("chunk"); String chunkSize = request.getParameter("chunkSize"); File checkFile = new File(serverPath+"/"+fileMd5+"/"+chunk); response.setContentType("text/html;charset=utf-8"); //檢查文件是否存在,且大小是否一致 if(checkFile.exists() && checkFile.length()==Integer.parseInt(chunkSize)){ response.getWriter().write("{\"ifExist\":1}"); }else{ response.getWriter().write("{\"ifExist\":0}"); } }
文件秒傳
在所有分塊請(qǐng)求之前,就已經(jīng)可以進(jìn)行實(shí)現(xiàn)秒傳功能?。?!
前端:
beforeSendFile:function(file){ //創(chuàng)建一個(gè)deffered var deferred = WebUploader.Deferred(); //1.計(jì)算文件的唯一標(biāo)記,用于斷點(diǎn)續(xù)傳和秒傳 (new WebUploader.Uploader()).md5File(file,0,5*1024*1024) .progress(function(percentage){ $("#"+file.id).find("div.state").text("正在獲取文件信息..."); }) .then(function(val){ fileMd5 = val; $("#"+file.id).find("div.state").text("成功獲取文件信息"); //2.請(qǐng)求后臺(tái)是否保存過該文件,如果存在,則跳過該文件,實(shí)現(xiàn)秒傳功能 $.ajax( { type:"POST", url:"${pageContext.request.contextPath}/UploadCheckServlet?action=fileCheck", data:{ //文件唯一標(biāo)記 fileMd5:fileMd5 }, dataType:"json", success:function(response){ if(response.ifExist){ $("#"+file.id).find("div.state").text("秒傳成功"); //如果存在,則跳過該文件,秒傳成功 deferred.reject(); }else{ //繼續(xù)上傳 deferred.resolve(); } } } ); }); //返回deffered return deferred.promise(); },
后臺(tái):
//檢查文件的md5數(shù)據(jù)是否跟在數(shù)據(jù)庫存在 private void fileCheck(HttpServletRequest request, HttpServletResponse response) throws IOException, FileNotFoundException { String fileMd5 = request.getParameter("fileMd5"); //模擬數(shù)據(jù)庫 Map<String,String> database = new HashMap<String,String>(); database.put("576018603f4091782b68b78af85704a1", "01.課程回顧.itcast"); response.setContentType("text/html;charset=utf-8"); if(database.containsKey(fileMd5)){ response.getWriter().write("{\"ifExist\":1}"); }else{ response.getWriter().write("{\"ifExist\":0}"); } }
以上所述是小編給大家介紹的JavaWeb文件上傳下載實(shí)例講解(酷炫的文件上傳技術(shù)),希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- JavaWeb實(shí)現(xiàn)文件上傳下載功能實(shí)例解析
- JavaWeb實(shí)現(xiàn)文件上傳下載功能實(shí)例詳解
- Javaweb實(shí)現(xiàn)上傳下載文件的多種方法
- JavaWeb項(xiàng)目實(shí)現(xiàn)文件上傳動(dòng)態(tài)顯示進(jìn)度實(shí)例
- JavaWeb實(shí)現(xiàn)多文件上傳及zip打包下載
- JavaWeb中struts2實(shí)現(xiàn)文件上傳下載功能實(shí)例解析
- JavaWeb實(shí)現(xiàn)文件上傳與下載的方法
- javaweb實(shí)現(xiàn)文件上傳示例代碼
- JavaWeb實(shí)現(xiàn)文件上傳與下載實(shí)例詳解
- JavaWeb實(shí)現(xiàn)簡(jiǎn)單上傳文件功能
相關(guān)文章
Springboot教程之如何設(shè)置springboot熱重啟
這篇文章主要介紹了Springboot教程之如何設(shè)置springboot熱重啟,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07MybatisPlus多數(shù)據(jù)源及事務(wù)解決思路
這篇文章主要介紹了MybatisPlus多數(shù)據(jù)源及事務(wù)解決思路,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式
這篇文章主要介紹了詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04spring boot @ResponseBody轉(zhuǎn)換JSON 時(shí) Date 類型處理方法【兩種方法】
這篇文章主要介紹了spring boot @ResponseBody轉(zhuǎn)換JSON 時(shí) Date 類型處理方法,主要給大家介紹Jackson和FastJson兩種方式,每一種方法給大家介紹的都非常詳細(xì),需要的朋友可以參考下2018-08-08