关于超大文件分片上传

原文地址点击此处

前端代码部分

// 点击 Button 触发 input从而选择文件并上传。
<template>
    <div>
      <button type="button" v-on:click="selectFile()" class="btn btn-white btn-default btn-round">
          <i class="ace-icon fa fa-upload"></i>{{text}}
      </button>
      <input class="hidden" type="file" ref="file" v-on:change="uploadFile()" v-bind:id="inputId+'-input'">

    </div>
</template>


/**
  * 点击【上传】
  */
 selectFile () {
     let _this = this;
     $("#" + _this.inputId + "-input").trigger("click");
 },

/**
 * 上传文件
 */
uploadFile() {
  let _this = this; 
  // 1. 获取 input 中被选中的文件
  let file = _this.$refs.file.files[0];


  // 2. 生成文件标识,标识多次上传的是不是同一个文件
  let key = hex_md5(file.name + file.size + file.type);
  let key10 = parseInt(key, 16);
  let key62 = Tool._10to62(key10);

  // 判断文件格式 (非必选,根据实际情况选择是否需要限制文件上传类型)
  let suffixs = _this.suffixs;
  let fileName = file.name;
  let suffix = fileName.substring(fileName.lastIndexOf(".")+1, fileName.length).toLowerCase();
  if(!(!suffixs || JSON.stringify(suffixs) === "{}" || suffixs.length === 0)) {
      let validateSuffix = false;
      for(let s of suffixs) {
          if(s.toLocaleLowerCase() === suffix) {
              validateSuffix = true;
              break;
          }
      }
      if(!validateSuffix) {
          Toast.warning("文件格式不正确!只支持上传:" + suffixs.join(","));
          $("#" + _this0.inputId + "-input").val("");
          return;
      }
  }

  // 3. 文件分片开始
      // 3.1 设置与计算分片必选参数
  let shardSize = 20 * 1024 *1024; // 20M为一个分片
  let shardIndex = 1;   // 分片索引,1表示第1个分片
  let size = file.size; // 文件的总大小
  let shardTotal = Math.ceil(size / shardSize); // 总分片数

      //  3.2 拼接将要传递到参数, use 非必选,这里用来标识文件用途。
  let param = {
    'shardIndex': shardIndex,
    'shardSize': shardSize,
    'shardTotal': shardTotal,
    'use': _this.use,
    'name': file.name,
    'suffix': suffix,
    'size': file.size,
    'key': key62
  };

  //  3.3  传递分片参数,通过递归完成分片上传。
  _this.upload(param);

},


 /**
   * 文件分片函数
  */
 getFileShard(shardIndex, shardSize) {
   let _this = this;
   let file = _this.$refs.file.files[0];
   let start = (shardIndex - 1) * shardSize; // 当前分片起始位置
   let end = Math.min(file.size, start + shardSize); // 当前分片结束位置
   let fileShard = file.slice(start, end); // 从文件中截取当前的分片数据
   return fileShard;
 },

后端部分

Base64ToMultipartFile直接百度粘贴即可


@PostMapping("/big-upload")
public ResponseDto uploadOfMerge(@RequestBody FileDto fileDto) throws IOException {
     log.info("上传文件开始");


     String use = fileDto.getUse();
     String key = fileDto.getKey();
     String suffix = fileDto.getSuffix();
     String shardBase64 = fileDto.getShard();
     // 1. 将分片转为 MultipartFile
     MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(fileDto.getShard());
     //  获取分片要保存到的路径
     //  根据use字段获取文件用途,从而上传到不同文件夹下(非必选)
     FileUseEnum useEnum = FileUseEnum.getByCode(use);
         // 若文件夹不存在则创建
     String dir = useEnum.name().toLowerCase();
     File fullDir = new File(FILE_PATH + dir);
     if (!fullDir.exists()) {
         fullDir.mkdir();
     }

     String path = new StringBuffer(dir)
             .append(File.separator)
             .append(key)
             .append(".")
             .append(suffix).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4
     String localPath = new StringBuffer(path)
             .append(".")
             .append(fileDto.getShardIndex()).toString(); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1
     String fullPath = FILE_PATH + localPath;
     // 2. 通过 transferTo 保存文件到服务器磁盘
     File dest = new File(fullPath);
     shard.transferTo(dest);
     log.info(dest.getAbsolutePath());
     // 3. 将文件分片信息保存/更新到数据库
     log.info("保存文件记录开始");
     fileDto.setPath(path);
     fileService.saveBigFile(fileDto);

     ResponseDto responseDto = new ResponseDto();
     responseDto.setContent(fileDto);

     // 4. 合并
         // 若分片均已上传,将所有分片合并成一个文件。
     if (fileDto.getShardIndex().equals(fileDto.getShardTotal())) {
         this.merge(fileDto);
     }
     // 5. 返回分片上传结果
     return responseDto;
 }

