HTML5结合springboot带进度条大文件分段上传
2018-09-14 本文已影响138人
极微
一、背景
web应用文件上传是经常会碰到的,一般我们上传的都是几十MB以内的文件,当我们要传输视频等大文件时,首先springboot的默认配置为10MB,大于这个我们是传不上服务器的,如果我们修改了默认配置,那无疑会加重服务器的负担。其次当文件大于一定程度时,不仅浏览器也会占用大量内存,而且http传输极可能会中断。
这里本人参考了大量资料做了一个http文件分段上传,支持GB级别和分段续传。前端主要用了HTML5的File API切割文件,后端主要用了java文件合并功能。不多说了,上代码。
二、前端代码
<script type="text/javascript">
function uploadFile(){
var file = $("#file")[0].files[0]; //文件对象
if(file==undefined){
alert("请先选中文件");
return;
}
if(file.size>1024*1024*1024*2){
alert("文件不能大于2GB");
return;
}
$("#upload").attr("disabled","disabled");
isUpload(file);
}
function isUpload (file) {
var form=new FormData();
var reader = new FileReader();
//绑定读取失败事件
$(reader).error(function(e){
$("#upload").removeAttr("disabled");
alert("读取失败");
})
var fileWithSize=file;
//根据文件大小计算fileMd5值
if(file.size>1024*1024*50){
var fileStart=file.slice(0,1024*1024);
var fileEnd=file.slice(file.size+1);
var arr=[fileStart,fileEnd];
fileWithSize = new Blob(arr, { type: "text/plain" });
}
//绑定读取成功事件
$(reader).load(function(e){
var fileMd5 = hex_md5(reader.result);
form.append("fileMd5", fileMd5);
form.append("videoSize", file.size);
//校验是否上传过该文件,返回上传进度
$.ajax({
url: "${basePath}/global/isFileExist",
type: "POST",
data: form,
async: true, //异步
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function(data){
if(data.code==1){
$("#process").css("width","100%");
$("#process").html("100%");
$("#upload").removeAttr("disabled");
alert(data.msg)
}else{//视频未上传或者部分上传
//分片上传
$("#process").css("width","0%");
$("#process").html("");
uploadBySplit(file,fileMd5,0);
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
$("#upload").removeAttr("disabled");
alert("服务器出错!");
}
});
})
reader.readAsBinaryString(fileWithSize);
}
//分片上传
function uploadBySplit(file,fileMd5,i){
var splitSize=1024*1024*20;//分片大小20M
var size=file.size;//总大小
splitCount = Math.ceil(size / splitSize); //总片数
if(i==splitCount){
$("#upload").removeAttr("disabled");
return;
}
//计算每一片的起始与结束位置
var start = i * splitSize;
var end = Math.min(size, start + splitSize);
var fileData=file.slice(start,end);
var reader = new FileReader();
$(reader).load(function(e){
var md5 = hex_md5(reader.result);
//构造一个表单,FormData是HTML5新增的
var form = new FormData();
form.append("fileMd5", fileMd5);
form.append("size", size);//总大小
form.append("total", splitCount); //总片数
form.append("index", i); //当前是第几片
form.append("md5", md5);
//判断分片是否上传
$.ajax({
url: "${basePath}/global/isFileSplitExist",
type: "POST",
data: form,
async: true, //异步
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function(data){
if(data.code==1){//已上传
//处理上传进度
var process=Math.round(end/size*100)+"%";
$("#process").css("width",process);
$("#process").html(process);
i++;
uploadBySplit(file,fileMd5,i);
}else{//未上传
form.append("fileData", fileData);
//上传分片
$.ajax({
url: "${basePath}/global/fileUpload",
type: "POST",
data: form,
async: true, //异步
processData: false, //很重要,告诉jquery不要对form进行处理
contentType: false, //很重要,指定为false才能形成正确的Content-Type
success: function(data){
//处理上传进度
var process=Math.round(end/size*100)+"%";
$("#process").css("width",process);
$("#process").html(process);
i++;
uploadBySplit(file,fileMd5,i);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
$("#upload").removeAttr("disabled");
alert("服务器出错!");
}
});
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
$("#upload").removeAttr("disabled");
alert("服务器出错!");
}
});
});
reader.readAsBinaryString(fileData);
}
三、后台代码
package com.daotong.controllers.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.daotong.controllers.base.DaoTongBaseController;
import com.daotong.core.conf.WebMappingTables;
import com.shove190.core.plugins.data.ActionResult;
/**
* 视频上传类
*/
@RestController
public class FileUploadController extends DaoTongBaseController {
@Value("${savePath}")
private String savePath;
/**
* 视频分片上传公共接口,考虑到用户效率,采用MD5算法确认文件唯一性,理论上文件不同而摘要相同的概率几近于0,对于发生hash碰撞的两个文件,
* 其得到的视频将会是与其发生hash碰撞的视频。
*
* @param request
* @return
* @throws IllegalStateException
* @throws IOException
*/
@SuppressWarnings("rawtypes")
@RequestMapping(value = WebMappingTables.MAPPING_WEB_COMMON_FIELUPLOADCONTROLLER)
public ActionResult upload(MultipartFile fileData) throws IllegalStateException, IOException, Exception {
// 搜素目录下是否包含有相同的MD5文件?
int total = Integer.valueOf(request.getParameter("total"));// 总片数
int index = Integer.valueOf(request.getParameter("index"));// 当前是第几片,第一片index必须为0
String fileMd5 = request.getParameter("fileMd5"); // 整个文件的md5
// 文件夹位置
File parent = new File(savePath + File.separator + fileMd5);
if (!parent.exists()) {
parent.mkdirs();
}
// 文件分片位置
File file = new File(parent, fileMd5 + "_" + index);
// 保存分片到本地
if (file.exists()) {
file.delete();
}
// 由前端控制上传进度
fileData.transferTo(file);
// 判断所有分片是否全部上传完成,完成则合并
File dir = new File(savePath + File.separator + fileMd5);
File[] files = dir.listFiles();
if (files.length == total) {// 上传完成
FileOutputStream fileOutputStream = null;
FileInputStream temp = null;// 文件分片
File newFile = new File(dir, fileMd5);
fileOutputStream = new FileOutputStream(newFile, true);
byte[] byt = new byte[10 * 1024 * 1024];
int len;
for (int i = 0; i < total; i++) {
temp = new FileInputStream(new File(dir, fileMd5 + "_" + i));
while ((len = temp.read(byt)) != -1) {
fileOutputStream.write(byt, 0, len);
}
}
fileOutputStream.close();
temp.close();
// 全部完成之后,删除分片
for (int i = 0; i < total; i++) {
File splitFile = new File(dir, fileMd5 + "_" + i);
if (splitFile.exists()) {
splitFile.delete();
}
}
}
return buildActionResult("操作成功");
}
/**
* 获取文件的MD5值
*/
private String getFileMD5(File file) {
if (!file.exists() || !file.isFile()) {
return null;
}
MessageDigest digest = null;
FileInputStream in = null;
byte buffer[] = new byte[1024];
int len;
try {
digest = MessageDigest.getInstance("MD5");
in = new FileInputStream(file);
while ((len = in.read(buffer, 0, 1024)) != -1) {
digest.update(buffer, 0, len);
}
in.close();
} catch (Exception e) {
e.printStackTrace();
return null;
}
BigInteger bigInt = new BigInteger(1, digest.digest());
// 16进制
return bigInt.toString(16);
}
/**
* 检测视频是否已上传
*/
@RequestMapping(value = WebMappingTables.MAPPING_WEB_COMMON_FIELUPLOADCONTROLLER_ISFILEEXIST)
public ActionResult<Object> isFileExist() {
File pdir = new File(savePath);
if (!pdir.exists()) {
pdir.mkdirs();
}
ActionResult<Object> actionResult = new ActionResult<Object>();
String fileMd5 = request.getParameter("fileMd5");
File dir = new File(savePath + File.separator + fileMd5);
if (dir.exists()) {// 目录存在
File file = new File(dir, fileMd5);
if (file.exists()) {// 文件存在
actionResult.setCode(1);
actionResult.setMsg("文件已上传,秒传");
return actionResult;
} else {// 文件不存在,计算分片大小
actionResult.setCode(-1);
actionResult.setMsg("文件已部分上传");
return actionResult;
}
} else {
actionResult.setCode(-1);
actionResult.setMsg("文件未上传");
return actionResult;
}
}
/**
* 检测视频分片是否已上传
*/
@RequestMapping(value = WebMappingTables.MAPPING_WEB_COMMON_FIELUPLOADCONTROLLER_ISFILESPLITEXIST)
public ActionResult<Object> isFileSplitExist() {
ActionResult<Object> actionResult = new ActionResult<>();
String splitMd5 = getReqStringParams("md5");// 分片Md5
String fileMd5 = getReqStringParams("fileMd5");// 文件md5
int index = getReqIntegerParams("index");// 第几片
File file = new File(savePath + File.separator + fileMd5, fileMd5 + "_" + index);
if (file.exists()) {
if (splitMd5.equals(getFileMD5(file))) {
actionResult.setCode(1);
actionResult.setMsg("视频分片已上传");
return actionResult;
} else {// 视频分片损坏,删除重传
file.delete();
actionResult.setCode(-1);
actionResult.setMsg("视频分片已损坏");
return actionResult;
}
} else {
actionResult.setCode(-1);
actionResult.setMsg("视频分片未上传");
return actionResult;
}
}
public static void main(String[] args) {
File file=new File("E:\\saveFileDir\\f18215e2014b70b78e30ce2af989643a\\f18215e2014b70b78e30ce2af989643a");
System.out.println(new FileUploadController().getFileMD5(file));
//f18215e2014b70b78e30ce2af989643a
//f18215e2014b70b78e30ce2af989643a
String path = ClassUtils.getDefaultClassLoader().getResource("").getPath();
System.out.println(path);
}
}