Vue large file slice upload, breakpoint resume upload

1. Medium file upload solution-nginx release
In our work, the most common upload function is the upload function of excel. Generally speaking, the size of an excel is within 10MB. If there are tens of MB of excel, it can barely be regarded as a medium file. At this time, we need Set the client_max_body_size value of nginx and let it go. It only uploads a file of tens of MB at a time. The interface will be slower, but it is still acceptable.

2. Large file upload solution – file slice upload and breakpoint resume upload
Idea:

1. Cut the large file into 10M pieces each (according to your own requirements), and then upload it in sequence (to make it easier for you to understand, you can understand it into pages, a total of 89 pieces of data, 10 pieces per page, a total of 9 pages)

2. After the upload is completed, the backend encapsulates the file and returns the uploaded URL address with a progress bar.

3. The upload is interrupted in the middle. If the upload is repeated, how to return to the original position and continue uploading? The solution is to check the backend request interface before each upload to which piece the file has been uploaded to, and connect the chunk parameters with the backend. For example, -1 means the upload has been completed, 0 means the 0th piece, and so on.

Improve various situations: For example, if an error occurs, can you try to upload again, and cancel the upload operation after leaving the page (after leaving the page, the upload continues. In this case, in addition to uploading, there is also a timer problem, which I won’t go into here. In short, clear your mind before leaving)

PS: One upload must be completed before the next one can be uploaded. It is serial, not parallel. In addition, ajax must be asynchronous, whether it is native or jq’s ajax. Because I started to make it synchronous, the progress event could not come out at all.

The backend needs to provide three interfaces

1. Interface for uploading files

2. Before uploading, check whether the file has been uploaded and which interface to upload it to.

3. Upload files to complete the merge interface

 Specific logic Vue
<template>
    <div>
      <input id="file" name="file" type="file"/>
      <el-button size="small" type="primary" id="startBtn">Upload video</el-button>
      <el-progress style="width: 400px" v-if="percentage==100 & amp; & amp; !isError" :percentage="percentage" status="success"></el-progress>
      <el-progress style="width: 400px" :percentage="percentage" v-else-if="percentage > 0 & amp; & amp; !isError & amp; & amp; percentage < 100"></el- progress>
      <el-progress style="width: 400px" :percentage="percentage" status="exception" v-else-if="isError"></el-progress>
    </div>
</template>
 