private void merge(FileDto fileDto) {
     log.info("合并分片开始");
     String path = fileDto.getPath();
     Integer shardTotal = fileDto.getShardTotal();
     File newFile = new File(FILE_PATH + path);
     byte[] byt = new byte[10 * 1024 * 1024];
     FileInputStream inputStream = null;   // 分片文件
     int len;

     // 文件追加写入
     try (FileOutputStream outputStream = new FileOutputStream(newFile, true);
           ) {
         for (int i = 0; i < shardTotal; i++) {
             // 读取第一个分片
             inputStream = new FileInputStream(new File(FILE_PATH + path + "." + (i+1))); // course\6sfSqfOwzmik4A4icMYuUe.mp4.1
             while ((len = inputStream.read(byt))!=-1) {
                 outputStream.write(byt, 0, len);
             }
         }
     } catch (FileNotFoundException e) {
         log.info("文件寻找异常", e);
     } catch (IOException e) {
         log.info("分片合并异常", e);
     } finally {
         try {
             if(inputStream !=null) {
                 inputStream.close();
             }
             log.info("IO流关闭");
         } catch (IOException e) {
             log.error("IO流关闭", e);
         }

     }
     log.error("合并分片结束");

     System.gc();
     // 删除分片
     log.info("删除分片开始");
     for (int i = 0; i < shardTotal; i++) {
         String filePath = FILE_PATH + path + "." + (i + 1);
         File file = new File(filePath);
         boolean result = file.delete();
         log.info("删除{},{}", filePath, result ? "成功" : "失败");
     }
     log.info("删除分片结束");
 }

关于断点续传以及极速秒传

uploadFile 中的 _this.upload(param); 被 检测已上传分片的函数 _this.check(param); 取代。

upload 上传函数由 check 调用。

check(param) {
   let _this = this;
   _this.$ajax.get(process.env.VUE_APP_SERVER + "/file/admin/check/" + param.key).then((res)=> {
       let resp = res.data;
       if(resp.success) {
         let obj = resp.content;
         if(!obj) {
           param.shardIndex = 1;
           console.log("没有找到文件记录,从分片1开始上传");
           _this.upload(param);
         } else if (obj.shardIndex === obj.shardTotal) {
           // 已上传分片 = 分片总数,说明已全部上传完,不需要再上传
           Toast.success("文件极速秒传成功!");
           _this.afterUpload(resp);  
           $("#" + _this.inputId + "-input").val("");  
         }else {
           param.shardIndex = obj.shardIndex + 1;
           console.log("没有找到文件记录,从分片1开始上传");
           _this.upload(param);
         }
       } else {
         console.log("文件上传失败");
         $("#" + _this.inputId + "-input").val("");
       }
   });
 },

后端

 @GetMapping("/check/{key}")
 public ResponseDto check(@PathVariable String key) {
     log.info("检测上传分片开始:{}}", key);
     ResponseDto responseDto = new ResponseDto();
     FileDto fileDto = fileService.findByKey(key);
     responseDto.setContent(fileDto);
     return responseDto;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值