文件上传有两套方案

  1. 基于文件流(form-data)
    1. elementui默认是基于文件流的
    2. 上传格式:multipart/form-data
    3. 数据格式:form-data:file文件流信息,filename文件名
  2. 客户端需要把文件转为为base64,再上传
    1. 解析文件FileReader,转为base64

断点续传,切片续传

  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. <!-- PROGRESS -->
  11. <div class="progress">
  12. <span>上传进度:{{ total | totalText }}%</span>
  13. <el-link type="primary" v-if="total > 0 && total < 100" @click="handleBtn">{{ btn | btnText }}</el-link>
  14. </div>
  15. <!-- VIDEO -->
  16. <div class="uploadImg" v-if="video">
  17. <video :src="video" controls />
  18. </div>
  19. </div>
  20. </template>
  21. <script>
  22. import axios from "axios";
  23. // 可以通过文件流信息生成对应的hash,文件内容不变生成的hash不变
  24. import SparkMD5 from "spark-md5";
  25. // 将文件转为的buffer流格式
  26. function fileParse(file, type = "base64") {
  27. return new Promise(resolve => {
  28. let fileRead = new FileReader();
  29. if (type === "base64") {
  30. fileRead.readAsDataURL(file);
  31. } else if (type === "buffer") {
  32. fileRead.readAsArrayBuffer(file);
  33. }
  34. fileRead.onload = (ev) => {
  35. resolve(ev.target.result);
  36. };
  37. });
  38. };
  39. export default {
  40. name: "App",
  41. data() {
  42. return {
  43. total: 0,
  44. video: null,
  45. btn: false,
  46. };
  47. },
  48. filters: {
  49. btnText(btn) {
  50. return btn ? "继续" : "暂停";
  51. },
  52. totalText(total) {
  53. return total > 100 ? 100 : total;
  54. },
  55. },
  56. methods: {
  57. async changeFile(file) {
  58. if (!file) return;
  59. file = file.raw;
  60. // 解析为BUFFER数据
  61. // 我们会把文件切片处理:把一个文件分割成为好几个部分(固定数量/固定大小)
  62. // 每一个切片有自己的部分数据和自己的名字
  63. // HASH_1.mp4
  64. // HASH_2.mp4
  65. // ...
  66. let buffer = await fileParse(file, "buffer"),
  67. spark = new SparkMD5.ArrayBuffer(),
  68. hash,
  69. suffix;
  70. spark.append(buffer);
  71. hash = spark.end();
  72. suffix = /\.([0-9a-zA-Z]+)$/i.exec(file.name)[1];
  73. // 创建100个切片
  74. let partList = [],
  75. partsize = ~~(file.size / 99),
  76. firstsize = file.size % 99,
  77. cur = firstsize;
  78. partList.push({
  79. chunk: file.slice(0, cur),
  80. filename: `${hash}_0.${suffix}`,
  81. })
  82. for (let i = 1; i < 100; i++) {
  83. let item = {
  84. chunk: file.slice(cur, cur + partsize),
  85. filename: `${hash}_${i}.${suffix}`,
  86. };
  87. cur += partsize;
  88. partList.push(item);
  89. }
  90. console.log(file.size, partsize, file, partList)
  91. this.partList = partList;
  92. this.hash = hash;
  93. // 发送请求
  94. this.sendRequest();
  95. },
  96. async sendRequest() {
  97. // 根据100个切片创造100个请求(集合)
  98. let requestList = [];
  99. this.partList.forEach((item, index) => {
  100. // 每一个函数都是发送一个切片的请求
  101. let fn = () => {
  102. let formData = new FormData();
  103. formData.append("chunk", item.chunk);
  104. formData.append("filename", item.filename);
  105. return axios
  106. .post("/single3", formData, {
  107. headers: { "Content-Type": "multipart/form-data" },
  108. })
  109. .then((result) => {
  110. result = result.data;
  111. if (result.code == 0) {
  112. this.total += 1;
  113. // 传完的切片我们把它移除掉
  114. this.partList.splice(index, 1);
  115. }
  116. });
  117. };
  118. requestList.push(fn);
  119. });
  120. // 传递:并行(ajax.abort())/串行(基于标志控制不发送)
  121. let i = 0;
  122. // 完成上传后
  123. let complete = async () => {
  124. let result = await axios.get("/merge", {
  125. params: {
  126. hash: this.hash,
  127. },
  128. });
  129. result = result.data;
  130. if (result.code == 0) {
  131. this.video = result.path;
  132. }
  133. };
  134. let send = async () => {
  135. // 已经中断则不再上传
  136. if (this.abort) return;
  137. if (i >= requestList.length) {
  138. // 都传完了
  139. complete();
  140. return;
  141. }
  142. await requestList[i]();
  143. i++;
  144. send();
  145. };
  146. send();
  147. },
  148. handleBtn() {
  149. if (this.btn) {
  150. //断点续传
  151. this.abort = false;
  152. this.btn = false;
  153. this.sendRequest();
  154. return;
  155. }
  156. //暂停上传
  157. this.btn = true;
  158. this.abort = true;
  159. },
  160. },
  161. };
  162. </script>