springboot實(shí)現(xiàn)分段上傳功能的示例代碼
文件上傳下載
斷點(diǎn)續(xù)傳,增量上傳等
導(dǎo)入依賴
<!--jdk提供的關(guān)于文件上傳--> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <!--apache提供了的文件上傳--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.14</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency>
前端配置
前端使用的是webuploader
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>webuploader</title> </head> <!--引入CSS--> <link rel="stylesheet" type="text/css" rel="external nofollow" > <style> #upload-container, #upload-list{width: 500px; margin: 0 auto; } #upload-container{cursor: pointer; border-radius: 15px; background: #EEEFFF; height: 200px;} #upload-list{height: 800px; border: 1px solid #EEE; border-radius: 5px; margin-top: 10px; padding: 10px 20px;} #upload-container>span{widows: 100%; text-align: center; color: gray; display: block; padding-top: 15%;} .upload-item{margin-top: 5px; padding-bottom: 5px; border-bottom: 1px dashed gray;} .percentage{height: 5px; background: green;} .btn-delete, .btn-retry{cursor: pointer; color: gray;} .btn-delete:hover{color: orange;} .btn-retry:hover{color: green;} </style> <!--引入JS--> <body> <div id="upload-container"> <span>點(diǎn)擊或?qū)⑽募献е链松蟼?lt;/span> </div> <div id="upload-list"> <!-- <div class="upload-item"> <span>文件名:123</span> <span data-file_id="" class="btn-delete">刪除</span> <span data-file_id="" class="btn-retry">重試</span> <div class="percentage"></div> </div> --> </div> <button id="picker" style="display: none;">點(diǎn)擊上傳文件</button> </body> <script src="http://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script> <script src="https://cdn.bootcss.com/webuploader/0.1.1/webuploader.js"></script> <script> $('#upload-container').click(function(event) { $("#picker").find('input').click(); }); var uploader = WebUploader.create({ auto: true,// 選完文件后,是否自動(dòng)上傳。 swf: 'https://cdn.bootcss.com/webuploader/0.1.1/Uploader.swf',// swf文件路徑 server: 'http://www.test.com/zyb.php',// 文件接收服務(wù)端。 dnd: '#upload-container', pick: '#picker',// 內(nèi)部根據(jù)當(dāng)前運(yùn)行是創(chuàng)建,可能是input元素,也可能是flash. 這里是div的id multiple: true, // 選擇多個(gè) chunked: true,// 開起分片上傳。 threads: 5, // 上傳并發(fā)數(shù)。允許同時(shí)最大上傳進(jìn)程數(shù)。 method: 'POST', // 文件上傳方式,POST或者GET。 fileSizeLimit: 1024*1024*100*100, //驗(yàn)證文件總大小是否超出限制, 超出則不允許加入隊(duì)列。 fileSingleSizeLimit: 1024*1024*100, //驗(yàn)證單個(gè)文件大小是否超出限制, 超出則不允許加入隊(duì)列。 fileVal:'upload', // [默認(rèn)值:'file'] 設(shè)置文件上傳域的name。 }); uploader.on('fileQueued', function(file) { // 選中文件時(shí)要做的事情,比如在頁面中顯示選中的文件并添加到文件列表,獲取文件的大小,文件類型等 console.log(file.ext) // 獲取文件的后綴 console.log(file.size) // 獲取文件的大小 console.log(file.name); var html = '<div class="upload-item"><span>文件名:'+file.name+'</span><span data-file_id="'+file.id+'" class="btn-delete">刪除</span><span data-file_id="'+file.id+'" class="btn-retry">重試</span><div class="percentage '+file.id+'" style="width: 0%;"></div></div>'; $('#upload-list').append(html); }); uploader.on('uploadProgress', function(file, percentage) { console.log(percentage * 100 + '%'); var width = $('.upload-item').width(); $('.'+file.id).width(width*percentage); }); uploader.on('uploadSuccess', function(file, response) { console.log(file.id+"傳輸成功"); }); uploader.on('uploadError', function(file) { console.log(file); console.log(file.id+'upload error') }); $('#upload-list').on('click', '.upload-item .btn-delete', function() { // 從文件隊(duì)列中刪除某個(gè)文件id file_id = $(this).data('file_id'); // uploader.removeFile(file_id); // 標(biāo)記文件狀態(tài)為已取消 uploader.removeFile(file_id, true); // 從queue中刪除 console.log(uploader.getFiles()); }); $('#upload-list').on('click', '.btn-retry', function() { uploader.retry($(this).data('file_id')); }); uploader.on('uploadComplete', function(file) { console.log(uploader.getFiles()); }); </script> </html>
前端上傳數(shù)據(jù)
#前臺(tái)傳輸文件的表單信息 id: WU_FILE_1 文件ID name: 錄制_2021_03_19_08_52_06_801.mp4 #文件名 type: video/mp4 #類型 lastModifiedDate: Fri Mar 19 2021 08:52:22 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間) size: 45711359 #大小 chunks: 9 #總分片數(shù) chunk: 0 #當(dāng)前分片的編號(hào) upload: (binary)
斷點(diǎn)續(xù)傳[增量上傳]
思路:前端會(huì)對文件進(jìn)行分片并以此調(diào)用此接口,后臺(tái)對文件進(jìn)行保存。當(dāng)判斷文件時(shí)最后一個(gè)分片文件的時(shí)候?qū)Ρ镜乇4娴奈募M(jìn)行組合。
@RequestMapping("/update") public String update(HttpServletRequest request,MultipartFile files, @RequestParam("chunks") Integer chunks, @RequestParam("chunk") Integer chunk, @RequestParam("name") String name){ //根據(jù)原理,需要準(zhǔn)備寫的流os,寫的位置locate, //分片臨時(shí)位置tempLocate,當(dāng)前分片數(shù)chunk[表單獲取],總分片數(shù)chunks[表單獲取], //文件名稱name[表單獲取],臨時(shí)分片名稱tempName String tempName = null, locate="E:\\file",tempLocate="E:\\file\\temp"; BufferedOutputStream os = null; try { //----------------參數(shù)判斷---------------- if (name == null || chunk == null || chunks == null){ log.error("參數(shù)未獲取到"); return ""; } if (files == null){ log.error("數(shù)據(jù)上傳失敗"); return ""; } //----------------斷點(diǎn)續(xù)傳---------------- tempName = name+"_"+chunk; File assest = new File(tempLocate,tempName); //文件不存在再寫,實(shí)現(xiàn)斷點(diǎn)續(xù)傳 if (!assest.exists()){ files.transferTo(assest); } //----------------合并數(shù)據(jù)---------------- long curTime = System.currentTimeMillis(); if (chunk == chunks-1){ File file = new File(locate,name); os = new BufferedOutputStream(new FileOutputStream(file)); for (int i = 0; i < chunks; i++) { File temp = new File(tempLocate,name+"_"+i); while (!temp.exists()){ //if (System.currentTimeMillis() - curTime > waitTime){ // return "合并時(shí)過長,請重新上傳文件"; //} Thread.sleep(100); } byte[] bytes = FileUtils.readFileToByteArray(temp); os.write(bytes); os.flush(); temp.delete(); } os.flush(); return "上傳文件成功"; } }catch (Exception e) { log.warn("文件上傳出錯(cuò)"); return "文件上傳錯(cuò)誤,等待網(wǎng)頁維護(hù)人員進(jìn)行維護(hù)"; } finally { try { if (os != null){ os.close(); } }catch (IOException e){ log.info("文件上傳流關(guān)閉失敗"); } } return "null"; }
分片下載
思路:前端支持分片,會(huì)在頭信息中帶有Range信息,內(nèi)容為起始位置-結(jié)束位置
。通過range讀取文件傳給前端
須知:
- 范圍讀取
- 請求頭編寫
分片下載
@RequestMapping("/download") public void download(HttpServletRequest request, HttpServletResponse response){ File file = new File("E:\\file\\測試.mp4"); if (!file.exists()){ return; } //-----------------設(shè)置參數(shù)----------------- Long begin = 0l,//開始讀取位置 fileSize=file.length(),//內(nèi)容長度 end = fileSize - 1,//結(jié)束讀取位置 curSize = 0l;//本次讀取分片的大小 BufferedOutputStream os = null;//寫流 RandomAccessFile is = null;//讀流 String fileName;//文件名稱 try { //-----------------設(shè)置文件的基礎(chǔ)參數(shù)----------------- //獲取文件名稱 fileName = file.getName(); String range = request.getHeader("Range"); //判斷range:bytes=XXX-XXX if (range != null && range.contains("bytes=") && range.contains("-")){ range = range.replaceAll("bytes=","").trim(); String[] split = range.split("-"); if (!StringUtils.hasLength(split[0])){ //特殊情況1:-XXX end = Long.parseLong(split[1]); }else if (split.length == 1){ //特殊情況2:XXX- begin = Long.parseLong(split[0]); }else if (split.length == 2){ //理想情況 begin = Long.parseLong(split[0]); end = Long.parseLong(split[1]); if (end > fileSize - 1){ end = fileSize - 1; } } }else { end = fileSize - 1; } curSize = end - begin + 1; //-----------------前端響應(yīng)頭設(shè)置----------------- //設(shè)置為下載 response.setContentType("application/x-download"); //設(shè)置下載文件的信息 response.addHeader("Content-Disposition", "attachment;filename="+new String(fileName.getBytes(),"ISO8859_1")); //支持分片下載 response.setHeader("Accept-Ranges","bytes"); //Content-Type 表示資源類型,如:文件類型 response.setHeader("Content-Type", request.getServletContext().getMimeType(fileName)); //設(shè)置文件大小 response.setHeader("Content-Length",String.valueOf(curSize)); //設(shè)置響應(yīng)數(shù)據(jù)量:bytes [下載開始位置]-[結(jié)束位置]/[文件大小] response.setHeader("Content-Range","bytes "+begin+"-"+end+"/"+fileSize); //設(shè)置專門的響應(yīng)類型 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); //自定義一些文件頭 response.setHeader("fileSize", String.valueOf(fileSize)); response.setHeader("fileName", new String(fileName.getBytes(),"ISO8859_1")); //-----------------讀寫文件----------------- os = new BufferedOutputStream(response.getOutputStream()); is = new RandomAccessFile(file,"r"); byte[] buffer = new byte[1024]; int len = 0; Long sum = 0l;//累計(jì)添加的流 //跳過已經(jīng)獲取的 is.seek(begin); while (sum<curSize){ int readSize = buffer.length; if ((curSize-sum)<readSize){ readSize = (int) (curSize-sum); } if ((len = is.read(buffer,0,readSize))==-1){ break; } os.write(buffer,0,len); sum+=len; } log.info("下載成功"); } catch (Exception e) { log.warn("下載出現(xiàn)錯(cuò)誤"); }finally { if (os != null){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if (is != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }
分片下載合并
前端不好對文件進(jìn)行分片的合并,需要后端進(jìn)行合并處理
思路:
- 通過JAVA模擬請求
- 將文件通過 FileUtils.readFileToByteArray(temp);讀取合并
不進(jìn)行延伸的原因:
- 對于B/S結(jié)構(gòu),因?yàn)闊o法知道用戶下載文件的具體位置,就無法進(jìn)行合并
- 為了用戶安全,每個(gè)分片下載都會(huì)進(jìn)行提示,用戶體驗(yàn)差
綜合所述,除了C/S結(jié)構(gòu),本地端不建議使用
到此這篇關(guān)于springboot實(shí)現(xiàn)分段上傳的文章就介紹到這了,更多相關(guān)springboot分段上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Servlet第一個(gè)項(xiàng)目的發(fā)布(入門)
這篇文章主要介紹了Servlet第一個(gè)項(xiàng)目的發(fā)布,下面是用servlet實(shí)現(xiàn)的一個(gè)簡單的web項(xiàng)目,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2021-04-04工廠模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了工廠模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理的相關(guān)資料,需要的朋友可以參考下2017-08-08解決nacos升級(jí)spring cloud 2020.0無法使用bootstrap.yml的問題
這篇文章主要介紹了解決nacos升級(jí)spring cloud 2020.0無法使用bootstrap.yml的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06java高并發(fā)ScheduledThreadPoolExecutor類深度解析
這篇文章主要為大家介紹了java高并發(fā)ScheduledThreadPoolExecutor類源碼深度解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11使用Java如何將圖片轉(zhuǎn)成Base64編碼,并壓縮至40k
這篇文章主要介紹了使用Java如何將圖片轉(zhuǎn)成Base64編碼,并壓縮至40k問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06