基于resumable.js的切片上傳大文件,且顯示進(jìn)度條(基于layui的進(jìn)度條), 并結(jié)合后臺(tái)使用springboot接收切片,合并切片的完整實(shí)例
1, 下載resumable.js 或使用 cdn引入js
可以到https://www.bootcdn.cn/resumable.js/ 下載
2, 前端代碼 單個(gè)文件上傳顯示
<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ā)請(qǐng)求數(shù)
testChunks: false, // 不進(jìn)行測(cè)試分塊完整性檢查,以提高性能
generateUniqueIdentifier: function(file) {
// 提供自定義的文件唯一標(biāo)識(shí)符生成方法
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'));
// 文件添加到隊(duì)列時(shí)觸發(fā)
r.on('fileAdded', function(file){
console.log('File added', file);
$("#demo0").removeClass('layui-hide')
r.upload()
});
// 文件上傳開(kāi)始時(shí)觸發(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());
});
// 文件上傳成功時(shí)觸發(fā)
r.on('fileSuccess', function(file, message){
// console.log('File uploaded successfully', file, message);
let obj = JSON.parse(message)
console.log("新的文件名:" + obj.data)
});
// 文件上傳失敗時(shí)觸發(fā)
r.on('fileError', function(file, message){
console.log('File upload error', file, message);
});
});
</script>3, 后臺(tái)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都會(huì)傳遞以下兩個(gè)參數(shù),參數(shù)接收會(huì)拼接
String fileName = request.getParameter("resumableFilename");
String identifier = request.getParameter("resumableIdentifier");
// 目標(biāo)文件路徑,identifier是唯一的標(biāo)識(shí)符,用于存儲(chǔ)文件分片
String targetPath = uploadPath + identifier;
File uploads = new File(targetPath);
//創(chuàng)建臨時(shí)文件夾
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); // 合并分片的方法實(shí)現(xiàn)見(jià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);
// 最終存儲(chǔ)路徑并生成新的文件名
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(); // 刪除臨時(shí)分片
}
new File(targetPath).delete(); //刪除臨時(shí)存儲(chǔ)切片的目錄
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;
}實(shí)際效果:


3, 前端代碼 多個(gè)文件上傳顯示
<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ā)請(qǐng)求數(shù)
testChunks: false, // 不進(jìn)行測(cè)試分塊完整性檢查,以提高性能
generateUniqueIdentifier: function(file) {
// 提供自定義的文件唯一標(biāo)識(shí)符生成方法
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'));
// 文件添加到隊(duì)列時(shí)觸發(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()
});
// 文件上傳開(kāi)始時(shí)觸發(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());
});
// 文件上傳成功時(shí)觸發(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)
});
// 文件上傳失敗時(shí)觸發(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>
