接着上一篇,實現多個大文件的斷點續傳。
前端JSP頁面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html style="height: 99%; width: 99.9%">
<head>
<title>文件上傳</title>
<link rel="stylesheet" href="${ctx}/js/webuploader-0.1.5/webuploader.css" />
<script src="${ctx}/js/jquery-ui-1.11.1/jquery-ui.min.js"></script>
<script src="${ctx}/js/webuploader-0.1.5/webuploader.js"></script>
</head>
<body class="div-form-container">
<table class="upload">
<tbody>
<tr>
<td height="200px">
<div id="uploader-container" style="width: 100%">
<div id="fileList" class="uploader-list">
</div>
<div id="upInfo"></div>
<div id="filePicker">選擇文件</div>
</div>
</td>
</tr>
<tr>
<td>
<input type="button" class="btnSave" id="btnUpload" value="上傳"/>
<input type="button" class="btnSave" id="btnReset" value="重置"/>
<input type="button" class="btnSave" value="取消" onclick="javascript:closeDialog();"/>
</td>
</tr>
</tbody>
</table>
<script type="text/javascript">
$(function() {
var $chunkSize = 10*1024*1024, // 分片尺寸 10M
$maxSingleSize = 1024*1024*1024, // 單文件最大尺寸
$maxSize = 10*1024*1024*1024; // 所有文件最大尺寸
var $list = $('#fileList'), // 頁面展示的文件列表
$fileArray = new Array(), // 要上傳的文件列表
$md5Array = new Array(), // 文件的MD5
$nameArray = new Array(), // 文件名稱
count = 0, // 正在上傳的文件在上傳列表中的位置
uploader; // Web Uploader實例
// 監聽分塊上傳的三個事件
WebUploader.Uploader.register({
"before-send-file" : "beforeSendFile", // 所有分塊上傳之前
"before-send" : "beforeSend", // 每個分塊上傳之前
"after-send-file" : "afterSendFile" // 所有分塊上傳完成後
},{
beforeSendFile : function(file){
var deferred = WebUploader.Deferred();
// 計算文件的MD5
(new WebUploader.Uploader()).md5File(file,0,10*1024*1024)
// 及時顯示進度
.progress(function(percentage) {})
// 計算完成,繼續下一步
.then(function(val) {
$md5Array.push(val);
$nameArray.push(file.name);
deferred.resolve();
});
return deferred.promise();
},
beforeSend : function(block){
var deferred = WebUploader.Deferred();
// 每個分塊上傳之前校驗是否已上傳
var url = "${ctx}/uploadBig/check",
param = {
fileName : $nameArray[count],
fileMd5 : $md5Array[count],
chunk : block.chunk,
chunkSize : block.end - block.start
};
// 同步校驗,防止沒校驗完就上傳了
$.ajaxSetup({async : false});
$.post(url,param,function(data){
// 已上傳則跳過,否則繼續上傳
if(1 == data){
deferred.reject();
}else{
deferred.resolve();
}
});
$.ajaxSetup({async : true});
this.owner.options.formData.fileMd5 = $md5Array[count];
deferred.resolve();
return deferred.promise();
},
afterSendFile : function(){
// 所有分塊上傳完畢,通知後臺合併分塊
var url = "${ctx}/uploadBig/merge",
// 上傳前設置其它參數
param = {
fileMd5 : $md5Array[count],
fileName : $nameArray[count]
};
$.ajaxSetup({async : false});
$.post(url,param,function(data){
count++;
if(count<=$fileArray.length-1){
uploader.upload($fileArray[count].id);
}
});
$.ajaxSetup({async : true});
}
});
// 初始化Web Uploader PS:IE使用的flash上傳,真心慢,大文件還是用Chrome上傳比較靠譜
uploader = WebUploader.create({
auto : false, // 手動上傳
swf : '${ctx}/js/webuploader-0.1.5/Uploader.swf',
server : '${ctx}/uploadBig/upload', // 文件接收服務端
threads : 1, // 只運行1個線程傳輸
duplicate : false, // 是否重複上傳(單次選擇同樣的文件)
prepareNextFile : true, // 允許在文件傳輸時提前把下一個文件準備好
chunked : true, // 是否要分片處理大文件上傳
chunkSize : $chunkSize, // 如果要分片,分多大一片? 10M默認大小爲5M
fileNumLimit : 10, // 文件總數量
fileSingleSizeLimit : $maxSingleSize, // 單個文件大小限制
fileSizeLimit : $maxSize, // 文件總大小限制
pick : {
id : '#filePicker', // 選擇文件的按鈕
multiple : true // 允許同時選擇多個文件
},
compress: false, // 不壓縮文件
accept : {
// TODO:待確認上傳文件的格式和大小
// 常見視頻文件格式:avi,wmv,mpeg,mp4,mov,mkv,flv,f4v,m4v,rmvb,rm,3gp,dat,ts,mts,vob
extensions: "txt,gif,jpg,jpeg,bmp,png,zip,rar,war,pdf,cebx,doc,docx,ppt,pptx,xls,xlsx",
mimeTypes: '.txt,.gif,.jpg,.jpeg,.bmp,.png,.zip,.rar,.war,.pdf,.cebx,.doc,.docx,.ppt,.pptx,.xls,.xlsx',
}
});
// 當有文件添加進來的時候
uploader.on('fileQueued', function(file) {
if((file.size <= $chunkSize) || (file.size > $maxSingleSize)){
return;
}
var $li = $('<div id="' + file.id + '" class="file-item">'
+ '<span class="info">' + file.name + '</span>'
+ '<span class="state">等待上傳</span>'
+ '</div>');
$list.append($li);
$fileArray.push(file);
});
// 對於太小的文件進行提示
uploader.on('filesQueued',function (files){
var smallFiles = '';
for(i=0;i<files.length;i++){
var name = files[i].name,
size = files[i].size;
if(size <= $chunkSize){
smallFiles += name + ','
}
}
var msg = '';
if(''!=smallFiles){
msg += "文件" + smallFiles + "小於10M,";
}
if('' != msg){
msg+="系統不支持上傳這些文件!";
showTipsMsg(msg,3000,3);
return;
}
});
// 上傳中
uploader.on('uploadProgress', function (file, percentage) {
var $li = $('#' + file.id);
var $state = $li.find('span.state');
$state.html("<font color='#330033'>上傳中...</font>");
});
// 文件上傳成功
uploader.on('uploadSuccess', function(file, response) {
var $li = $('#' + file.id);
var $state = $li.find('span.state');
$state.html("<font color='green'>上傳成功!</font>");
});
// 文件上傳失敗
uploader.on('uploadError', function(file, code) {
var $li = $('#' + file.id);
var $state = $li.find('span.state');
$state.html("<font color='red'>上傳失敗!</font>");
});
// 手動上傳
$("#btnUpload").click(function() {
// 執行上傳操作
uploader.upload();
});
// 重置
$("#btnReset").click(function() {
// 清空文件列表,重置上傳控件
$list.html('');
$fileArray = new Array();
$md5Array = new Array();
$nameArray = new Array();
count = 0;
uploader.reset();
});
});
</script>
</body>
</html>
後端Controllerimport java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import com.alibaba.fastjson.JSON;
@Controller
@RequestMapping(value = "/uploadBig")
public class BigFileUploadController extends BaseController {
/** 日誌記錄 */
private static Logger logger = Logger.getLogger(BigFileUploadController.class);
/**
* 保存文件分片
* @param request HttpServletRequest
* @param response HttpServletResponse
*/
@RequestMapping(value = "/upload",
method = RequestMethod.POST)
public void upload(HttpServletRequest request, HttpServletResponse response) {
// 參數獲取
String fileName = request.getParameter("name");
String fileMd5 = request.getParameter("fileMd5");
String chunk = request.getParameter("chunk");
SysUser user = getSysUser(request);
// 臨時目錄
String tempPath = FilePathUtil.getRootTempPath();
// 轉換請求對象得到文件對象
MultipartHttpServletRequest Murequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> files = Murequest.getFileMap();
if (null != files && !files.isEmpty()) {
for (MultipartFile item : files.values()) {
String tempDir = getTempDir(tempPath, user.getUserName(), fileName, fileMd5);
// 創建臨時文件夾
File dir = new File(tempDir);
if (!dir.exists()) {
dir.mkdirs();
}
// 創建分片文件並保存
File chunkFile = new File(tempDir + File.separator + chunk);
try {
chunkFile.createNewFile();
item.transferTo(chunkFile);
} catch (IllegalStateException e) {
logger.error("保存分片文件出錯:" + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
logger.error("保存分片文件出錯:" + e.getMessage());
e.printStackTrace();
}
}
}
}
/**
* 校驗分片是否已上傳
*
* @Title: checkChunk
* @param request HttpServletRequest
* @param response HttpServletResponse
*/
@RequestMapping(value = "/check",
method = RequestMethod.POST)
public void checkChunk(HttpServletRequest request, HttpServletResponse response) {
// 獲取參數
String fileName = request.getParameter("fileName");
String fileMd5 = request.getParameter("fileMd5");
String chunk = request.getParameter("chunk");
String chunkSize = request.getParameter("chunkSize");
SysUser user = getSysUser(request);
// 臨時文件
String tempPath = FilePathUtil.getRootTempPath();
String tempDir = getTempDir(tempPath, user.getUserName(), fileName, fileMd5);
File chunkFile = new File(tempDir + File.separator + chunk);
int result = 0;
// 分片文件是否存在,尺寸是否一致
if (chunkFile.exists() && chunkFile.length() == Integer.parseInt(chunkSize)) {
result = 1;
}
returnJSONData(response, JSON.toJSONString(result));
}
/**
* 合併分片成完整文件
*
* @Title: mergeChunks
* @param request HttpServletRequest
* @param response HttpServletResponse
*/
@SuppressWarnings("resource")
@RequestMapping(value = "/merge",
method = RequestMethod.POST)
public void mergeChunks(HttpServletRequest request, HttpServletResponse response) {
// 獲取參數
String fileName = request.getParameter("fileName");
String fileMd5 = request.getParameter("fileMd5");
SysUser user = getSysUser(request);
// 獲得目標文件夾
String savePath = FilePathUtil.getFilePath();
// 創建目標文件夾
File saveDir = new File(savePath);
if (!saveDir.exists()) {
saveDir.mkdirs();
}
// 文件臨時文件夾"根目錄/temp/userName/文件名/MD5"
String tempDirPath = getTempDir(FilePathUtil.getRootTempPath(), user.getUserName(), fileName, fileMd5);
File tempDir = new File(tempDirPath);
// 獲得分片文件列表
File[] fileArray = tempDir.listFiles(new FileFilter() {
// 只需要文件
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
return false;
} else {
return true;
}
}
});
// 轉成集合進行排序後合併文件
List<File> fileList = new ArrayList<File>(Arrays.asList(fileArray));
Collections.sort(fileList, new Comparator<File>() {
// 按文件名升序排列
@Override
public int compare(File o1, File o2) {
if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
return -1;
} else {
return 1;
}
}
});
// 目標文件
File outfile = new File(savePath + File.separator + fileName);
try {
outfile.createNewFile();
} catch (IOException e) {
logger.error("創建目標文件出錯:" + e.getMessage());
e.printStackTrace();
}
// 執行合併操作
FileChannel outChannel = null;
FileChannel inChannel;
try {
outChannel = new FileOutputStream(outfile).getChannel();
for (File file : fileList) {
inChannel = new FileInputStream(file).getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
file.delete();
}
outChannel.close();
} catch (FileNotFoundException e) {
logger.error("合併分片文件出錯:" + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
logger.error("合併分片文件出錯:" + e.getMessage());
e.printStackTrace();
}
// 刪除臨時文件夾 根目錄/temp/userName/fileName
File tempFileDir = new File(
FilePathUtil.getRootTempPath() + File.separator + user.getUserName() + File.separator + fileName);
FileUtil.deleteDir(tempFileDir);
}
/**
* 獲得分片文件臨時保存路徑
*
* @Title: getTempDir
* @param tempPath 臨時文件根目錄
* @param userName 用戶名
* @param fileName 文件名
* @param fileMd5 文件MD5
* @return String 分片臨時保存路徑
*/
private String getTempDir(String tempPath, String userName, String fileName, String fileMd5) {
StringBuilder dir = new StringBuilder(tempPath);
dir.append(File.separator).append(userName);
dir.append(File.separator).append(fileName);
dir.append(File.separator).append(fileMd5);
return dir.toString();
}
}