<script>
  export default {<!-- -->
    data() {<!-- -->
return {<!-- -->
percentage: 0, // progress bar
      isError: false, // Whether an error occurred
      request: null // ajax request
}
},
mounted() {<!-- -->
var height;
  var start;
  var end;
var file;
var name;
var size;
var shardCount;
var i = 0;
var shardSize;
    varGUID;
\t\t
        var status = 0;
        var _this = this;
\t\t
        var page = {<!-- -->
            init: function(){<!-- -->
                $("#startBtn").click($.proxy(this.upload, this));
            },
            upload: function(){<!-- -->
                status = 0;
                
          
                file = $("#file")[0].files[0]; //File object
 
                if (!file) {<!-- -->
                  _this.$message.warning('Please select a file');
                  return;
                }
                name = file.name; //File name
                size = file.size; //total size
                GUID = this.guid(file.name, file.lastModified, file.size, file.type);
                shardSize = 10 * 1024 * 1024; // Use 1MB as a shard
                shardCount = Math.ceil(size / shardSize); //Total number of slices
                
                // Get the current number of slices
                let formData = new FormData();
                formData.append("md5", GUID);
                getCurrentFileChunk(formData).then(res => {<!-- -->
                  if (res.result.chunk < 0) {<!-- -->
                    _this.form.videoUrl = res.result.url;
                    _this.percentage = 100;
                  } else {<!-- -->
                    status = res.result.chunk;
                    start = res.result.chunk * shardSize;
                    end = Math.min(size, start + shardSize);
                    var partFile = file.slice(start,end);
                    
 
                    var pecent=100*(start * shardSize)/file.size;
                    _this.percentage = parseInt(pecent);
 
                    this.partUpload(GUID,partFile,name,shardCount,status);
                  }
                });
            },
            partUpload:function(GUID,partFile,name,chunks,chunk){<!-- -->
              // When re-uploading
              _this.isError = false;
 
              //Construct a form. FormData is new to HTML5.
              var now = this;
              var form = new FormData();
              form.append("md5", GUID);
              form.append("file", partFile); //slice method is used to cut out a part of the file
              form.append("chunk", chunk); //Which piece is the current one?
              //form.append("chunks", chunks); //Total number of pieces
              //Ajax submission
              _this.request = $.ajax({<!-- -->
                  url: process.env.API_F_URL + "/files/uploadVideo",
                  type: "POST",
                  data: form,
                  async: true, //synchronization
                  processData: false, //very important, tell jquery not to process the form
                  contentType: false, //very important, specify false to form the correct Content-Type
                  success: function(data){<!-- -->
                      status + + ;
                      if (status < chunks) {<!-- -->
                        start = status * shardSize,
                        end = Math.min(size, start + shardSize);
                        var partFile = file.slice(start,end);
                        now.partUpload(GUID,partFile,name,shardCount,status);
                      }
                      
                      // if(data.code == 200){<!-- -->
                      // $("#output").html(status + " / " + chunks);
                      // }
                      if(status==chunks){<!-- -->
                        now.mergeFile(GUID,name, chunks);
                      }
                  },
                  error: function(err) {<!-- -->
                    console.log('err', err);
                    if (err.statusText === 'abort') {<!-- -->
                       _this.$message.warning('Upload canceled');
                    } else {<!-- -->
                      _this.isError = true;
                      _this.$message.error('Upload failed, please upload again');
                      
//Upload failed, upload again
                      // start = status * shardSize,
                      // end = Math.min(size, start + shardSize);
                      // var partFile = file.slice(start,end);
                      // now.partUpload(GUID,partFile,name,shardCount,status);
                    }
                  },
                  xhr: function () {<!-- -->
                    //Get the xhr object of ajaxSettings in ajax and bind the processing function of the progress event to its upload attribute.
                    var myXhr = $.ajaxSettings.xhr();
                    if (myXhr.upload) {<!-- -->
                      //Check whether its attribute upload exists
                      myXhr.upload.addEventListener("progress", function(ev){<!-- -->
                        if(ev.lengthComputable){<!-- -->
                          pecent=100*(ev.loaded + start)/file.size;
                          if(pecent>99){<!-- -->
                            cent=99;
                          }
                         
                          _this.percentage = parseInt(pecent);
                        }
                      }, false);
                    }
                    return myXhr;
                  },
                });
        },
        mergeFile:function(GUID,name,chunks){<!-- -->
            var formMerge = new FormData();
            formMerge.append("md5", GUID);
            formMerge.append("fileName", name);
formMerge.append("chunks", chunks);
            $.ajax({<!-- -->
                url: process.env.API_F_URL + "/files/mergeVideo",
                type: "POST",
                data: formMerge,
                processData: false, //very important, tell jquery not to process the form
                contentType: false, //very important, specify false to form the correct Content-Type
                success: function(res){<!-- -->
                  if (res.status == 'success') {<!-- -->
                    _this.isError = false;
                    _this.form.videoUrl = res.result.url;
                    _this.percentage = 100;
                  } else {<!-- -->
                    _this.isError = true;
                    _this.$message.error('Upload failed, please upload again');
                  }
                },
                error: function(err) {<!-- -->
                  _this.isError = true;
                  _this.$message.error('Upload failed, please upload again');
                }
            });
        },
        guid:function(name, lastModified, size, type){<!-- -->
return md5(name + '#' + lastModified + '#' + size + '#' + type);
        }
    };
        $(function(){<!-- -->
            page.init();
        });
    },
    beforeDestroy() {<!-- -->
        this.request.abort();
    }
}
</script>

Reference article: http://blog.ncmem.com/wordpress/2023/10/31/vue large file slice upload-breakpoint resume/
Welcome to join the group to discuss