后端代码
/**
* 分片上传
*
* @param file 上传的文件
* @param start 文件开始上传的位置
* @param fileName 文件名称
* @return 上传结果
*/
@PostMapping("/fragmentUpload")
@ResponseBody
public AjaxResult fragmentUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start, @RequestParam("fileName") String fileName) {
try {
// 检查上传目录是否存在,如果不存在则创建
File directory = new File(uploadPath);
if (!directory.exists()) {
directory.mkdirs();
}
// 设置上传文件的目标路径
File targetFile = new File(uploadPath +File.separator+ fileName);
// 创建 RandomAccessFile 对象以便进行文件的随机读写操作
RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");
// 获取 RandomAccessFile 对应的 FileChannel
FileChannel channel = randomAccessFile.getChannel();
// 设置文件通道的位置,即从哪里开始写入文件内容
channel.position(start);
// 从 MultipartFile 对象的资源通道中读取文件内容,并写入到指定位置
channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());
// 关闭文件通道和 RandomAccessFile 对象
channel.close();
randomAccessFile.close();
// 返回上传成功的响应
return AjaxResult.success("上传成功");
} catch (Exception e) {
// 捕获异常并返回上传失败的响应
return AjaxResult.error("上传失败");
}
}
/**
* 检测文件是否存在
* 如果文件存在,则返回已经存在的文件大小。
* 如果文件不存在,则返回 0,表示前端从头开始上传该文件。
* @param filename
* @return
*/
@GetMapping("/checkFile")
@ResponseBody
public AjaxResult checkFile(@RequestParam("filename") String filename) {
File file = new File(uploadPath+File.separator + filename);
if (file.exists()) {
return AjaxResult.success(file.length());
} else {
return AjaxResult.success(0L);
}
}
前端
var prefix = ctx + "/kuroshiro/file-upload";
// 每次上传大小
const chunkSize = 1 * 1024 * 1024;
/**
* 开始上传
*/
function startUpload(type) {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
alert("请选择文件");
return;
}
if(type == 1){
checkFile(filename).then(start => {
uploadFile(file, start,Math.min(start + chunkSize, file.size));
})
}
}
/**
* 检查是否上传过
* @param filename
* @returns {Promise<unknown>}
*/
function checkFile(filename) {
return $fetch(prefix+`/checkFile?filename=${filename}`);
}
/**
* 开始分片上传
* @param file 文件
* @param start 开始位置
* @param end 结束位置
*/
function uploadFile(file, start,end) {
if(start < end){
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk);
formData.append('start', start);
formData.append('fileName', file.name);
$fetch(prefix+'/fragmentUpload', {
method: 'POST',
body: formData
}).then(response => {
console.log(`分片 ${start} - ${end} 上传成功`);
// 递归调用
uploadFile(file,end,Math.min(end + chunkSize, file.size))
})
}
}
function $fetch(url,requestInit){
return new Promise((resolve, reject) => {
fetch(url,requestInit).then(response => {
if (!response.ok) {
throw new Error('请求失败');
}
return response.json();
}).then(data => {
if (data.code === 0) {
resolve(data.data);
} else {
console.error(data.msg);
reject(data.msg)
}
}).catch(error => {
console.error(error);
reject(error)
});
});
}
以上虽然实现的分片上传,但是它是某种意义上来说还是与整体上传差不多,它是一段一段的上传,某段上传失败后,后续的就不会再继续上传;不过比起整体上传来说,它会保存之前上传的内容,下一个上传时,从之前上传的位置接着上传。不用整体上传。下面进行优化。