使用Web Uploader文件上传组件辅助Java实现断点续传
一、断点续传
断点续传概述
断点续传指的是在下载或上传时,将下载或上传任务划分为几个 部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。
上传流程:
1、上传前先把文件分成块
2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传
3、各分块上传完成最后合并文件
文件分块
文件分块的流程如下:
1、获取源文件长度
2、根据设定的分块文件的大小计算出块数
3、从源文件读数据依次向每一个块文件写数据。
@Test
public void testChunk() throws IOException {
//源文件
File sourceFile = new File("D:\\video\\test.avi");
//块文件目录
String chunkFileFolder = "D:\\video\\chunks\\";
//定义块文件大小
long chunkFileSize = 2 * 1024 * 1024;
//块数
long chunkFileNum = (long) Math.ceil(sourceFile.length() * 1.0 /chunkFileSize);
//创建读文件的对象
RandomAccessFile raf_read = new RandomAccessFile(sourceFile,"r");
//缓冲区
byte[] b = new byte[1024];
for(int i=0;i<chunkFileNum;i++){
//块文件路径
File chunkFile = new File(chunkFileFolder+i);
//创建向块文件的写对象
RandomAccessFile raf_write = new RandomAccessFile(chunkFile,"rw");
int len = -1;
while((len = raf_read.read(b))!=-1){
raf_write.write(b,0,len);
//如果块文件的大小达到 2M开始写下一块儿
if(chunkFile.length()>=chunkFileSize){
break;
}
}
raf_write.close();
}
raf_read.close();
}
文件合并
文件合并流程:
1、找到要合并的文件并按文件合并的先后进行排序。
2、创建合并文件
3、依次从合并的文件中读取数据向合并文件写入数
@Test
public void testMergeFile() throws IOException {
//块文件目录
String chunkFileFolderPath = "D:\\video\\chunks\\";
//块文件目录对象
File chunkFileFolder = new File(chunkFileFolderPath);
//分块文件列表
File[] files = chunkFileFolder.listFiles();
//转成集合,便于排序 将块文件排序,按名称升序
List<File> fileList = Arrays.asList(files);
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;
}
});
//合并文件
File mergeFile = new File("D:\\video\\test_merge.avi");
//创建新文件
boolean newFile = mergeFile.createNewFile();
//创建写对象
RandomAccessFile raf_write = new RandomAccessFile(mergeFile,"rw");
byte[] b = new byte[1024];
for(File chunkFile:fileList){
//创建一个读块文件的对象
RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"r");
int len = -1;
while((len = raf_read.read(b))!=-1){
raf_write.write(b,0,len);
}
raf_read.close();
}
raf_write.close();
}
二、Web Uploader
Web Uploader概述
WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+, android 4+。两套运行时,同样的调用方式,可供用户任意选用。
特点
钩子方法
在webuploader中提供很多钩子方法,核心钩子方法:
注册钩子方法源代码:
WebUploader.Uploader.register({
"before‐send‐file":"beforeSendFile",
"before‐send":"beforeSend",
"after‐send‐file":"afterSendFile"
}
使用webUploader前需要创建webUploader对象。
指定上传分块的地址:/api/media/upload/uploadchunk
// 创建uploader对象,配置参数
this.uploader = WebUploader.create(
{
swf:"/static/plugins/webuploader/dist/Uploader.swf",//上传文件的flash文件,浏览器不支持h5时启动
flash
server:"/api/media/upload/uploadchunk",//上传分块的服务端地址,注意跨域问题
fileVal:"file",//文件上传域的name
pick:"#picker",//指定选择文件的按钮容器
auto:false,//手动触发上传
disableGlobalDnd:true,//禁掉整个页面的拖拽功能
chunked:true,// 是否分块上传
chunkSize:1*1024*1024, // 分块大小(默认5M)
threads:3, // 开启多个线程(默认3个)
prepareNextFile:true// 允许在文件传输时提前把下一个文件准备好
}
)
before-send-file
:文件开始上传前前端请求服务端准备上传工作
type:"POST",
url:"/api/media/upload/register",
data:{
// 文件唯一表示
fileMd5:this.fileMd5,
fileName: file.name,
fileSize:file.size,
mimetype:file.type,
fileExt:file.ext
}
before-send
:上传分块前前端请求服务端校验分块是否存在。
type:"POST",
url:"/api/media/upload/checkchunk",
data:{
// 文件唯一表示
fileMd5:this.fileMd5,
// 当前分块下标
chunk:block.chunk,
// 当前分块大小
chunkSize:block.end‐block.start
}
after-send-file
:在所有分块上传完成后触发,可以请求服务端合并分块文件
type:"POST",
url:"/api/media/upload/mergechunks",
data:{
fileMd5:this.fileMd5,
fileName: file.name,
fileSize:file.size,
mimetype:file.type,
fileExt:file.ext
}
事件
事件名 | 参数说明 | 描述 |
---|---|---|
dndAccept | items {DataTransferItemList}DataTransferItem | 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 |
beforeFileQueued | file {File}File对象 | 当文件被加入队列之前触发,此事件的handler返回值为false,则此文件不会被添加进入队列。 |
fileQueued | file {File}File对象 | 当文件被加入队列以后触发。 |
filesQueued | files {File}数组,内容为原始File(lib/File)对象。 | 当一批文件添加进队列以后触发。 |
fileDequeued | file {File}File对象 | 当文件被移除队列后触发。 |
reset | 当 uploader 被重置的时候触发。 | |
startUpload | 当开始上传流程时触发。 | |
stopUpload | 当开始上传流程暂停时触发。 | |
uploadFinished | 当所有文件上传结束时触发。 | |
uploadStart | file {File}File对象 | 某个文件开始上传前触发,一个文件只会触发一次。 |
uploadBeforeSend | object {Object} data {Object}默认的上传参数,可以扩展此对象来控制上传参数。 headers {Object}可以扩展此对象来控制上传头部。 |
当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 |
uploadAccept | object {Object} ret {Object}服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 |
当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为false, 则此文件将派送server类型的uploadError事件。 |
uploadProgress | file {File}File对象 percentage {Number}上传进度 |
上传过程中触发,携带上传进度。 |
uploadError | file {File}File对象 reason {String}出错的code |
当文件上传出错时触发。 |
uploadSuccess | file {File}File对象response {Object}服务端返回的数据 | 当文件上传成功时触发。 |
uploadComplete | file {File} [可选]File对象 | 不管成功或者失败,文件上传完成时触发。 |
error | type {String}错误类型。 | 当validate不通过时,会以派送错误事件的形式通知调用者。 通过upload.on(‘error’, handler)可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 Q_EXCEED_NUM_LIMIT 在设置了fileNumLimit且尝试给uploader添加的文件数量超出这个值时派送。 Q_EXCEED_SIZE_LIMIT 在设置了Q_EXCEED_SIZE_LIMIT且尝试给uploader添加的文件总大小超出这个值时派送。 Q_TYPE_DENIED 当文件类型不满足时触发。。 |
三、服务端实现功能
1、上传前检查上传环境
检查文件是否上传,已上传则直接返回。
检查文件上传路径是否存在,不存在则创建。
2、分块检查
检查分块文件是否上传,已上传则返回true。
未上传则检查上传路径是否存在,不存在则创建。
3、分块上传
将分块文件上传到指定的路径。
4、合并分块
将所有分块文件合并为一个文件。
在数据库记录文件信息。
FileUploadController
@RestController
@RequestMapping("/upload")
public class FileUploadController {
@Autowired
private IFileUploadService fileUploadService;
//文件上传前的注册
@PostMapping("/register")
public Object register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
return fileUploadService.register(fileMd5,fileName,fileSize,mimetype,fileExt);
}
//分块检查
@PostMapping("/checkchunk")
public Object checkchunk(String fileMd5, Integer chunk, Integer chunkSize) {
return fileUploadService.checkchunk(fileMd5,chunk,chunkSize);
}
//上传分块
@PostMapping("/uploadchunk")
public Object uploadchunk(MultipartFile file, String fileMd5, Integer chunk) {
return fileUploadService.uploadchunk(file,fileMd5,chunk);
}
//合并文件
@PostMapping("/mergechunks")
public Object mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
return fileUploadService.mergechunks(fileMd5,fileName,fileSize, mimetype,fileExt);
}
}
IFileUploadService
public interface IFileUploadService {
/**
* 文件上传前检查文件是否存在
*
* 根据文件md5得到文件路径
*
* @param fileMd5 文件md5值
* @param fileName 文件名
* @param fileSize 文件大小
* @param mimetype 文件类型
* @param fileExt 文件扩展名
* @return
*/
Object register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt);
/**
* 分块文件检查
* @param fileMd5 文件md5
* @param chunk 分块文件下标
* @param chunkSize 分块文件大小
* @return
*/
Object checkchunk(String fileMd5, Integer chunk, Integer chunkSize);
/**
* 上传分块文件
* @param file 文件对象
* @param fileMd5 文件md5
* @param chunk 分块文件下标
* @return
*/
Object uploadchunk(MultipartFile file, String fileMd5, Integer chunk);
/**
* 合并分块文件
* @param fileMd5 文件md5
* @param fileName 文件名
* @param fileSize 文件大小
* @param mimetype 文件类型
* @param fileExt 文件扩展名
* @return
*/
Object mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt);
}
FileUploadServiceImpl
@Service
public class FileUploadServiceImpl implements IFileUploadService {
@Value("${fileUploadPaht}")
String uploadPath;
@Override
public Object register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
//检查文件在磁盘上是否存在
//文件所属目录的路径
String fileFolderPath = this.getFileFolderPath(fileMd5);
//文件路径
String filePath = this.getFilePath(fileMd5, fileExt);
File file = new File(filePath);
//文件是否存在
boolean exists = file.exists();
//TODO 检查数据库中文件信息是否存在
//文件不存,检查文件所在目录是否存在,不存在则创建
File fileFolder = new File(fileFolderPath);
if (!fileFolder.exists()) {
fileFolder.mkdirs();
}
HashMap map = new HashMap<String, Integer>();
map.put("ok", 1);
return map;
}
@Override
public Object checkchunk(String fileMd5, Integer chunk, Integer chunkSize) {
//得到分块文件的所在目录
String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
//得到分块文件
File chunkFile = new File(chunkFileFolderPath + chunk);
//检查分块文件是否存在
if (chunkFile.exists()) {
//块文件存在
return BaseUtil.back(1, "");
} else {
//块文件不存在
return BaseUtil.back(0, "");
}
}
@Override
public Object uploadchunk(MultipartFile file, String fileMd5, Integer chunk) {
//得到分块文件目录
String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
//得到分块文件路径
String chunkFilePath = chunkFileFolderPath + chunk;
//检查分块目录,不存在则要自动创建
File chunkFileFolder = new File(chunkFileFolderPath);
if (!chunkFileFolder.exists()) {
chunkFileFolder.mkdirs();
}
//得到上传文件的输入流
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = file.getInputStream();
outputStream = new FileOutputStream(new File(chunkFilePath));
IOUtils.copy(inputStream, outputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return BaseUtil.back(1, "");
}
@Override
public Object mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
//得到分块文件所属目录
String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
File chunkFileFolder = new File(chunkFileFolderPath);
//得到分块文件列表
File[] files = chunkFileFolder.listFiles();
List<File> fileList = Arrays.asList(files);
//创建合并文件路径
String filePath = this.getFilePath(fileMd5, fileExt);
File mergeFile = new File(filePath);
//执行合并
mergeFile = this.mergeFile(fileList, mergeFile);
if (mergeFile == null) {
//合并文件失败
BaseUtil.back(0, "");
}
//校验文件的md5值是否和前端传入的md5一致
boolean checkFileMd5 = this.checkFileMd5(mergeFile, fileMd5);
if (!checkFileMd5) {
//校验文件失败
BaseUtil.back(0, "");
}
//TODO 将文件的信息写入数据库
String file_id = fileMd5;
String file_name = fileName;
String file_name_ext = fileMd5 + "." + fileExt;
//文件路径保存相对路径
String file_path = fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + "." + fileExt;
;
Long file_size = fileSize;
String file_type = mimetype;
String file_ext = fileExt;
return BaseUtil.back(1, "");
}
/**
* 校验文件的完整性
*
* @param mergeFile
* @param md5
* @return
*/
private boolean checkFileMd5(File mergeFile, String md5) {
try {
//创建文件输入流
FileInputStream inputStream = new FileInputStream(mergeFile);
//得到文件的md5
String md5Hex = DigestUtils.md5Hex(inputStream);
//和传入的md5比较
if (md5.equalsIgnoreCase(md5Hex)) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return false;
}
/**
* 文件合并
*
* @param chunkFileList
* @param mergeFile
* @return
*/
private File mergeFile(List<File> chunkFileList, File mergeFile) {
try {
//如果合并文件已存在则删除,否则创建新文件
if (mergeFile.exists()) {
mergeFile.delete();
} else {
//创建一个新文件
mergeFile.createNewFile();
}
//对块文件进行排序
Collections.sort(chunkFileList, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if (Integer.parseInt(o1.getName()) > Integer.parseInt(o2.getName())) {
return 1;
}
return -1;
}
});
//创建一个写对象
RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");
byte[] b = new byte[1024];
for (File chunkFile : chunkFileList) {
RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "r");
int len = -1;
while ((len = raf_read.read(b)) != -1) {
raf_write.write(b, 0, len);
}
raf_read.close();
}
raf_write.close();
return mergeFile;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 获取文件所属目录路径
*
* @param fileMd5 文件md5值
* @return
*/
private String getFileFolderPath(String fileMd5) {
return uploadPath + fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/";
}
/**
* 获取文件的路径
*
* @param fileMd5 文件md5值
* @param fileExt 文件扩展名
* @return
*/
private String getFilePath(String fileMd5, String fileExt) {
return uploadPath + fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + "." + fileExt;
}
/**
* 获取分块文件所在目录路径
*
* @param fileMd5
* @return
*/
private String getChunkFileFolderPath(String fileMd5) {
return uploadPath + fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/chunk/";
}
}
public class BaseUtil {
private int code;
private String msg;
public static HashMap<Object,Object> back(int code, String msg){
HashMap<Object, Object> map = new HashMap<>();
map.put("id",code);
map.put("msg",msg);
return map;
}
}
四、Web Uploader的使用
引入webuploader
直接引入文件
<!--引入CSS-->
<link rel="stylesheet" type="text/css" href="webuploader文件夹/webuploader.css">
<!--引入JQuery-->
<script type="text/javascript" src="JQuery文件夹/JQuery.js"></script>
<!--引入JS-->
<script type="text/javascript" src="webuploader文件夹/webuploader.js"></script>
模块化引入
下载安装集成相应模块
npm install webuploader --save
npm install jquery --save
入webuploder和jquery
import $ from 'jquery'
import WebUploader from 'webuploader'
初始化webuploader
// 初始化webuploader 创建uploader对象,配置参数
this.uploader = WebUploader.create(
{
//上传文件的flash文件,浏览器不支持h5时启动flash
swf: './Uploader.swf',
//上传分块的服务端地址,注意跨域问题,路径写死Or代理转发请求路径
server: "http://localhost:8080/zhunda-training-api/training/upload/uploadchunk" ,
//跨域时,是否允许携带cookie
withCredentials: true,
//文件上传域的name
fileVal:"file",
//指定选择文件的按钮容器
pick:"#picker",
//手动触发上传
auto:false,
//禁掉整个页面的拖拽功能
disableGlobalDnd:true,
//开起分片上传
chunked:true,
//分块大小(默认5M)
chunkSize:1*1024*1024,
//开启多个线程(默认3个)
threads:3,
//允许在文件传输时提前把下一个文件准备好
prepareNextFile:true
}
);
选择文件后触发
//选择文件后触发
this.uploader.on("beforeFileQueued", function(file) {
// this.uploader.removeFile(file)
//重置uploader
console.log(file)
this.uploader.reset()
this.percentage = 0;
}.bind(this));
将文件添加到队列
// 将文件添加到队列
this.uploader.on("fileQueued", function(file) {
console.log("将文件添加到队列")
this.fileList.push(file);
var $list = $('#fileList')
$list.append( '<div id="' + file.id + '" class="item">' +
'<h4 class="info">' + file.name + '</h4>' +
'<p class="state">等待上传...</p>' +
'</div>' );
this.uploadFile=file;
this.percentage = 0;
}.bind(this)
);
上传失败:uploadError
//上传失败触发
this.uploader.on("uploadError", function(file,reason) {
console.log(reason)
alert("上传文件失败");
});
上传成功:uploadSuccess
//上传成功触发
this.uploader.on("uploadSuccess", function(file,res ) {
console.log(res)
alert("上传文件成功!");
});
监控上传进度:uploadProgress
// 监控上传进度
// percentage:代表上传文件的百分比
this.uploader.on("uploadProgress", function(file, percentage) {
console.log("监控上传进度")
this.percentage = Math.ceil(percentage * 100);
}.bind(this));
//每个分块上传请求后触发
this.uploader.on( 'uploadAccept', function( file, res ) {
console.log("每个分块上传请求后触发")
if(res.code===0){//分块上传失败
return false;
}
});
Demo示例
<template>
<div id="app">
<div id="uploader" class="wu-example">
<!-- 选择文件的按钮 -->
<div class="btns" style="float:left;padding-right: 20px">
<div id="picker">选择文件</div>
</div>
<!-- 开始上传按钮 -->
<div id="ctlBtn" class="webuploader-pick" @click="upload()">开始上传</div>
</div>
<!--用来存放文件信息-->
<div v-for="(file,index) in fileList" :key="index" id="fileLilst" class="uploader-list" :class="`file-${file.id}`">
<span>文件名: {{file.name}}</span>
<span>文件类型: {{file.ext}}</span>
<span >上传进度: {{percentage}}%</span>
<button @click="start(file)">开始 </button>
<button @click="stop(file)">暂停 </button>
<button @click="removeFile(file)">删除</button>
</div>
</div>
</template>
<script>
import $ from 'jquery'
import WebUploader from 'webuploader'
export default {
name: 'App',
data(){
return{
uploader:{},
uploadFile:{},
percentage:0,
fileMd5:'',
fileList:[],
}
},
created() {
console.log($)
},
methods:{
//开始上传
upload(){
if(this.fileList.length>0){
this.fileList.forEach(file =>{
this.uploader.upload(file);
})
}else{
alert("请选择文件");
}
},
start(file){
this.uploader.upload(file);
},
//暂停文件上传
stop(file) {
this.uploader.stop(file);
},
// 在队列中移除文件
removeFile(file) {
// 取消并中断文件上传
this.uploader.cancelFile(file,true);
// 在队列中移除文件
this.uploader.removeFile(file);
//界面移除
this.fileList=this.fileList.filter(item => item.id!==file.id)
},
},
mounted(){
let that = this;
//检查文件是否已上穿
let beforeSendFile=function(file){
//创建一个Deferred,Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。
// 在文件开始发送前做些异步操作。
// WebUploader会等待此异步操作完成后,开始发送文件。
var deferred = WebUploader.Deferred();
//计算文件 md5 值,用于断点续传,返回一个 promise 对象,可以监听 progress 进度
(new WebUploader.Uploader()).md5File(file)
.then(function(val) {
that.fileMd5=val
that.uploadFile = file;
//封装参数
let searchParams = new URLSearchParams();
searchParams.append("fileMd5",that.fileMd5);
searchParams.append("fileName", file.name);
searchParams.append("fileSize",file.size);
searchParams.append("mimetype",file.type);
searchParams.append("fileExt",file.ext);
//向服务端请求注册上传文件
that.axios.post(that.$Api.webUploader.register,searchParams).then(res =>{
if (res.data.code===1){
//alert('上传文件注册成功开始上传');
console.log("初始化准备完成")
deferred.resolve();
}else{
// alert("上传文件注册失败");
deferred.reject();
console.log("初始化准备失败")
}
})
}.bind(that));
return deferred.promise();
};
//每次上传分块前校验分块,如果已存在分块则不再上传,达到断点续传的目的
let beforeSend=function(block){
var deferred = WebUploader.Deferred();
//封装请求参数
let searchParams = new URLSearchParams();
// 文件唯一表示
searchParams.append("fileMd5",that.fileMd5);
// 当前分块下标
searchParams.append("chunk",block.chunk);
// 当前分块大小
searchParams.append("chunkSize",block.end-block.start);
that.axios.post(that.$Api.webUploader.checkchunk,searchParams).then(res =>{
if (res.data.code===1){
// 分块存在,跳过该分块
deferred.reject();
console.log("分块存在,跳过该分块")
}else{
// 分块不存在或不完整,重新发送
deferred.resolve();
console.log("分块不存在或不完整,重新发送")
}
})
//构建fileMd5参数,上传分块时带上fileMd5
that.uploader.options.formData.fileMd5 = that.fileMd5;
that.uploader.options.formData.chunk = block.chunk;
return deferred.promise();
};
// 合并分块
let afterSendFile=function(file){
//封装请求参数
let searchParams = new URLSearchParams();
searchParams.append("fileMd5",that.fileMd5);
searchParams.append("fileName",file.name);
searchParams.append("fileSize",file.size);
searchParams.append("mimetype",file.type);
searchParams.append("fileExt",file.ext);
that.axios.post(that.$Api.webUploader.mergechunks,searchParams).then(res =>{
if (res.data.code===1){
console.log("文件合并成功")
}else{
console.log("文件合并失败")
}
})
};
/**
* Uploader.register(map, proto);
*
* responseMap {object}API 名称与函数实现的映射
* proto {object}组件原型,构造函数通过 constructor 属性定义添加组件
*/
WebUploader.Uploader.register({
"before-send-file":"beforeSendFile",
"before-send":"beforeSend",
"after-send-file":"afterSendFile"
},{beforeSendFile, beforeSend, afterSendFile,}
);
// 创建uploader对象,配置参数
this.uploader = WebUploader.create(
{
//上传文件的flash文件,浏览器不支持h5时启动flash
swf: '../node_modules/webuploader/dist/Uploader.swf',
//上传分块的服务端地址,注意跨域问题,路径写死Or代理转发请求路径
server: "http://localhost:8080/zhunda-training-api/training/upload/uploadchunk" ,
//跨域时,是否允许携带cookie
withCredentials: true,
//文件上传域的name
fileVal:"file",
//id:指定选择文件的按钮容器,不指定则不创建按钮 multiple:是否开起同时选择多个文件能力
pick:{id:"#picker",multiple:true },
//选完文件后,是否自动上传
auto:false,
//禁掉整个页面的拖拽功能
disableGlobalDnd:true,
//分片上传
chunked:true,
//重复上传
duplicate: false,
//分块大小(默认5M)
chunkSize:1*1024*1024,
//开启多个线程(默认3个)
threads:3,
// 限制上传个数
fileNumLimit: 3,
//允许在文件传输时提前把下一个文件准备好
prepareNextFile:true,
//指定接受哪些类型的文件。需要分开指定。
//title {String} 文字描述
//extensions {String} 允许的文件后缀,不带点,多个用逗号分割。
//mimeTypes {String} 多个用逗号分割。
accept:[{title: 'file', extensions: 'mp4,avi', mimeTypes: 'video/*'}],
}
);
// 将文件添加到队列
this.uploader.on("fileQueued", function(file) {
console.log("将文件添加到队列")
this.uploadFile=file;
this.fileList.push(file);
}.bind(this)
);
//选择文件后触发
this.uploader.on("beforeFileQueued", function(file) {
// this.uploader.removeFile(file)
//重置uploader
console.log(file)
//this.uploader.reset()
}.bind(this));
// 监控上传进度
// percentage:代表上传文件的百分比
this.uploader.on("uploadProgress", function(file, percentage) {
console.log('监控上传进度',file,percentage)
this.percentage = Math.ceil(percentage * 100);
}.bind(this));
//上传失败触发
this.uploader.on("uploadError", function(file,reason) {
console.log(reason)
alert("上传文件失败");
});
//上传成功触发
this.uploader.on("uploadSuccess", function(file,res ) {
console.log(res)
//alert("上传文件成功!");
});
//每个分块上传请求后触发
this.uploader.on( 'uploadAccept', function( file, res ) {
console.log("每个分块上传请求后触发")
if(res.code===0){//分块上传失败
return false;
}
});
}
}
</script>
<style >
.webuploader-container {
position: relative;
}
.webuploader-element-invisible {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px,1px,1px,1px);
}
.webuploader-pick {
position: relative;
display: inline-block;
cursor: pointer;
background: #00b7ee;
padding: 10px 15px;
color: #fff;
text-align: center;
border-radius: 3px;
overflow: hidden;
}
.webuploader-pick-hover {
background: #00a2d4;
}
.webuploader-pick-disable {
opacity: 0.6;
pointer-events:none;
}
</style>