文件上传有两套方案
- 基于文件流(form-data)
- elementui默认是基于文件流的
- 上传格式:multipart/form-data
- 数据格式:form-data:file文件流信息,filename文件名
- 客户端需要把文件转为为base64,再上传
- 解析文件FileReader,转为base64
断点续传,切片续传
<template><div id="app"><el-upload drag action :auto-upload="false" :show-file-list="false" :on-change="changeFile"><i class="el-icon-upload"></i><div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div></el-upload><!-- PROGRESS --><div class="progress"><span>上传进度:{{ total | totalText }}%</span><el-link type="primary" v-if="total > 0 && total < 100" @click="handleBtn">{{ btn | btnText }}</el-link></div><!-- VIDEO --><div class="uploadImg" v-if="video"><video :src="video" controls /></div></div></template><script>import axios from "axios";// 可以通过文件流信息生成对应的hash,文件内容不变生成的hash不变import SparkMD5 from "spark-md5";// 将文件转为的buffer流格式function fileParse(file, type = "base64") {return new Promise(resolve => {let fileRead = new FileReader();if (type === "base64") {fileRead.readAsDataURL(file);} else if (type === "buffer") {fileRead.readAsArrayBuffer(file);}fileRead.onload = (ev) => {resolve(ev.target.result);};});};export default {name: "App",data() {return {total: 0,video: null,btn: false,};},filters: {btnText(btn) {return btn ? "继续" : "暂停";},totalText(total) {return total > 100 ? 100 : total;},},methods: {async changeFile(file) {if (!file) return;file = file.raw;// 解析为BUFFER数据// 我们会把文件切片处理:把一个文件分割成为好几个部分(固定数量/固定大小)// 每一个切片有自己的部分数据和自己的名字// HASH_1.mp4// HASH_2.mp4// ...let buffer = await fileParse(file, "buffer"),spark = new SparkMD5.ArrayBuffer(),hash,suffix;spark.append(buffer);hash = spark.end();suffix = /\.([0-9a-zA-Z]+)$/i.exec(file.name)[1];// 创建100个切片let partList = [],partsize = ~~(file.size / 99),firstsize = file.size % 99,cur = firstsize;partList.push({chunk: file.slice(0, cur),filename: `${hash}_0.${suffix}`,})for (let i = 1; i < 100; i++) {let item = {chunk: file.slice(cur, cur + partsize),filename: `${hash}_${i}.${suffix}`,};cur += partsize;partList.push(item);}console.log(file.size, partsize, file, partList)this.partList = partList;this.hash = hash;// 发送请求this.sendRequest();},async sendRequest() {// 根据100个切片创造100个请求(集合)let requestList = [];this.partList.forEach((item, index) => {// 每一个函数都是发送一个切片的请求let fn = () => {let formData = new FormData();formData.append("chunk", item.chunk);formData.append("filename", item.filename);return axios.post("/single3", formData, {headers: { "Content-Type": "multipart/form-data" },}).then((result) => {result = result.data;if (result.code == 0) {this.total += 1;// 传完的切片我们把它移除掉this.partList.splice(index, 1);}});};requestList.push(fn);});// 传递:并行(ajax.abort())/串行(基于标志控制不发送)let i = 0;// 完成上传后let complete = async () => {let result = await axios.get("/merge", {params: {hash: this.hash,},});result = result.data;if (result.code == 0) {this.video = result.path;}};let send = async () => {// 已经中断则不再上传if (this.abort) return;if (i >= requestList.length) {// 都传完了complete();return;}await requestList[i]();i++;send();};send();},handleBtn() {if (this.btn) {//断点续传this.abort = false;this.btn = false;this.sendRequest();return;}//暂停上传this.btn = true;this.abort = true;},},};</script>
