前段時間做了個網盤,所以添加了一個大文件上傳接口,這裏記錄一下
直接給出代碼:
前端頁面:
<!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;
}
}