基于resumable.js的切片上傳大文件,且顯示進(jìn)度條(基于layui的進(jìn)度條), 并結(jié)合后臺使用springboot接收切片,合并切片的完整實例
1, 下載resumable.js 或使用 cdn引入js
可以到https://www.bootcdn.cn/resumable.js/ 下載
2, 前端代碼 單個文件上傳顯示
<form class="layui-form" action="" lay-filter="addForm"> <div class="layui-form-item"> <label class="layui-form-label">文件上傳</label> <div class="layui-input-block"> <div class="layui-btn" id="uploadBigFile"> <i class="layui-icon layui-icon-upload"></i> 選擇文件 </div> <div class="layui-progress layui-hide" id="demo0" style="margin-top: 20px" lay-showPercent="true" lay-filter="file-upload-progress"> <div class="layui-progress-bar" lay-percent="0%"></div> </div> </div> </div> </form> <script src="layui.js"></script> <script src="resumable.js"></script> <script> layui.config({ base: '/vendor/layuiAdmin/res/' // 靜態(tài)資源所在路徑 }).use(function(){ let $ = layui.$ ,layer = layui.layer ,element = layui.element; //基于Resumable.js const r = new Resumable({ target: '/uploadFile', // 后端分片上傳接口 chunkSize: 1 * 1024 * 1024, // 分片大?。J(rèn) 1MB) simultaneousUploads: 3, // 并發(fā)請求數(shù) testChunks: false, // 不進(jìn)行測試分塊完整性檢查,以提高性能 generateUniqueIdentifier: function(file) { // 提供自定義的文件唯一標(biāo)識符生成方法 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } }); r.assignBrowse(document.getElementById('uploadBigFile')); // 文件添加到隊列時觸發(fā) r.on('fileAdded', function(file){ console.log('File added', file); $("#demo0").removeClass('layui-hide') r.upload() }); // 文件上傳開始時觸發(fā) r.on('fileProgress', function(file){ // 渲染進(jìn)度條組件 element.progress('file-upload-progress', Math.floor(file.progress() * 100) + '%'); // 設(shè)置 50% 的進(jìn)度 //console.log('File progress', file.progress()); }); // 文件上傳成功時觸發(fā) r.on('fileSuccess', function(file, message){ // console.log('File uploaded successfully', file, message); let obj = JSON.parse(message) console.log("新的文件名:" + obj.data) }); // 文件上傳失敗時觸發(fā) r.on('fileError', function(file, message){ console.log('File upload error', file, message); }); }); </script>
3, 后臺springboot控制器代碼
@RestController public class AdmUploadController { //配置的上傳目錄 @Value("${web.uploadPath}") private String uploadPath; //接收分片文件上傳 @PostMapping("uploadFile") public R uploadFile( @RequestParam("resumableChunkNumber") int chunkNumber, @RequestParam("resumableTotalChunks") int totalChunks, @RequestParam("file") MultipartFile chunk, HttpServletRequest request) throws IOException { //表單body和url都會傳遞以下兩個參數(shù),參數(shù)接收會拼接 String fileName = request.getParameter("resumableFilename"); String identifier = request.getParameter("resumableIdentifier"); // 目標(biāo)文件路徑,identifier是唯一的標(biāo)識符,用于存儲文件分片 String targetPath = uploadPath + identifier; File uploads = new File(targetPath); //創(chuàng)建臨時文件夾 if (!uploads.exists()) {uploads.mkdirs();} System.out.println(targetPath+"/"+fileName + "." + chunkNumber); Path chunkPath = Paths.get(targetPath,fileName + "." + chunkNumber); // 分片文件路徑 Files.copy(chunk.getInputStream(), chunkPath, StandardCopyOption.REPLACE_EXISTING); // 保存分片到服務(wù)器 // 檢查是否所有分片都已上傳完畢,如果是,則合并分片文件到最終文件 if (chunkNumber == totalChunks) { String newName = mergeChunks(targetPath, identifier, totalChunks,fileName); // 合并分片的方法實現(xiàn)見下文 return R.builder().msg("All upload success").code(200).data(newName).build(); // 所有分片上傳完畢,返回成功消息或進(jìn)行其他處理 } else { return R.builder().msg("chunk upload success").code(200).build(); // 分片上傳成功,返回成功消息或進(jìn)行其他處理 } } private String mergeChunks(String targetPath, String identifier, int totalChunks,String fileName) throws IOException { String suffix = fileName.substring(fileName.lastIndexOf(".")); String newName = UUID.randomUUID().toString().replace('-','_') + suffix; File mergedFile = new File(uploadPath + newName); // 最終存儲路徑并生成新的文件名 try (FileOutputStream fos = new FileOutputStream(mergedFile, true)) { // 合并所有分片 for (int i = 1; i <= totalChunks; i++) { File chunk = new File(targetPath + "/" + fileName + "." + i); Files.copy(chunk.toPath(), fos); chunk.delete(); // 刪除臨時分片 } new File(targetPath).delete(); //刪除臨時存儲切片的目錄 System.out.println("合并成功"); } catch (IOException e) { System.out.println("合并失敗"); } return newName; } //刪除文件 @GetMapping("delFile") public R delFile(String fileName){ File file = new File(uploadPath + fileName); file.delete(); return R.builder().msg("del success").code(200).build(); } } @Data @Builder public class R { private String msg; private int code; private String data; }
實際效果:
3, 前端代碼 多個文件上傳顯示
<form class="layui-form" action="" lay-filter="addForm"> <div class="layui-form-item"> <label class="layui-form-label">文件上傳</label> <div class="layui-input-block"> <div class="layui-btn" id="uploadBigFile"> <i class="layui-icon layui-icon-upload"></i> 選擇文件 </div> <table class="layui-table" id="file-list"> <thead><tr><th>文件名</th><th>文件大小</th><th>上傳狀態(tài)</th><th>操作</th></tr></thead> <tbody> </tbody> </table> <div class="layui-progress layui-hide" id="demo0" style="margin-top: 20px" lay-showPercent="true" lay-filter="file-upload-progress"> <div class="layui-progress-bar" lay-percent="0%"></div> </div> </div> </div> </form>
<script src="/vendor/layuiAdmin/res/layui/layui.js"></script> <script src="/vendor/resumable.js"></script> <script> layui.config({ base: '/vendor/layuiAdmin/res/' // 靜態(tài)資源所在路徑 }).use(function(){ let $ = layui.$ ,layer = layui.layer ,element = layui.element; //基于Resumable.js const r = new Resumable({ target: '/adm/uploadFile', // 后端分片上傳接口 chunkSize: 2 * 1024 * 1024, // 分片大?。J(rèn) 1MB) simultaneousUploads: 3, // 并發(fā)請求數(shù) testChunks: false, // 不進(jìn)行測試分塊完整性檢查,以提高性能 generateUniqueIdentifier: function(file) { // 提供自定義的文件唯一標(biāo)識符生成方法 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } }); r.assignBrowse(document.getElementById('uploadBigFile')); // 文件添加到隊列時觸發(fā) r.on('fileAdded', function(file){ console.log('File added', file); let strTr = `<tr> <td>${file.fileName}</td> <td>${getfilesize(file.file.size)}</td> <td> <div class="layui-progress" lay-showPercent="true" lay-filter="${file.file.uniqueIdentifier}"> <div class="layui-progress-bar" lay-percent="0%"></div> </div> </td> <td> <input type="hidden" name="files" data-original="${file.fileName}" data-size="${getfilesize(file.file.size)}" id="${file.file.uniqueIdentifier}"> <button type="button" class="layui-btn layui-bg-red layui-hide ${file.file.uniqueIdentifier} layui-btn-xs del_file">刪除</button> </td> </tr>` $("#file-list > tbody").append(strTr) element.render('progress', file.file.uniqueIdentifier); r.upload() }); // 文件上傳開始時觸發(fā) r.on('fileProgress', function(file){ // 渲染進(jìn)度條組件 element.progress(file.file.uniqueIdentifier, Math.floor(file.progress() * 100) + '%'); element.render('progress', file.file.uniqueIdentifier); //console.log('File progress', file.progress()); }); // 文件上傳成功時觸發(fā) r.on('fileSuccess', function(file, message){ // console.log('File uploaded successfully', file, message); let obj = JSON.parse(message) console.log("新的文件名:" + obj.data) $("."+file.file.uniqueIdentifier).removeClass('layui-hide').data('filename',obj.data) $("#"+file.file.uniqueIdentifier).val(obj.data) }); // 文件上傳失敗時觸發(fā) r.on('fileError', function(file, message){ console.log('File upload error', file, message); }); $(document).on("click",".del_file",function(){ console.log("ok") let _this = $(this) let fileName = $(this).data('filename') $.get("/adm/delFile",{fileName},function(res){ _this.parents('tr').remove(); }) return false; }) function getfilesize(size) { if (!size) return ""; var num = 1024.00; //byte if (size < num) return size + "B"; if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + "K"; //kb if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + "M"; //M if (size < Math.pow(num, 4)) return (size / Math.pow(num, 3)).toFixed(2) + "G"; //G return (size / Math.pow(num, 4)).toFixed(2) + "T"; //T } }); </script>