我们在使用 vue 做上传文件的功能的时候,有一种比较复杂的需求场景是这样的:
- 客户端本地需要要先计算文件的 
md5 值,传递给后端进行比对,如果已经存在,则极速完成上传 
- 文件需要分片传输,而且需要控制并发量
 
- 需要支持文件夹上传
 
下面我们就上面几个需求需要关注的重点说明一下:
- 对于本地计算 md5,可以安装库 
spark-md5,然后引入 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | import SparkMD5 from "spark-md5"; export default {     methods: {                  getMd5(file, relativePath) {             return new Promise((resolve, reject) => {                 let blobSlice =                         File.prototype.slice ||                         File.prototype.mozSlice ||                         File.prototype.webkitSlice,                     chunkSize = 1024 * 1024 * 100,                     chunks = Math.ceil(file.size / chunkSize),                     currentChunk = 0,                     spark = new SparkMD5.ArrayBuffer(),                     fileReader = new FileReader();
                  const loadNext = () => {                     let start = currentChunk * chunkSize,                         end =                             start + chunkSize >= file.size                                 ? file.size                                 : start + chunkSize;
                      fileReader.readAsArrayBuffer(                         blobSlice.call(file, start, end)                     );                 };
                  fileReader.onload = function (e) {                     spark.append(e.target.result);                     currentChunk++;
                      
                      if (currentChunk < chunks) {                         loadNext();                     } else {                         console.log("finished loading");                         resolve(spark.end());                     }                 };
                  fileReader.onerror = function () {                     reject("计算失败");                     console.warn("md5计算失败");                 };
                  loadNext();             });         },     }, };
   | 
 
- 并发上传
 
文件需要并发上传,我们要清楚一个浏览器的重要知识点,就是一个浏览器的 TCP 并发连接数是有限制的,一般是 6 个,这样我们的并发就千万不要超过这个值,具体可以在浏览器的 network 请求列表中,查看右侧的时间线,如果有是在“开始连接”中有很大的时间占用,就说明这个并发数太大了,没有控制好。
当我们一次性选择多个文件时,当开始上传时是同时并发上传吗?当然不是的,因为如果全部文件同时上传,而每个文件又要分片上传处理,这样就达不到控制上传并发的目的,这样我们一般的做法是一次只处理一个文件,每个文件再去控制并发数来达到控制整体并发的目的。
- 文件夹与文件
 
对于上传文件夹,在 html 中,我们给触发元素添加 webkitdirectory 属性,需要注意的是,一般个按钮不能同时实现上传文件夹和上传文件的功能,一般做法是二选一,用不同的按钮来实现不同的目的,在有拖拽的场景时,可以通过监听拖拽事件,来实现同时处理文件和文件夹的目的,不过还是需要自已打标识
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
   | export default {     mounted() {         this.getRemainFileCount();         this.$nextTick(() => {             let that = this;             document                 .querySelector(".el-upload-dragger")                 .addEventListener("drop", (e) => {                     let items = e.dataTransfer.items;                     this.$nextTick(async () => {                         const fileList = [];
                          const processItem = async (item) => {                             if (item.kind === "file") {                                 let entry = item.webkitGetAsEntry();                                                                  await that.getFileEntry(entry, fileList);                             }                         };
                                                   const promises = [];                         for (let i = 0; i < items.length; i++) {                             promises.push(processItem(items[i]));                         }                         await Promise.all(promises);
                                               });                 });         });     },
      methods: {                  async getFileEntry(entry, fileList) {             if (entry.isFile) {                 return new Promise((resolve, reject) => {                     entry.file(                         (file) => {                                                          if (fileList.length <= this.fileLimit) {                                 let path = entry.fullPath.substring(1);                                                                  let newFile = new File([file], file.name, {                                     type: file.type,                                 });                                 Object.defineProperty(                                     newFile,                                     "webkitRelativePath",                                     {                                         writable: false,                                                                                  value:                                             path.indexOf("/") > -1 ? path : "",                                     }                                 );                                 const fileItem = {                                     name: path,                                     percentage: 0,                                     raw: newFile,                                     size: newFile.size,                                     status: "ready",                                     uid: newFile.uid,                                 };                                 fileList.push(fileItem);                             }                             resolve();                         },                         (e) => {                             console.log(e);                             reject(e);                         }                     );                 });             } else {                 let reader = entry.createReader();
                  let entries = await new Promise((resolve, reject) => {                     reader.readEntries(resolve, reject);                 });
                                   let promises = entries.map((entry) =>                     this.getFileEntry(entry, fileList)                 );                 await Promise.all(promises);             }
              return true;         },     }, };
  | 
 
对于文件夹的整体处理,需要关注属性 webkitRelativePath ,然后整体赋值其相对路径,让后端能够正常处理。
      
     
    
      
  
  
    
      
      
        
        致力于网站建设与Web开发。喜欢新事物,关注前后端动态,对新的技术有追求, 做一个优秀的web全栈工程师。