前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 1.引入文件 -->
<link rel="stylesheet" type="text/css" href="/css/webuploader.css">
<script type="text/javascript" src="/js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="/js/webuploader.js"></script>
</head>
<body>
<!-- 2.创建页面元素 -->
<div id="uploader">
<!-- 创建用于拖拽的区域:
把文件拖到这里,它就会自动上传 -->
<#--<div id="dndArea">-->
<#--<p style="font-size: 8px">把文件拖到这里,它就会自动上传</p>-->
<#--</div>-->
<!-- 用于显示文件列表 -->
<div id="fileListDiv" class="container">
<table id="fileList" class="table table-striped" border="1" width="800px">
<tr class="filelist-head">
<th class="file-name">文件名称</th>
<th class="file-size">大小</th>
<th width="20%" class="file-pro">进度</th>
<th class="file-status">状态</th>
<th width="20%" class="file-manage">操作</th>
</tr>
</table>
</div>
<!-- 用于选择文件:
手动选择文件
filePicker这个控件ID是固定的-->
<div id="filePicker">文件上传</div>
</div>
<!-- 3.添加js代码 -->
<script type="text/javascript">
// 监听分块上传的时间点,断点续传
var fileMd5=[];
//文件名,用于最后合并时生成相同名字的文件,最好不用中文文件名
var fileName=[];
var count=0;//当前正在上传的文件在数组中的下标,一次上传多个文件时使用
var filesArr=new Array();
//注册事件函数
WebUploader.Uploader.register({
"before-send-file":"beforeSendFile",
"before-send":"beforeSend",
"after-send-file":"afterSendFile"
},{
//beforeSendFile先于beforeSend事件执行
beforeSendFile:function(file) {
fileName.push(file.name);
// fileName=file.name;
console.info("上传文件:" + fileName);
// 创建一个deffered,用于通知是否完成操作
var deferred = WebUploader.Deferred();
// 计算文件的唯一标识MD5值,用于断点续传和妙传
(new WebUploader.Uploader()).md5File(file, 0, 5*1024*1024)
.progress(function(percentage){
// $("#"+file.id).find("span.state").text("正在获取文件信息...");
})
.then(function(val) {
// fileMd5 = val;
fileMd5.push(val);
//$("#" + file.id).find("span.state").text("成功获取文件信息");
//放行
deferred.resolve();
});
// 通知完成操作
return deferred.promise();
},
beforeSend:function(block) {
var deferred = WebUploader.Deferred();
// 支持断点续传,发送到后台判断是否已经上传过
$.ajax({
type:"POST",
url:"/check",//后台处理分块接口
async: false,
data:{
// 文件唯一表示
fileMd5:fileMd5[0],
// 当前分块下标(插件会自动生成,至于生成多少块,插件决定)
chunk:block.chunk,
// 当前分块大小
chunkSize:block.end-block.start
},
dataType:"json",
success:function(response) {
if(response.ifExist) {
// 分块存在,跳过该分块
deferred.reject();
} else {
// 分块不存在或不完整,重新发送
deferred.resolve();
}
}
}
);
// 发送文件md5字符串到后台
this.owner.options.formData.fileMd5 = fileMd5;
return deferred.promise();
},
afterSendFile:function(file) {
// 当所有分块都发送完毕之后,通知后台合并分块
var name =fileName.pop();
var md5=fileMd5.pop();
async: false,
$.ajax({
type:"POST",
url:"/union",//调用后台合并分块的接口
data:{
fileMd5:md5,
fileName:name, //原文件名要传到后台,作为合并后文件的文件名
//fileSize:file.get
fileSize:file.size
},
success:function(response){
}
});
}
}
);
// 上传基本配置--具体上传器,上传每个块
var uploader = WebUploader.create({
swf:"/js/Uploader.swf",
server:"/bigupload",//后台的上传接口,用于接收每个块
pick:"#filePicker",
auto:true,
// dnd:"#dndArea", //拖拽区域
disableGlobalDnd:true,
paste:"#uploader",
// 分块上传设置
// 是否分块
chunked:true,
// 每块文件大小(默认5M)
chunkSize:5*1024*1024,
// 开启几个并行线程(默认3个)
threads:1,
// fileNumLimit
// 在上传当前文件时,准备好下一个文件
prepareNextFile:false,
duplicate: false //是否支持重复上传
// chunkRetry : 2, //如果某个分片由于网络问题出错,允许自动重传次数
});
// 生成缩略图和上传进度
uploader.on("fileQueued", function(file) {
//fileQueued事件
// 把文件信息追加到fileList的div中
var $list = $("#fileList");
$list.append('<tr id="'+ file.id +'" class="file-item">'+
'<td class="file-name">'+ file.name +'</td>'+
'<td width="20%" class="file-size">'+ (file.size/1024/1024).toFixed(1)+'M' +'</td>' +
'<td width="20%" class="file-pro">0%</td>'+
'<td class="file-status">等待上传</td>'+
'<td width="20%" class="file-manage"><a class="stop-btn" href="javascript:;">暂停 </a>' +
'<a class="restart-btn" href="javascript:;">开始 </a>' + '<a class="remove-this" href="javascript:;">取消</a></td>'+
'</tr>');
//暂停上传的文件
$list.on('click','.stop-btn',function(){
uploader.stop(true);
});
//删除上传的文件(取消操作,这并不会重置进度,下次重传还是会从之前的进度开始)
$list.on('click','.remove-this',function(){
if ($(this).parents(".file-item").attr('id') == file.id) {
uploader.removeFile(file);
$(this).parents(".file-item").remove();
}
});
//暂停后继续开始
$list.on('click','.restart-btn',function(){
// uploader.start();//用这个是错误的
// uploader.startUpload(file);//也是错的
//至于哪个函数才是正确的,看下源码就知道
//正确的是:upload
uploader.upload(file);
});
}
);
//重复添加文件
var timer1;
uploader.onError = function(code){
clearTimeout(timer1);
timer1 = setTimeout(function(){
alert('该文件已存在');
},250);
};
// 监控上传进度
// percentage:代表上传文件的百分比
// uploader.on("uploadProgress", function(file, percentage) {
// //更新进度的具体逻辑,可由自己实现
// //最简单的就是展示数字,例如:87%
// $("#" + file.id).find("span.percentage").text(Math.round(percentage * 100) + "%");
// });
//形象一点,可以展示滚动条,进度条
// 文件上传过程中创建进度条实时显示
uploader.on( 'uploadProgress', function( file, percentage ) {
$("td.file-pro").text("");
var $li = $( '#'+file.id ).find('.file-pro'),
$percent = $li.find('.file-progress .progress-bar');
// 避免重复创建
if ( !$percent.length ) {
$percent = $('<div class="progress progress-striped active">' +
'<div class="progress-bar" role="progressbar" style="width: 0%">' +
'<p class="per" style="line-height: 0px;">0%</p>' +
'</div>' +
'</div>' ).appendTo( $li ).find('.progress-bar');
}
$li.siblings('.file-status').text('上传中');
//将百分比赋值到文本控件
$li.find('.per').text((percentage * 100).toFixed(2) + '%');
$percent.css('width', percentage * 100 + '%');
});
//其他事件
// 文件上传成功
uploader.on( 'uploadSuccess', function( file ) {
$( '#'+file.id ).find('.file-status').text('已上传');
});
// 文件上传失败,显示上传出错
uploader.on( 'uploadError', function( file ) {
$( '#'+file.id ).find('.file-status').text('上传出错');
});
</script>
</body>
</html>
后端代码
package com.lopo.controller;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.*;
/**
* 合并文件
*/
@RestController
public class BigfileController {
//文件后台统一存放位置
private static final String serverPath = "C:\\upload";
/**
* 文件合并
*
* @param request
* @param response
* @throws IOException
*/
// String fileMd5=null;
@PostMapping("/union")
public void BigFileUnion(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获得需要合并的目录
String fileMd5 = request.getParameter("fileMd5");
//文件的原始文件名
String fileName = request.getParameter("fileName");
Integer fileSize = Integer.valueOf(request.getParameter("fileSize"));
System.out.println("fileSize = " + fileSize);
// 读取目录所有文件
File f = new File(serverPath + File.separator + fileMd5);
File[] fileArray = f.listFiles();
// File[] fileArray = f.listFiles(new FileFilter() {
// // 排除目录,只要文件
// public boolean accept(File pathname) {
// if (pathname.isDirectory()) {
// return false;
// }
// return true;
// }
// });
// 转成集合,便于排序
List<File> fileList = new ArrayList<>(Arrays.asList(fileArray));
//System.out.println("fileList = " + fileList);
// 从小到大排序
Collections.sort(fileList, new Comparator<File>() {
public int compare(File o1, File o2) {
if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
return -1;
}
return 1;
}
});
//输出文件名
File outputFile = new File(serverPath + File.separator + File.separator + fileName);
System.out.println("outputFile = " + outputFile);
File parentFile = outputFile.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
// 创建文件
outputFile.createNewFile();
// 输出流
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
FileChannel outChannel = fileOutputStream.getChannel();
// 合并,核心就是FileChannel,将多个文件合并为一个文件
FileChannel inChannel;
for (File file : fileList) {
inChannel = new FileInputStream(file).getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
// 删除分片
file.delete();
}
// 关闭流
fileOutputStream.close();
outChannel.close();
// 清除文件夹
File tempFile = new File(serverPath + File.separator + fileMd5);
if (tempFile.isDirectory() && tempFile.exists()) {
tempFile.delete();
}
}
@ResponseBody
@PostMapping("/check")
public String BigFileCheck(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 校验文件是否已经上传并返回结果给前端,就一个作用:校验块是否存在,假如不存在,前端会再次用上传器传到后台
// 文件唯一表示
String fileMd5 = request.getParameter("fileMd5");
// 当前分块下标
String chunk = request.getParameter("chunk");
// 当前分块大小
String chunkSize = request.getParameter("chunkSize");
// 直接根据块的索引号找到分块文件
File checkFile = new File(serverPath + File.separator + fileMd5 + File.separator + chunk);
// 检查文件是否存在,且大小一致(必须满足这两个条件才认为块是已传成功)
// response.setContentType("text/html;charset=utf-8");
if (checkFile.exists() && checkFile.length() == Integer.parseInt((chunkSize)) && fileMd5.length() == 32) {
// response.getWriter().write("{\"ifExist\":1}");
return "{\"ifExist\":1}";
} else {
//假如文件没存在,说明没有上传成功,返回0
// response.getWriter().write("{\"ifExist\":0}");
return "{\"ifExist\":0}";
}
}
/**
* 文件上传
* @param request
* @param response
* @throws IOException
*/
@PostMapping("/bigupload")
public void BigFileUpload(HttpServletRequest request, HttpServletResponse response) throws IOException {
// response.getWriter().append("Served at: ").append(request.getContextPath());
System.out.println("进入FileUploadServlet后台...");
// 1.创建DiskFileItemFactory对象,配置缓存用
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
// 2. 创建 ServletFileUpload对象
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
// 3. 设置文件名称编码
servletFileUpload.setHeaderEncoding("utf-8");
// 4. 开始解析文件
// 文件md5获取的字符串
String fileMd5 = null;
// 文件的索引
String chunk = null;
try {
List<FileItem> items = servletFileUpload.parseRequest(request);
for (FileItem fileItem : items) {
if (fileItem.isFormField()) {
//普通数据,例如字符串
String fieldName = fileItem.getFieldName();
if ("info".equals(fieldName)) {
String info = fileItem.getString("utf-8");
System.out.println("info:" + info);
}
if ("fileMd5".equals(fieldName)) {
fileMd5 = fileItem.getString("utf-8");
System.out.println("fileMd5:" + fileMd5);
}
if ("chunk".equals(fieldName)) {
chunk = fileItem.getString("utf-8");
System.out.println("chunk:" + chunk);
}
} else {
if (StringUtils.isEmpty(fileMd5)) {
fileMd5 = "test";//假如md5没有,就用test作为目录名
}
if (StringUtils.isEmpty(chunk)) {
chunk = fileItem.getName();//filename
}
// 如果文件夹没有创建文件夹
File file = new File(serverPath + File.separator + fileMd5);
if (!file.exists()) {
file.mkdirs();
}
//这时保存的每个块,块先存好,后续会调合并接口,将所有块合成一个大文件
File chunkFile = new File(serverPath + File.separator + fileMd5 + File.separator + chunk);
FileUtils.copyInputStreamToFile(fileItem.getInputStream(), chunkFile);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
版权声明:本文为weixin_45944917原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。