使用Web Uploader文件上传组件辅助Java实现断点续传

使用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();
    }

使用Web Uploader文件上传组件辅助Java实现断点续传

使用Web Uploader文件上传组件辅助Java实现断点续传

文件合并

文件合并流程:

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文件上传组件辅助Java实现断点续传

二、Web Uploader

WebUploader官网

Web Uploader概述

WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+, android 4+。两套运行时,同样的调用方式,可供用户任意选用。

特点

使用Web Uploader文件上传组件辅助Java实现断点续传

钩子方法

在webuploader中提供很多钩子方法,核心钩子方法:
使用Web Uploader文件上传组件辅助Java实现断点续传

注册钩子方法源代码:

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>文件名:&nbsp;&nbsp; {{file.name}}</span>&nbsp;&nbsp;&nbsp;&nbsp;
      <span>文件类型:&nbsp;&nbsp; {{file.ext}}</span>&nbsp;&nbsp;&nbsp;&nbsp;
      <span >上传进度:&nbsp;&nbsp; {{percentage}}%</span>&nbsp;&nbsp;&nbsp;&nbsp;
      <button @click="start(file)">开始 </button>&nbsp;&nbsp;&nbsp;&nbsp;
      <button @click="stop(file)">暂停 </button>&nbsp;&nbsp;&nbsp;&nbsp;
      <button @click="removeFile(file)">删除</button>&nbsp;&nbsp;&nbsp;&nbsp;
    </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>

上一篇:【web前端HTML5+CSS3】06CSS--font(字体)&background(背景)


下一篇:web网页的js定位操作语法