Vue+elelemtUI实现断点续传(前端)
一、在config->index.js中设置 proxy
1 devServer: { 2 proxy: { 3 '/': { 4 target: 'http://127.0.0.1:8888', 5 changeOrigin: true 6 } 7 } 8 }
二、下载并引用相应依赖 (main.js)
本次demo是利用 Element-ui 配合开发。
所需依赖(我的版本):
"element-ui": "^2.13.2",
"axios": "^0.19.2",
"spark-md5": "^3.0.1",
"qs": "^6.9.4",
如果嫌一个一个下载麻烦,可以把上面内容复制到 package.json 中 “dependencies” ,执行 "npm install" 即可。
main.js引入所需包:
1 import ElementUI from 'element-ui'; 2 import 'element-ui/lib/theme-chalk/index.css'; 3 Vue.use(ElementUI);
App.vue中引入所需包:
1 import { fileParse } from "./assets/utils"; 2 import axios from "axios"; 3 import SparkMD5 from "spark-md5";
三、HTML部分
使用Element-UI实现前端样式展示
1 <template> 2 <div id="app"> 3 <el-upload drag action :auto-upload="false" :show-file-list="false" :on-change="changeFile"> 4 <i class="el-icon-upload"></i> 5 <div class="el-upload__text"> 6 将文件拖到此处,或 7 <em>点击上传</em> 8 </div> 9 </el-upload> 10 11 <!-- PROGRESS --> 12 <div class="progress"> 13 <span>上传进度:{{total|totalText}}%</span> 14 <el-link type="primary" v-if="total>0 && total<100" @click="handleBtn">{{btn|btnText}}</el-link> 15 </div> 16 17 <!-- VIDEO --> 18 <div class="uploadImg" v-if="video"> 19 <video :src="video" controls /> 20 </div> 21 </div> 22 </template>
四、使用 promise 封装 fileParse方法
promise:promise构造函数是同步执行的,并且是立即执行的函数,promise.then中的函数是异步的。并且promise状态改变后将不会再更改。
Promise有三个状态:pending(等待)、fulfilled(实现)、rejected(拒绝),其中resolve和reject只有第一次执行有效。
utils.js(对文件转义形式进行封装,根据所传入参数确定封装格式【base64、buffer】)
1 export function fileParse(file, type = "base64") { 2 return new Promise(resolve => { 3 let fileRead = new FileReader(); 4 if (type === "base64") { 5 fileRead.readAsDataURL(file); 6 } else if (type === "buffer") { 7 fileRead.readAsArrayBuffer(file); 8 } 9 fileRead.onload = (ev) => { 10 resolve(ev.target.result); 11 }; 12 }); 13 };
五、逻辑流程(前端)
1、利用结合promise封装filepase方法,解析文件为buffer数据
2、使用sparkMD5生成文件的哈希值,并获取后缀;使用spark.append(buffer)生成哈希值
3、通过正则获取文件后缀
4、创建切面默认参数,包括:切片个数、索引值和结束值
5、通过flie.slice()进行切片
6、进行遍历上传切片,判断当前索引 >= 切片数组后停止,调用合并文件接口告知服务器进行合并
六、项目整体代码
1 <script> 2 import { fileParse } from "./assets/utils"; 3 import axios from "axios"; 4 import SparkMD5 from "spark-md5"; 5 6 export default { 7 name: "App", 8 data() { 9 return { 10 total: 0, 11 video: null, 12 btn: false, 13 }; 14 }, 15 filters: { 16 btnText(btn) { 17 return btn ? "继续" : "暂停"; 18 }, 19 totalText(total) { 20 return total > 100 ? 100 : total; 21 }, 22 }, 23 methods: { 24 async changeFile(file) { 25 if (!file) return; 26 file = file.raw; 27 28 // 解析为BUFFER数据 29 // 我们会把文件切片处理:把一个文件分割成为好几个部分(固定数量/固定大小) 30 // 每一个切片有自己的部分数据和自己的名字 31 // HASH_1.mp4 32 // HASH_2.mp4 33 // ... 34 let buffer = await fileParse(file, "buffer"), 35 spark = new SparkMD5.ArrayBuffer(), 36 hash, 37 suffix; 38 spark.append(buffer); 39 hash = spark.end(); 40 suffix = /\.([0-9a-zA-Z]+)$/i.exec(file.name)[1]; 41 42 // 创建100个切片 43 let partList = [], 44 partsize = file.size / 100, 45 cur = 0; 46 for (let i = 0; i < 100; i++) { 47 let item = { 48 chunk: file.slice(cur, cur + partsize), 49 filename: `${hash}_${i}.${suffix}`, 50 }; 51 cur += partsize; 52 partList.push(item); 53 } 54 55 this.partList = partList; 56 this.hash = hash; 57 this.sendRequest(); 58 }, 59 async sendRequest() { 60 // 根据100个切片创造100个请求(集合) 61 let requestList = []; 62 this.partList.forEach((item, index) => { 63 // 每一个函数都是发送一个切片的请求 64 let fn = () => { 65 let formData = new FormData(); 66 formData.append("chunk", item.chunk); 67 formData.append("filename", item.filename); 68 return axios 69 .post("/single3", formData, { 70 headers: { "Content-Type": "multipart/form-data" }, 71 }) 72 .then((result) => { 73 result = result.data; 74 if (result.code == 0) { 75 this.total += 1; 76 // 传完的切片我们把它移除掉 77 this.partList.splice(index, 1); 78 } 79 }); 80 }; 81 requestList.push(fn); 82 }); 83 84 // 传递:并行(ajax.abort())/串行(基于标志控制不发送) 85 let i = 0; 86 let complete = async () => { 87 let result = await axios.get("/merge", { 88 params: { 89 hash: this.hash, 90 }, 91 }); 92 result = result.data; 93 if (result.code == 0) { 94 this.video = result.path; 95 } 96 }; 97 let send = async () => { 98 // 已经中断则不再上传 99 if (this.abort) return; 100 if (i >= requestList.length) { 101 // 都传完了 102 complete(); 103 return; 104 } 105 await requestList[i](); 106 i++; 107 send(); 108 }; 109 send(); 110 }, 111 handleBtn() { 112 if (this.btn) { 113 //断点续传 114 this.abort = false; 115 this.btn = false; 116 this.sendRequest(); 117 return; 118 } 119 //暂停上传 120 this.btn = true; 121 this.abort = true; 122 }, 123 }, 124 }; 125 </script>
参考资料:https://www.bilibili.com/video/BV18v411v7Xu?t=346