一、在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