前段时间做了个网盘,所以添加了一个大文件上传接口,这里记录一下
直接给出代码:
前端页面:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<link href="/cloud/js/webuploader-0.1.5/webuploader.css" rel="stylesheet" type="text/css" />
<script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="/cloud/js/webuploader-0.1.5/webuploader.min.js"></script>
<script src="/cloud/js/layer/layer.js"></script>
<style type="text/css">
.wu-example {
width: 847px;
position: relative;
padding: 45px 15px 15px;
margin: 0 auto;
background-color: #fafafa;
box-shadow: inset 0 3px 6px rgba(0, 0, 0, .05);
border-color: #e5e5e5 #eee #eee;
border-style: solid;
border-width: 1px 0;
}
#picker {
display: inline-block;
line-height: 1.428571429;
vertical-align: middle;
margin: 0 12px 0 0;
}
</style>
</head>
<body>
<div class="alert alert-success" role="alert" style="text-align: center;font-size: 18px;">大文件上传接口</div>
<div id="uploader" class="wu-example">
<!--用来存放文件信息-->
<div id="thelist" class="uploader-list"></div>
<div class="btns">
<div id="picker">选择文件</div>
<button id="ctlBtn" class="btn btn-default">开始上传</button>
</div>
<p>
<span>上传所用时间:</span>
<span id="useTime">0</span>s
</p>
</div>
</body>
<script type="text/javascript">
function webpageClose(){
window.parent.location.reload()
window.parent.layer.closeAll();
}
var path;
var localUrl = window.location.href;
var localUrlSplit = localUrl.split("?");
if(localUrlSplit.length==1){
path = "/";
}else{
path = decodeURIComponent(localUrlSplit[1].split("=")[1]);
}
console.log(path)
var fileMd5;
var $list = $("#thelist");
var $btn = $("#ctlBtn");
var state = 'pending'; // 上传文件初始化
var timer;
var fileArray = [];
var GUID;
var chunkSize = 32 * 1024 * 1024; // 每片32M
var thisFile;
//监听分块上传过程中的三个时间点
WebUploader.Uploader.register({
"before-send-file" : "beforeSendFile",
"before-send" : "beforeSend",
"after-send-file" : "afterSendFile",
}, {
//时间点1:所有分块进行上传之前调用此函数
beforeSendFile : function(file) {
thisFile = file;
console.log(file);
var deferred = WebUploader.Deferred();
var owner = this.owner;
//1、计算文件的唯一标记,用于断点续传
(new WebUploader.Uploader()).md5File(file, 0, chunkSize)
.progress(function(percentage) {
$('#' + file.id).find('p.state').text("正在读取文件信息...");
}).then(function(val) {
fileMd5 = val;
$('#' + file.id).find("p.state").text("成功获取文件信息...");
//2、判断文件是否已存在
$.ajax({
type : "GET",
url : "/cloud/bigFile/findResourceFileExistsByMd5",
data : {
//文件唯一标记
md5value : fileMd5,
fileName : file.name,
caozuoPath : path
},
dataType : "json",
async:false,
success : function(response) {
if (response.statusCode==200) {
layer.msg("上传成功,即将关闭窗口!",{icon: 1});
$('#' + file.id).find('p.state').text('已上传');
//文件已上传,跳过
// console.log("文件已上传");
deferred.reject();
owner.skipFile(file);
setTimeout( webpageClose,3000)//4s钟后关闭
} else {
//获取文件信息后进入下一步
deferred.resolve();
}
}
});
});
return deferred.promise();
},
//时间点2:如果有分块上传,则每个分块上传之前调用此函数
beforeSend : function(block) {
var deferred = WebUploader.Deferred();
$.ajax({
type : "GET",
url : "/cloud/bigFile/findResourceChunkExists",
data : {
//文件唯一标记
guid : GUID,
//当前分块下标
chunk : block.chunk
},
dataType : "json",
async:false,
success : function(response) {
if (response.statusCode==200) {
//分块存在,跳过
console.log("分块存在,跳过");
deferred.reject();
} else {
//分块不存在或不完整,重新发送该分块内容
console.log("分块不存在或不完整,重新发送该分块内容");
deferred.resolve();
}
}
});
this.owner.options.formData.md5value =fileMd5;
this.owner.options.formData.chunk =block.chunk;
deferred.resolve();
return deferred.promise();
},
//时间点3:所有分块上传成功后调用此函数
afterSendFile : function() {
//如果分块上传成功,则获取上传结果
$.post('/cloud/bigFile/saveFileInfo', { guid: GUID, fileName: thisFile.name, md5value : fileMd5, caozuoPath: path, fileSize:thisFile.size}, function (data) {
if(data.statusCode == 200){
setTimeout( webpageClose,3000)//4s钟后关闭
layer.msg("上传成功,即将关闭窗口!",{icon: 1});
}else{
setTimeout( webpageClose,3000)//4s钟后关闭
layer.msg("上传失败,即将关闭窗口!",{icon: 2});
}
});
}
});
var uploader = WebUploader.create({
// swf文件路径
swf: '/cloud/js/webuploader-0.1.5/Uploader.swf',
// 文件接收服务端。
server: '/cloud/bigFile/upload',
// 选择文件的按钮。可选。
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
pick: '#picker',
chunked : true, // 分片处理
chunkSize : chunkSize,
chunkRetry : false,// 如果失败,则不重试
threads : 1,// 上传并发数。允许同时最大上传进程数。
// 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
resize: false
});
//点击上传之前调用的方法
uploader.on("uploadStart", function (file) {
GUID = WebUploader.Base.guid();
var paramOb = {"guid": GUID, "filedId": file.source.ruid}
uploader.options.formData.guid = GUID;
fileArray.push(paramOb);
});
//当有文件被添加进队列的时候
uploader.on('beforeFileQueued', function(file) {
//清空队列
uploader.reset();
});
//当有文件被添加进队列的时候
uploader.on('fileQueued', function (file) {
$list.append('<div id="' + file.id + '" class="item">' +
'<h4 class="info">' + file.name + '</h4>' +
'<p class="state">等待上传...</p>' +
'</div>');
});
//文件上传过程中创建进度条实时显示。
uploader.on('uploadProgress', function (file, percentage) {
var $li = $('#' + file.id),
$percent = $li.find('.progress .progress-bar');
// 避免重复创建
if (!$percent.length) {
$percent = $('<div class="progress progress-striped active">' +
'<div class="progress-bar" role="progressbar" style="width: 0%">' +
'</div>' +
'</div>').appendTo($li).find('.progress-bar');
}
$li.find('p.state').text('上传中');
$percent.css('width', percentage * 100 + '%');
});
$("#ctlBtn").click(function () {
uploader.upload();
});
//文件成功、失败处理
uploader.on('uploadSuccess', function (file) {
var successFileId = file.source.ruid;
var successDuid;
if (fileArray.length > 0) {
for (var i = 0; i < fileArray.length; i++) {
if (fileArray[i].filedId === successFileId) {
successDuid=fileArray[i].guid;
fileArray.splice(i, 1);
}
}
}
clearInterval(timer);
$('#' + file.id).find('p.state').text('已上传');
});
uploader.on('uploadError', function (file) {
$('#' + file.id).find('p.state').text('上传出错');
});
uploader.on('uploadComplete', function (file) {
$('#' + file.id).find('.progress').fadeOut();
});
$btn.on('click', function () {
if (state === 'uploading') {
uploader.stop();
} else {
uploader.upload();
timer = setInterval(function () {
var useTime = parseInt($("#useTime").html());
useTime = useTime + 1;
$("#useTime").html(useTime);
}, 1000);
}
});
</script>
</html>
后端代码:
先在application.yml中添加配置:
spring:
http:
encoding:
force: true
charset: utf-8
multipart:
max-file-size: 100MB
max-request-size: 100MB
package com.hm.pan.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.io.FileUtils;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.hm.pan.annotation.CheckLogin;
import com.hm.pan.model.FileInfo;
import com.hm.pan.model.ResultObj;
import com.hm.pan.model.User;
import com.hm.pan.model.UserFile;
import com.hm.pan.service.FileUploadService;
import com.hm.pan.service.UserFileService;
@RestController
@RequestMapping("bigFile")
@CheckLogin
public class BigFileUploadController {
@Resource
private FileUploadService fileUploadService;
@Resource
private UserFileService userFileService;
@Value("${upload-path}")
private String uploadPath;
// 通过MD5查找文件信息,如果有的话,就不用上传文件,只要向用户文件里插入文件信息就行
@GetMapping("findResourceFileExistsByMd5")
public Object findMd5(String md5value, String fileName, String caozuoPath, HttpSession session)
throws Exception {
Timestamp uploadTime = new Timestamp(System.currentTimeMillis());
UserFile oneFileByPath2 = userFileService.getOneFileByPath(caozuoPath);// 通过前端传过来的操作路径找到该用户文件对象
Long fatherId2 = oneFileByPath2.getUserfileId();
// log.info("================"+md5);
User user = (User) session.getAttribute("user"); // 通过session拿到前端用户信息
// log.info("-----------------"+user);
Long userId = user.getUserId(); // 获取前端传过来的用户id
// log.info("+++++++++++++++++"+userId);
String newName = getName(fileName);
Object filemd5 = fileUploadService.findMd5(md5value);
// 如果数据里有MD5,就不用上传,但是用户文件需要插入。
if (filemd5 != null) {
Object fileInfo = fileUploadService.findByNameFatherId(fileName, fatherId2);
if (fileInfo != null) {
fileUploadService.insertUserFile(uploadTime, newName, md5value, fatherId2, userId);
} else {
fileUploadService.insertUserFile(uploadTime, fileName, md5value, fatherId2, userId);
}
return new ResultObj(200, fileInfo);
} else {
return new ResultObj(400, filemd5);
}
}
//这里通过文件编号guid和分片数chunk来检查分片文件,其实严格一点还需要检查分片文件的偏移位置和文件大小,以防止分片文件上传失败的情况,这里表示一下就行,需要自己改改
@GetMapping("findResourceChunkExists")
public ResultObj findResourceChunkExists(String guid,Integer chunk){
// 临时目录用来存放所有分片文件
String tempFileDir = uploadPath+ "/bigFileTemp/" + guid;
File parentFileDir = new File(tempFileDir);
File tempPartFile = new File(parentFileDir, guid + "_" + chunk + ".part");
if(tempPartFile.exists()) {
return new ResultObj(200, "存在");
}else {
return new ResultObj(404, "不存在");
}
}
// 如果前面的MD5没有找到文件信息的话,就会返回一个400到前端,前端再根据判断,把文件给上传。
@PostMapping("saveFileInfo")
public ResultObj aa(String guid, String md5value, Long fileSize, String fileName,
String caozuoPath, HttpSession session, HttpServletRequest req) throws Exception {
Timestamp uploadTime = new Timestamp(System.currentTimeMillis());
System.out.println("saveFileInfo:");
System.out.println("------------guid:"+guid);
System.out.println("------------md5value:"+md5value);
System.out.println("------------fileSize:"+fileSize);
System.out.println("------------fileName:"+fileName);
System.out.println("------------caozuoPath:"+caozuoPath);
String newName = getName(fileName);
// 将文件存到指定项目文件下,没有的话就创建
// File realpath = ResourceUtils.getFile("");
// String realpath = req.getServletContext().getRealPath("/upload");
// String filePath = realpath+fileName;
File dest = mergeFile(guid, fileName);
// log.info("<<<<<<<<<<<<<<<< "+file2.getAbsolutePath());
FileInfo fileInfo = new FileInfo();
fileInfo.setFilePath(dest.getPath()); // 获取文件的相对路径
fileInfo.setFileMd5(md5value);
fileInfo.setFileSize(fileSize);
User user = (User) session.getAttribute("user"); // 通过session拿到前端用户信息
// log.info("-----------------"+user);
Long userId = user.getUserId(); // 获取前端传过来的用户id
// log.info("+++++++++++++++++"+userId);
Object fileinfo = fileUploadService.insertFileInfo(fileInfo);
UserFile oneFileByPath = userFileService.getOneFileByPath(caozuoPath); // 通过操作路径找到fatherId
if (oneFileByPath != null && fileinfo != null) {
Long fatherId = oneFileByPath.getUserfileId();
Object findByName = fileUploadService.findByNameFatherId(fileName, fatherId);
if (findByName != null) {
fileUploadService.insertUserFile(uploadTime, newName, md5value, fatherId, userId);
} else {
fileUploadService.insertUserFile(uploadTime, fileName, md5value, fatherId, userId);
}
return new ResultObj(200, fileinfo);
}
return new ResultObj(400, fileinfo);
}
// 获取不同的文件名
public String getName(String fileName) {
// 解决文件名重名,以时间戳+随机数
// 获得当前时间
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
// 转换为字符串
String formDate = format.format(new Date());
// 随机生成文件编号
int random = new Random().nextInt(1000);
String[] split = fileName.split("\\.");
// 1.2.3.4.cc
// mm
StringBuffer sb = new StringBuffer(split[0]);
for (int i = 1; i < split.length - 1; i++) {
sb.append('.').append(split[i]);
}
sb.append('_').append(formDate).append(random);
if (fileName.indexOf(".") != -1) {
sb.append('.').append(split[split.length - 1]);
}
String name = sb.toString();
return name;
}
/**
* 上传文件
* @param request
* @param response
* @param guid
* @param chunk
* @param file
* @param chunks
*/
@RequestMapping("upload")
public void bigFile(HttpServletRequest request, HttpServletResponse response,String guid,Integer chunk, MultipartFile file,Integer chunks){
try {
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
// 临时目录用来存放所有分片文件
String tempFileDir = uploadPath+ "/bigFileTemp/" + guid;
File parentFileDir = new File(tempFileDir);
if (!parentFileDir.exists()) {
parentFileDir.mkdirs();
}
// 分片处理时,前台会多次调用上传接口,每次都会上传文件的一部分到后台
File tempPartFile = new File(parentFileDir, guid + "_" + chunk + ".part");
FileUtils.copyInputStreamToFile(file.getInputStream(), tempPartFile);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 合并文件
* @param guid
* @param fileName
* @throws Exception
*/
public File mergeFile(String guid,String fileName){
// 得到 destTempFile 就是最终的文件
try {
File parentFileDir = new File(uploadPath+ "/bigFileTemp/" + guid);
if(parentFileDir.isDirectory()){
// File destTempFile = new File(uploadPath+ "/bigFileTemp/merge/", fileName);
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
// 转换为字符串
String formDate = format.format(new Date());
File destTempFile = new File(uploadPath+"/"+formDate, fileName);
System.out.println(destTempFile.getPath());
if(!destTempFile.exists()){
//先得到文件的上级目录,并创建上级目录,在创建文件,
destTempFile.getParentFile().mkdirs();
try {
//创建文件
destTempFile.createNewFile(); //上级目录没有创建,这里会报错
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println(parentFileDir.listFiles().length);
for (int i = 0; i < parentFileDir.listFiles().length; i++) {
File partFile = new File(parentFileDir, guid + "_" + i + ".part");
FileOutputStream destTempfos = new FileOutputStream(destTempFile, true);
//遍历"所有分片文件"到"最终文件"中
FileUtils.copyFile(partFile, destTempfos);
destTempfos.close();
}
// 删除临时目录中的分片文件
FileUtils.deleteDirectory(parentFileDir);
return destTempFile;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
}