前端大文件上传

之前被问到这个问题,感觉有点搞头。所以抽时间写了个demo,1个file标签2个接口,后端用golang实现

问题抛出

我们都知道,使用<input type="file">会得到一个文件对象,接着用FormData格式包裹,就可以往后端上传文件了。这种方式一般没问题,但如果文件较大(200M往上),考虑到网洛、性能等因素,就不太合适了。

在这里插入图片描述

解决思路

前端–大文件拆分

File对象里有个size属性,表示选择文件所占大小,它的单位是byte。其实看到这,大概想到了方法。前端可以把大文件拆分成几个文件块(这里我设置的 最大2M),一次次的上传,也叫文件流式上传。

//具体实现
const fileName = (this.fileName = file.name);
const size = file.size;
const shardSize = 1024 * 1024 * 2; //2M
const shardCount = Math.ceil(size / shardSize);

// 切割成多个片段
let shardList = [];
for (let i = 0; i < shardCount; i++) {
  let formData = new FormData();
  formData.append("fileName", fileName);
  formData.append("clipNum", i); //第几块
  const start = i * shardSize;
  const end = Math.min(size, start + shardSize);
  formData.append("content", file.slice(start, end)); //用slice方法切片
  shardList.push(formData);
}
前端–分批请求

以上 使用File对象自带的slice方法,把二进制文件编码切割成了多个文件块。然后需要发很多请求,一次次的搬运到后端去组装文件,这里请求的部分也要稍微注意下。

假定一个文件200M,然后把它拆分成多个大小为2M的文件块,一次请求处理1个文件块,所以至少也要请求100次。如果同时发送大量请求,对后端并发是个考验,前端的用户体验也不太友好。

这里我写了个请求封装,实现了最大请求并行数,上传错误处理等机制

<template>
  <div style="margin: 50px">
    <el-upload
      class="avatar-uploader"
      action=""
      :show-file-list="false"
      :before-upload="beforeAvatarUpload"
    >
      <i class="el-icon-plus avatar-uploader-icon"></i>
    </el-upload>
  </div>
</template>

<script>
import axios from "axios";
axios.defaults.baseURL = "http://localhost:8000";
export default {
  data() {
    return {
      fileName: "",
      shardList: [],
      mergeApi: false,
    };
  },
  methods: {
    beforeAvatarUpload(file) {
      const fileName = (this.fileName = file.name);
      const size = file.size;
      const shardSize = 1024 * 1024 * 2; //2M
      const shardCount = Math.ceil(size / shardSize);

      // 切割成多个片段
      let shardList = [];
      for (let i = 0; i < shardCount; i++) {
        let formData = new FormData();
        formData.append("fileName", fileName);
        formData.append("clipNum", i); //第几块
        const start = i * shardSize;
        const end = Math.min(size, start + shardSize);
        formData.append("content", file.slice(start, end)); //用slice方法切片
        shardList.push(formData);
      }
      this.shardList = shardList;

      // 最大上传并发 10/5
      let uploadMaxCount =
        shardList.length > 10
          ? 10
          : shardList.length > 5
          ? 5
          : shardList.length;
      // 设置多个 并行(保证上传更快),递归(保证连续上传)请求
      for (let j = 0; j < uploadMaxCount; j++) {
        this.uploadApi();
      }
    },
    async uploadApi() {
      // 每次请求取出一个片段
      const formData = this.shardList.shift();
      let ret = await axios.post("/uploadClip", formData);
      // 上传失败 push到末尾
      if (!ret.data.success) {
        this.shardList.push(formData);
      }

      // 判断是否上传完毕
      if (this.shardList.length == 0) {
        // 上传全部片段后,发一次合并请求
        if (!this.mergeApi) {
          this.mergeApi = true;
          let ret = await axios.post("/mergeClip", { fileName: this.fileName });
          this.mergeApi = false;
          console.log(ret.data);
        }
      } else {
        // 继续上传
        this.uploadApi();
      }
    },
  },
};
</script>

<style>
.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.avatar-uploader .el-upload:hover {
  border-color: #409eff;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  line-height: 178px;
  text-align: center;
}
.avatar {
  width: 178px;
  height: 178px;
  display: block;
}
</style>
后端–临时目录,合并文件

这里使用Gin框架配合 前端,写了两个接口(略)

1.uploadClip接口会生成一个临时目录

2.merClip接口,在前端上传完毕后被调用,用于合并所有文件块

在这里插入图片描述
在这里插入图片描述

总结

写完这个例子,对文件上传掌握更深了。在封装请求过程中,还尝试过使用Promise.all(),axios.all()等方法,虽然没有直接效果,还是收到了启发。当然现实各种成熟存储技术,编程中一般不需要自己造轮子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值