文章目录
- Java实现文件分片上传
- 为什么要使用分片上传
- 什么是分片上传?
- 前后端代码
Java实现文件分片上传
为什么要使用分片上传
在需要上传文件时,不可避免地会遇到上传文件内容过大,上传时间太长地问题,采用文件分片上传就可以解决这个问题。
什么是分片上传?
简单的说就是本来是需要一次搬一个很大的东西,比如是一大桶水,一次搬起来比较费事费力。我们可以把这一大桶水分装在几个或几十个或者更多的小瓶里,这样搬运起来就比较省力,也比较方便,等到目的地后,我们在将这些小瓶子里的水都倒回大桶里,这样就完成了一大桶水的搬运工作。这个将一大桶水分成许多小瓶子的过程就是分片的过程,最后将水倒回大桶的过程就是合并的过程。分片与合并也是文件分片上传的重要过程。
前后端代码
这个分片的过程是在前端实现的,上传完成后的合并工作是后端完成的。
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS分片上传-极速上传</title>
</head>
<body>
<input type="file" name="slice" id="slice" >
<br/>
</body>
<script src="http://libs.baidu.com/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript">
$("#slice").change(function(event) {
var file = $("#slice")[0].files[0];
PostFile(file,0);
});
//执行分片上传
function PostFile(file,i, uuid){
var name = file.name, //文件名
size = file.size, //总大小
shardSize = 10 * 1024 * 1024, //以10MB为一个分片,每个分片的大小
shardCount = Math.ceil(size / shardSize); //总片数
if(i >= shardCount){
return;
}
//判断uuid是否存在
if (uuid === null || uuid === undefined) {
uuid = guid();
}
//console.log(size,i+1,shardSize); //文件总大小,第一次,分片大小//
var start = i * shardSize;
var end = start + shardSize;
var packet = file.slice(start, end); //将文件进行切片
/* 构建form表单进行提交 */
var form = new FormData();
form.append("uuid", uuid);// 前端生成uuid作为标识符传个后台每个文件都是一个uuid防止文件串了
form.append("data", packet); //slice方法用于切出文件的一部分
form.append("name", name);
form.append("totalSize", size);
form.append("total", shardCount); //总片数
form.append("index", i + 1); //当前是第几片
$.ajax({
url: "http://127.0.0.1:8080/index/doPost",
type: "POST",
data: form,
//timeout:"10000", //超时10秒
async: true, //异步
dataType:"json",
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function (msg) {
console.log(msg);
/* 表示上一块文件上传成功,继续下一次 */
if (msg.status === 201) {
form = '';
i++;
PostFile(file, i, uuid);
} else if (msg.status === 502) {
form = '';
/* 失败后,每2秒继续传一次分片文件 */
setInterval(function () { PostFile(file, i, uuid) }, 2000);
} else if (msg.status === 200) {
merge(uuid, name)
console.log("上传成功");
} else if (msg.status === 500) {
console.log('第'+msg.i+'次,上传文件有误!');
} else {
console.log('未知错误');
}
}
})
}
function merge(uuid, fileName) {
$.ajax({
url: "http://127.0.0.1:8080/index/merge",
type: "GET",
data: {uuid: uuid, newFileName: fileName},
//timeout:"10000", //超时10秒
async: true, //异步
dataType:"json",
success: function (msg) {
console.log(msg);
}
})
}
function guid() {
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
</script>
</html>
后端代码:
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/index")
public class test_controller {
private static final String fileUploadTempDir = "D:/portalupload/fileuploaddir";
private static final String fileUploadDir = "D:/portalupload/file";
@RequestMapping("/doPost")
@ResponseBody
public Map fragmentation(HttpServletRequest req, HttpServletResponse resp) {
resp.addHeader("Access-Control-Allow-Origin", "*");
Map<String, Object> map = new HashMap<>();
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) req;
// 获得文件分片数据
MultipartFile file = multipartRequest.getFile("data");
// 分片第几片
int index = Integer.parseInt(multipartRequest.getParameter("index"));
// 总片数
int total = Integer.parseInt(multipartRequest.getParameter("total"));
// 获取文件名
String fileName = multipartRequest.getParameter("name");
String name = fileName.substring(0, fileName.lastIndexOf("."));
String fileEnd = fileName.substring(fileName.lastIndexOf("."));
// 前端uuid,作为标识
String uuid = multipartRequest.getParameter("uuid");
File uploadFile = new File(fileUploadTempDir + "/" + uuid, uuid + name + index + ".tem");
if (!uploadFile.getParentFile().exists()) {
uploadFile.getParentFile().mkdirs();
}
if (index < total) {
try {
file.transferTo(uploadFile);
// 上传的文件分片名称
map.put("status", 201);
return map;
} catch (IOException e) {
e.printStackTrace();
map.put("status", 502);
return map;
}
} else {
try {
file.transferTo(uploadFile);
// 上传的文件分片名称
map.put("status", 200);
return map;
} catch (IOException e) {
e.printStackTrace();
map.put("status", 502);
return map;
}
}
}
@RequestMapping(value = "/merge", method = RequestMethod.GET)
@ResponseBody
public Map merge(String uuid, String newFileName) {
System.out.println(newFileName);
Map retMap = new HashMap();
try {
File dirFile = new File(fileUploadTempDir + "/" + uuid);
if (!dirFile.exists()) {
throw new RuntimeException("文件不存在!");
}
//分片上传的文件已经位于同一个文件夹下,方便寻找和遍历(当文件数大于十的时候记得排序用冒泡排序确保顺序是正确的)
String[] fileNames = dirFile.list();
String name = newFileName.substring(0, newFileName.lastIndexOf("."));
Arrays.sort(fileNames, (o1,o2)->{
int i1 = Integer.parseInt(o1.substring(o1.indexOf(name)+name.length()).split("\\.tem")[0]);
int i2 = Integer.parseInt(o2.substring(o2.indexOf(name)+name.length()).split("\\.tem")[0]);
return i1 - i2;
});
// 创建空的合并文件
File targetFile = new File(fileUploadDir, newFileName);
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
RandomAccessFile writeFile = new RandomAccessFile(targetFile, "rw");
long position = 0;
for (String fileName : fileNames) {
System.out.println(fileName);
File sourceFile = new File(fileUploadTempDir + "/" + uuid, fileName);
RandomAccessFile readFile = new RandomAccessFile(sourceFile, "rw");
int chunksize = 1024 * 3;
byte[] buf = new byte[chunksize];
writeFile.seek(position);
int byteCount;
while ((byteCount = readFile.read(buf)) != -1) {
if (byteCount != chunksize) {
byte[] tempBytes = new byte[byteCount];
System.arraycopy(buf, 0, tempBytes, 0, byteCount);
buf = tempBytes;
}
writeFile.write(buf);
position = position + byteCount;
}
readFile.close();
FileUtils.deleteQuietly(sourceFile);//删除缓存的临时文件
}
writeFile.close();
retMap.put("code", "200");
}catch (IOException e){
e.printStackTrace();
retMap.put("code", "500");
}
return retMap;
}
}
测试:
可以看到文件进行了分片上传,我是上传了一个系统镜像文件,4GB多,上传的也是非常的快。