1. 意义
普通文件流上传,这是一篇介绍普通的ajax + SpringMVC文件上传的文章,这里直接把文件转化为字节流上传到服务器,可能在大多数情况下没有什么问题。但是在上传超大文件时一旦中断只能重新上传,这是很让人奔溃的。
分片上传的意义在于把一个文件分成多份,一片一片的上传。当某一片上传失败时可以记录下来,进行重传或者其他处理,分片的附带好处还能很方便的实现进度条。
2.前端做法
前后端需要统一下每个切片的大小,我设置的是1M(也可以动态设定分片尺寸,比如放在请求参数中或者请求头中)
把文件用slice拆分后上传(像上传普通的文件一样就行)
直接上代码:
<!DOCTYPE html>
<html xmlns="http://c7.gg/cd5XT" xmlns:th="http://c7.gg/cd9re"
xmlns:sec="http://c7.gg/cd9rv">
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>
</head>
<body>
<h1 th:inlines="text">文件上传</h1>
<h2>分片上传</h2>
<p>选择文件: <input type="file" id="file" name="file"/></p>
<p><input type="submit" value="提交" onclick="commit()"/></p>
</body>
</html>
<script>
// 每个文件切片大小定为1M(1024*1024字节)(需要跟服务器协商好).
var BYTES_PER_SLICE = 1<<20;
// 已发送的数量
var hasSendNum = 0;
// 总切片数
var totalSlices;
// 提交方法
function commit() {
// 拿出选中的第一个文件
var file = document.getElementById("file").files[0];
// 文件的总字节数
var totalSize = file.size;
// 当前片数
var index = 0;
// 分片的开始、结束(不含)
var start,end;
// 文件名
var fileName = file.name;
// 初始化已发送数量为0
hasSendNum = 0;
// 计算文件切片总数(向上取整)
totalSlices = Math.ceil(file.size / BYTES_PER_SLICE);
// 不断循环将切片上传
while(index < totalSlices) {
start = index*BYTES_PER_SLICE;
end = start + BYTES_PER_SLICE;
var slice =file.slice(start,end);//切割文件
uploadFile(slice, index++,fileName);
}
}
//上传文件
function uploadFile(slice, index,fileName) {
var retry = 1;
var formDate = new FormData();
formDate.append("slice", slice);
formDate.append("fileName",fileName);
formDate.append("index",index);
var xhr = new XMLHttpRequest();
xhr.open('POST', 'sliceUpload', true);//false指同步上传,因为我的服务器内存较小,选择同步,如果追求速度,可以选择 //ture,异步上传
xhr.onreadystatechange = ()=>uploadCallBack(xhr,slice,index,fileName);
xhr.send(formDate);
}
/**
* @desc 上传回调
* @param xhr
*/
function uploadCallBack(xhr,slice,index,fileName) {
if(xhr.readyState==4) {
if(xhr.status==200) {
if(xhr.responseText==1) {
hasSendNum++;
console.log("第"+index+"片,完成度"+parseInt(hasSendNum/totalSlices*100)+"%");
if(hasSendNum==totalSlices) {
console.log("上传完毕");
}
}
} else {
console.log("上传失败,重试##################################");
// 重试
uploadFile(slice, index,fileName);
}
}
}
</script>
3. 服务端做法
服务端需要用到一个RandomAccessFile,不了解的可以自行百度,总之他的作用就是可以随机访问一个文件的任何位置,利用这个功能把前端发送过来的文件切片拼接成一个完整的文件。
代码如下:
1
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;
/**
* @Author:Lvxingqing
* @Description:
* @Date:Create in 17:40 2018/6/27
* @Modified By:
*/
@RestController
public class SliceUpload {
private final int BYTES_PER_SLICE = 1<<20;
@RequestMapping(value="sliceUpload",method= RequestMethod.POST)
public int upload(@RequestParam("slice")MultipartFile slice,String fileName,int index) {
int result = 0;
if(slice.isEmpty()){
return 0;
}
int size = (int) slice.getSize();
System.out.println(fileName + "-->" + size);
String path = "d:/test" ;
File dest = new File(path + "/" + fileName);
RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile(dest,"rw");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if(!dest.getParentFile().exists()){ //判断文件父目录是否存在
dest.getParentFile().mkdir();
}
try {
byte[] bytes = slice.getBytes(); //保存文件
randomAccessFile.seek(index*BYTES_PER_SLICE);
randomAccessFile.write(bytes);
result = 1;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}