先去下载webuploader 只需要将下面4个文件添加到工程里面就可以了
<link href="${ctxStatic }/webupload/webuploader.css" type="text/css"
rel="stylesheet" />
<script type="text/javascript"
src="${ctxStatic }/webupload/webuploader.nolog.min.js"></script>
<meta name="decorator" content="default" />
<script type="text/javascript">
$(document).ready(function() {
$("#startOrStopBtn").hide();
var $list = $('#thelist'),//文件列表
$btn = $('#startOrStopBtn'),//开始上传按钮
state = 'pending',//初始按钮状态
uploader; //uploader对象
var fileMd5; //文件唯一标识
var fileName;//文件名称
var oldJindu;//如果该文件之前上传过 已经上传的进度是多少
var count=0;//当前正在上传的文件在数组中的下标,一次上传多个文件时使用
var success=0;//上传成功的文件数
var filesArr=new Array();//文件数组:每当有文件被添加进队列的时候 就push到数组中
var map={};//key存储文件id,value存储该文件上传过的进度
// 监听分块上传过程中的三个时间点
WebUploader.Uploader.register({
"before-send-file":"beforeSendFile",//整个文件上传前
"before-send":"beforeSend", //每个分片上传前
"after-send-file":"afterSendFile", //分片上传完毕
},
{
//时间点1:所有分块进行上传之前调用此函数
beforeSendFile:function(file){
var deferred = WebUploader.Deferred();
//1、计算文件的唯一标记fileMd5,用于断点续传 如果.md5File(file)方法里只写一个file参数则计算MD5值会很慢 所以加了后面的参数:10*1024*1024
(new WebUploader.Uploader()).md5File(file,0,10*1024*1024).progress(function(percentage){
$('#'+file.id ).find('p.state').text('正在读取文件信息...');
})
.then(function(val){
$('#'+file.id ).find("p.state").text("成功获取文件信息...");
fileMd5=val;
//获取文件信息后进入下一步
deferred.resolve();
});
fileName=file.name; //为自定义参数文件名赋值
return deferred.promise();
},
//时间点2:如果有分块上传,则每个分块上传之前调用此函数
beforeSend:function(block){
var deferred = WebUploader.Deferred();
$.ajax({
type:"POST",
url:"${ctx}/recorder/data/mergeOrCheckChunks", //ajax验证每一个分片
data:{
param:"checkChunk",
fileName : fileName,
jindutiao:$("#jindutiao").val(),
fileMd5:fileMd5, //文件唯一标记
chunk:block.chunk, //当前分块下标
chunkSize:block.end-block.start//当前分块大小
},
cache: false,
async: false, // 同步
timeout: 1000, //todo 超时的话,只能认为该分片未上传过
//dataType:"json",
success:function(response){
var res = eval('('+response+')');
if(res.ifExist){
//分块存在,跳过
deferred.reject();
}else{
//分块不存在或不完整,重新发送该分块内容
deferred.resolve();
}
}
});
this.owner.options.formData.fileMd5 = fileMd5;
deferred.resolve();
return deferred.promise();
},
//时间点3:所有分块上传成功后调用此函数
afterSendFile:function(){
//如果分块上传成功,则通知后台合并分块
$.ajax({
type:"POST",
url:"${ctx}/recorder/data/mergeOrCheckChunks", //ajax将所有片段合并成整体
data:{
param:"mergeChunks",
fileName : fileName,
fileMd5:fileMd5
},
success:function(data){
count++; //每上传完成一个文件 count+1
if(count<=filesArr.length-1){
uploader.upload(filesArr[count].id);//上传文件列表中的下一个文件
}
//合并成功之后的操作
}
});
}
});
uploader = WebUploader.create({
auto : false, //是否自动上传
pick : {
id : '#picker',
label : '选择文件',
multiple:false
},
duplicate : false, //同一文件是否可重复选择
prepareNextFile: true,
// 不压缩image
resize: false,
accept : {
title : 'ZIP',
extensions : 'zip',
mimeTypes : 'application/x-compressed,application/x-zip-compressed,application/zip,multipart/x-zip'
},
compress : null,//图片不压缩
chunked : true, //分片
chunkSize : 10 * 1024 * 1024, //每片10M
chunkRetry : 3,//如果失败,则不重试
threads : 3,//上传并发数。允许同时最大上传进程数。
fileNumLimit : 10,//验证文件总数量, 超出则不允许加入队列
fileSizeLimit:6*1024*1024*1024,//6G 验证文件总大小是否超出限制, 超出则不允许加入队列
fileSingleSizeLimit:3*1024*1024*1024, //3G 验证单个文件大小是否超出限制, 超出则不允许加入队列
// 禁掉全局的拖拽功能。这样不会出现图片拖进页面的时候,把图片打开。
disableGlobalDnd : true,
// swf文件路径
swf : '${ctxStatic }/webupload/Uploader.swf',
// 文件接收服务端。
server : '${ctx}/recorder/data/fileSave'
});
// 当有文件添加进来的时候
uploader.on( 'fileQueued', function( file ) {
//限制单个文件的大小 超出了提示
if(file.size>3*1024*1024*1024){
$.jBox.tip("单个文件大小不能超过3G");
return false;
}
$("#startOrStopBtn").show();
//如果一次只能选择一个文件,再次选择替换前一个,就增加如下代码
//清空文件队列
//$list.html("");
//清空文件数组
//filesArr=[];
//将选择的文件添加进文件数组
filesArr.push(file);
success++;
$.ajax({
type:"POST",
url:"${ctx}/recorder/data/selectProgressByFileName", //先检查该文件是否上传过,如果上传过,上传进度是多少
data:{
fileName : file.name //文件名
},
cache: false,
async: false, // 同步
//dataType:"json",
success:function(result){
var res = eval('('+result+')');
//上传过
if(res.jindutiao>0){
//上传过的进度的百分比
oldJindu=res.jindutiao/100;
//如果上传过 上传了多少
var jindutiaoStyle="width:"+res.jindutiao+"%";
$list.append( '<div id="' + file.id + '" class="item">' +
'<h4 class="info">' + file.name + '</h4>' +
'<p class="state">已上传'+res.jindutiao+'%</p>' +
'<a href="javascript:void(0);" class="btn btn-primary file_btn btnRemoveFile">删除</a>' +
'<div class="progress progress-striped active">' +
'<div class="progress-bar" role="progressbar" style="'+jindutiaoStyle+'">' +
'</div>' +
'</div>'+
'</div>' );
//将上传过的进度存入map集合
map[file.id]=oldJindu;
}else{//没有上传过
$list.append( '<div id="' + file.id + '" class="item">' +
'<h4 class="info">' + file.name + '</h4>' +
'<p class="state">等待上传...</p>' +
'<a href="javascript:void(0);" class="btn btn-primary file_btn btnRemoveFile">删除</a>' +
'</div>' );
}
}
});
uploader.stop(true);
//删除队列中的文件
$(".btnRemoveFile").bind("click", function() {
var fileItem = $(this).parent();
uploader.removeFile($(fileItem).attr("id"), true);
$(fileItem).fadeOut(function() {
$(fileItem).remove();
});
//数组中的文件也要删除
for(var i=0;i<filesArr.length;i++){
if(filesArr.id==$(fileItem).attr("id")){
filesArr.splice(i,1);//i是要删除的元素在数组中的下标,1代表从下标位置开始连续删除一个元素
}
}
//隐藏上传按钮
success--;
if(success == 0){
$("#startOrStopBtn").hide();
}
});
});
//文件上传过程中创建进度条实时显示
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');
}
//将实时进度存入隐藏域
$("#jindutiao").val(Math.round(percentage * 100));
//根据fielId获得当前要上传的文件的进度
var oldJinduValue = map[file.id];
if(percentage<oldJinduValue && oldJinduValue!=1){
$li.find('p.state').text('上传中'+Math.round(oldJinduValue * 100) + '%');
$percent.css('width', oldJinduValue * 100 + '%');
}else{
$li.find('p.state').text('上传中'+Math.round(percentage * 100) + '%');
$percent.css('width', percentage * 100 + '%');
}
});
//上传成功后执行的方法
uploader.on('uploadSuccess', function( file ) {
//上传成功去掉进度条
$('#'+file.id).find('.progress').fadeOut();
//隐藏删除按钮
$(".btnRemoveFile").hide();
//隐藏上传按钮
success--;
if(success == 0){
$("#startOrStopBtn").hide();
}
$('#'+file.id).find('p.state').text('文件已上传成功');
});
//上传出错后执行的方法
uploader.on('uploadError', function( file ) {
errorUpload=true;
$btn.text('开始上传');
uploader.stop(true);
$('#'+file.id).find('p.state').text('上传出错,请检查网络连接');
});
//文件上传成功失败都会走这个方法
uploader.on('uploadComplete', function( file ) {
});
uploader.on('all', function(type){
if (type === 'startUpload'){
state = 'uploading';
}else if(type === 'stopUpload'){
state = 'paused';
}else if(type === 'uploadFinished'){
state = 'done';
}
if (state === 'uploading'){
$btn.text('暂停上传');
} else {
$btn.text('开始上传');
}
});
//上传按钮的onclick时间
$btn.on('click', function(){
if (state === 'uploading'){
uploader.stop(true);
} else {
//当前上传文件的文件名
var currentFileName;
//当前上传文件的文件id
var currentFileId;
//count=0 说明没开始传 默认从文件列表的第一个开始传
if(count==0){
currentFileName=filesArr[0].name;
currentFileId=filesArr[0].id;
}else{
if(count<=filesArr.length-1){
currentFileName=filesArr[count].name;
currentFileId=filesArr[count].id;
}
}
//先查询该文件是否上传过 如果上传过已经上传的进度是多少
$.ajax({
type:"POST",
url:"${ctx}/recorder/data/selectProgressByFileName",
data:{
fileName : currentFileName//文件名
},
cache: false,
async: false, // 同步
// dataType:"json",
success:function(data){
var res = eval('('+data+')');
//如果上传过 将进度存入map
if(res.jindutiao>0){
map[currentFileId]=res.jindutiao/100;
}
//执行上传
uploader.upload(currentFileId);
}
});
}
});
});
</script>
HTML:
<body>
<input id="jindutiao" type="hidden" />
<div id="uploader" class="wu-example">
<div class="btns">
<div id="picker" class="webuploader-container">
<div class="webuploader-pick">选择文件</div>
<div id="rt_rt_1bchdejhrarjdvd11h41eoh1nt1"
style="position: absolute; top: 0px; left: 0px; width: 88px; height: 35px; overflow: hidden; bottom: auto; right: auto;">
<input id="file_bp" name="file"
class="webuploader-element-invisible" type="file" /> <label
style="opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255, 255, 255) none repeat scroll 0% 0%;"></label>
</div>
</div>
<!-- 文件列表:选择文件后在该div显示 -->
<div id="thelist"
class="uploader-list list-group-item clearfix ng-hide"></div>
<label class="text-right"
style="font-weight: 100; float: left; margin-left: 15px; width: 144px; margin-right: 15px;"></label>
<button class="btn m-b-xs btn-sm btn-info btn-addon"
id="startOrStopBtn" style="padding: 7px 50px; margin-top: 20px;">开始上传</button>
</div>
</div>
</body>
后端:
@Controller
@RequestMapping(value = "${adminPath}/recorder/data")
public class UploadAndDownloadController extends BaseController {
private static final String FILE_UPLOAD = "upload";
private static final Logger logger = LoggerFactory.getLogger(UploadAndDownloadController.class);
@RequiresPermissions("recorder:data:upload")
@RequestMapping(value = "uploadList")
public String uploadList(HttpServletRequest request, HttpServletResponse response, Model model) {
return "modules/evrms/uploadAndDownload/fileUpload";
}
@RequiresPermissions("recorder:data:download")
@RequestMapping(value = "downloadList")
public String downloadList(HttpServletRequest request, HttpServletResponse response, Model model) {
String fileName = request.getRealPath("/") + "/upload/";
File destFile = new File(fileName);
if (!destFile.exists()) {
model.addAttribute("fileNameList", new ArrayList<>());
return "modules/evrms/uploadAndDownload/fileDownload";
}
File[] files = ObjectUtils.listOnlyFiles(destFile);
List<File> fileList = new ArrayList<>(Arrays.asList(files));
// 将文件按最后修改时间倒序排
Collections.sort(fileList, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if (o1.lastModified() < o2.lastModified()) {
return -1;
}
return 1;
}
});
List<String> fileNameList = new ArrayList<>();
for (File file : fileList) {
fileNameList.add(file.getName());
}
model.addAttribute("fileNameList", fileNameList);
return "modules/evrms/uploadAndDownload/fileDownload";
}
@RequiresPermissions("recorder:data:upload")
@RequestMapping(value = "mergeOrCheckChunks")
@ResponseBody
public String mergeOrCheckChunks(HttpServletRequest request, HttpServletResponse response, Model model) {
String param = request.getParameter("param");
String fileName = request.getParameter("fileName");
String savePath = request.getRealPath("/");
// 文件上传的临时文件保存在项目的temp文件夹下
savePath = new File(savePath) + "/upload/";
String json = Constants.EMPTY;
if (param.equals("mergeChunks")) {
// 合并文件
try {
// 读取目录里的所有文件
File f = new File(savePath + "/" + CacheUtils.get(FILE_UPLOAD, "fileName_" + fileName));
// 排除目录,只要文件
File[] fileArray = ObjectUtils.listOnlyFiles(f);
// 转成集合,便于排序
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;
}
return 1;
}
});
// 截取文件名的后缀名
int pointIndex = fileName.lastIndexOf(".");
// 后缀名
String suffix = fileName.substring(pointIndex);
// 合并后的文件
File outputFile = new File(savePath + "/" + fileName);
// 创建文件
try {
outputFile.createNewFile();
} catch (IOException e) {
logger.error("IO Exception:", e);
}
FileChannel outChannel = new FileOutputStream(outputFile).getChannel();
// 合并
FileChannel inChannel;
for (File file : fileList) {
inChannel = new FileInputStream(file).getChannel();
try {
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
logger.error("IO Exception:", e);
}
try {
inChannel.close();
} catch (IOException e) {
logger.error("IO Exception:", e);
}
// 删除分片
file.delete();
}
try {
outChannel.close();
} catch (IOException e) {
logger.error("IO Exception:", e);
}
// 清除文件夹
File tempFile = new File(savePath + "/" + CacheUtils.get(FILE_UPLOAD, "fileName_" + fileName));
if (tempFile.isDirectory() && tempFile.exists()) {
tempFile.delete();
}
Map<String, String> resultMap = new HashMap<>();
// 将文件的最后上传时间和生成的文件名返回
resultMap.put("lastUploadTime",
ObjectUtils.toString(CacheUtils.get(FILE_UPLOAD, "lastUploadTime_" + fileName)));
resultMap.put("pathFileName", CacheUtils.get(FILE_UPLOAD, "fileName_" + fileName) + suffix);
// 合并成功后删除缓存中的进度信息
CacheUtils.remove(FILE_UPLOAD, "jindutiao_" + fileName);
// 合并成功后删除缓存中的最后上传时间,只存没上传完成的
CacheUtils.remove(FILE_UPLOAD, "lastUploadTime_" + fileName);
// 合并成功后删除文件名称与该文件上传时生成的存储分片的临时文件夹的名称在缓存中的信息
CacheUtils.remove(FILE_UPLOAD, "fileName_" + fileName);
json = JsonMapper.getInstance().toJson(resultMap);
} catch (Exception e) {
logger.error("file merge failure:", e);
}
} else if (param.equals("checkChunk")) {
// 检查当前分块是否上传成功
String fileMd5 = request.getParameter("fileMd5");
String chunk = request.getParameter("chunk");
String chunkSize = request.getParameter("chunkSize");
String jindutiao = request.getParameter("jindutiao");// 文件上传的实时进度
try {
// 将当前进度存入缓存
CacheUtils.put(FILE_UPLOAD, "jindutiao_" + fileName, jindutiao);
// 将系统当前时间转换为字符串
String lastUploadTime = ObjectUtils.dateToString(new Date(), "yyyy-MM-dd HH:mm:ss");
// 将最后上传时间以字符串形式存入缓存
CacheUtils.put(FILE_UPLOAD, "lastUploadTime_" + fileName, lastUploadTime);
String tempFileName = ObjectUtils.toString(System.currentTimeMillis());
if (ObjectUtils.isNull(CacheUtils.get(FILE_UPLOAD, "fileName_" + fileName))) {
// 将文件名与该文件上传时生成的存储分片的临时文件夹的名称存入缓存
// 文件上传时生成的存储分片的临时文件夹的名称由MD5和时间戳组成
CacheUtils.put(FILE_UPLOAD, "fileName_" + fileName, fileMd5 + tempFileName);
}
File checkFile = new File(
savePath + "/" + CacheUtils.get(FILE_UPLOAD, "fileName_" + fileName) + "/" + chunk);
// 检查文件是否存在,且大小是否一致
if (checkFile.exists() && checkFile.length() == Integer.parseInt(chunkSize)) {
// 上传过
json = "{\"ifExist\":1}";
} else {
// 没有上传过
json = "{\"ifExist\":0}";
}
} catch (Exception e) {
logger.error("file check failure:", e);
}
}
return json;
}
@RequiresPermissions("recorder:data:upload")
@RequestMapping(value = "fileSave")
// 保存上传分片
public void fileSave(HttpServletRequest request, HttpServletResponse response) {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(factory);
sfu.setHeaderEncoding("utf-8");
String savePath = request.getRealPath("/");
savePath = new File(savePath) + "/upload/";
String fileMd5 = null;
// 小于10m的文件FileItem.getFieldName()是得不到chunk的,所以此处不能是空字符串,否则创建文件时会出错
String chunk = null;
String fileName = null;
try {
List<FileItem> items = sfu.parseRequest(request);
for (FileItem item : items) {
// 上传文件的真实名称
fileName = item.getName();
if (item.isFormField()) {
String fieldName = item.getFieldName();
if (fieldName.equals("fileMd5")) {
try {
fileMd5 = item.getString("utf-8");
} catch (UnsupportedEncodingException e) {
logger.error("encoding cast failure:", e);
}
}
if (fieldName.equals("chunk")) {
try {
chunk = item.getString("utf-8");
} catch (UnsupportedEncodingException e) {
logger.error("encoding cast failure:", e);
}
}
} else {
try {
File file = new File(savePath + "/" + CacheUtils.get(FILE_UPLOAD, "fileName_" + fileName));
if (!file.exists()) {
file.mkdir();
}
File chunkFile = new File(
savePath + "/" + CacheUtils.get(FILE_UPLOAD, "fileName_" + fileName) + "/" + chunk);
FileUtils.copyInputStreamToFile(item.getInputStream(), chunkFile);
} catch (Exception e) {
logger.error("IO Exception:", e);
}
}
}
} catch (FileUploadException e) {
logger.error("file upload failure:", e);
}
}
@RequiresPermissions("recorder:data:upload")
@RequestMapping(value = "selectProgressByFileName")
@ResponseBody
// 当有文件添加进队列时 通过文件名查看该文件是否上传过 上传进度是多少
public String selectProgressByFileName(String fileName) {
String jindutiao = Constants.EMPTY;
if (ObjectUtils.isNotNull(fileName)) {
jindutiao = ObjectUtils.toString(CacheUtils.get(FILE_UPLOAD, "jindutiao_" + fileName));
}
return "{jindutiao :'" + jindutiao + "'}";
}
@RequiresPermissions("recorder:data:download")
@RequestMapping(value = "downFile")
public String downFile(HttpServletResponse response, HttpServletRequest request,
RedirectAttributes redirectAttributes) {
try {
String fileName = request.getParameter("fileName");
File file = new File(request.getRealPath("/") + "/upload/" + fileName);
if (!file.exists()) {
addMessage(redirectAttributes, "您要下载的资源已被删除!");
return "redirect:" + GlobalConfig.getAdminPath() + "/recorder/data/downloadList/?repage";
}
download(response, request, file);
} catch (Exception e) {
logger.error("file download failure:", e);
}
return null;
}
private void download(HttpServletResponse response, HttpServletRequest request, File file) {
InputStream in = null;
ServletOutputStream out = null;
try {
int fSize = Integer.parseInt(String.valueOf(file.length()));
response.setCharacterEncoding("utf-8");
response.setContentType("application/x-download");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Length", String.valueOf(fSize));
// 防止中文乱码
response.setHeader("Content-Disposition",
"attachment;fileName=" + new String(file.getName().getBytes(), "ISO-8859-1"));
in = new FileInputStream(file);
long pos = 0;
if (null != request.getHeader("Range")) {
// 断点续传
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
try {
pos = Long.parseLong(request.getHeader("Range").replaceAll("bytes=", "").replaceAll("-", ""));
} catch (NumberFormatException e) {
pos = 0;
}
}
out = response.getOutputStream();
String contentRange = new StringBuffer("bytes ").append(pos + "").append("-").append((fSize - 1) + "")
.append("/").append(fSize + "").toString();
response.setHeader("Content-Range", contentRange);
in.skip(pos);
byte[] buffer = new byte[1024 * 10];
int length = 0;
while ((length = in.read(buffer, 0, buffer.length)) != -1) {
out.write(buffer, 0, length);
}
} catch (Exception e) {
logger.error("file download failure:", e);
} finally {
try {
if (null != out) {
out.flush();
out.close();
}
if (null != in) {
in.close();
}
} catch (IOException e) {
logger.error("IO Exception:", e);
}
}
}
|
|