背景
根据部门的业务需求,需要在网络状态不良的情况下上传很大的文件(1G+)。
其中会遇到的问题:
1,文件过大,超出服务端的请求大小限制;
2,请求时间过长,请求超时;
3,传输中断,必须重新上传导致前功尽弃。
解决方案实现思路,拿到文件,保存文件唯一性标识,切割文件、分片上传、文件MD5验证、断点续传、手动重试上传。
前言
鉴于过往有使用过webupload文件上传组件的经验,于是此次采用的是Plupload作为替换。Plupload是一款由著名的web编辑器TinyMCE团队开发的上传组件,简单易用且功能强大。
Plupload有以下功能和特点
拥有多种上传方式:HTML5、flash、silverlight以及传统的。Plupload会自动侦测当前的环境,选择最合适的上传方式,并且会优先使用HTML5的方式。所以你完全不用去操心当前的浏览器支持哪些上传方式,Plupload会自动为你选择最合适的方式。
支持以拖拽的方式来选取要上传的文件
支持在前端压缩图片,即在图片文件还未上传之前就对它进行压缩
可以直接读取原生的文件数据,这样的好处就是例如可以在图片文件还未上传之前就能把它显示在页面上预览
支持把大文件切割成小片进行上传,因为有些浏览器对很大的文件比如几G的一些文件无法上传。
环境
vue2.x
webpack3.x
axios
代码
npm安装plupload,文件引入组件,
1 <uploader browse_button="upload_area"
2 :max_retries="3"
3 :url="action"
4 :headers="headers"
5 chunk_size="10MB"
6 drop_element="upload_area"
7 @disableBrowse="!loading"
8 :BeforeUpload="beforeUpload"
9 :ChunkUploaded="chunkUploaded"
10 :FilesAdded="filesAdded"
11 :StateChanged="stateChanged"
12 @inputUploader="inputUploader" />
初始化方法filesAdded(),每次上传前清空队列的其他文件,保证上传的一致性。其次对文件类型进行判断过滤fileType(),文件进入时进行总md5一次fileMd5(),然后进入文件分片chunkCheckStatus(),每个分片都要进行md5并与后台进行校验fileMd5(),确保文件在中断后继续上传的准确性。
1 filesAdded (up, files) {
2 // 删除上传队列中其他文件,只保留最近上传的文件
3 let fileLen = files.length, that = this
4 if (fileLen > 1) {
5 files = files.splice(0, fileLen - 1)// 清空上传队列
6 }
7 files.forEach((f) => {
8 f.status = -1
9 that.dataForm.file = f
10 that.fileType(f.getNative())
11 if (that.loading) {
12 that.computeStatus = true
13 that.progress = 0
14 // 文件分片
15 let chunkSize = 2097152, // Read in chunks of 2MB
16 chunks = Math.ceil(f.size / chunkSize)
17 that.fileMd5(f.getNative(), (e, md5) => {
18 that.dataForm.md5 = md5
19 if (that.loading == true) {
20 that.count = 0
21 that.chunkCheckStatus(md5, that.dataForm.fileName, (uploader, dataList) => {
22 that.uploading = uploader
23 if (that.uploading == true) {
24 for (let chunk = 1; chunk <= chunks; chunk++) {
25 that.fileChunkFile(f.getNative(), chunk, (e, chunkFile) => {
26 that.fileMd5(chunkFile, (e, blockMd5) => {
27 that.PostFile(up, chunkFile, chunk, chunks, md5, blockMd5)
28 })
29 })
30 }
31 } else {
32 // 去重
33 that.progress = 0
34 for (let chunk = 1; chunk <= chunks; chunk++) {
35 let status = 0
36 dataList.some((item) => {
37 if (item.chunk == chunk) {
38 status = 1
39 return false
40 }
41 })
42 if (status == 0) {
43 that.fileChunkFile(f.getNative(), chunk, (e, chunkFile) => {
44 that.fileMd5(chunkFile, (e, blockMd5) => {
45 that.PostFile(up, chunkFile, chunk, chunks, md5, blockMd5)
46 })
47 })
48 }
49 }
50 }
51 })
52 }
53 })
54 }
55 })
56 }
文件md5方法,这里使用了SparkMD5,import SparkMD5 from ‘spark-md5’
1 fileMd5 (file, callback) {
2 let that = this
3 var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
4 file = file,
5 chunkSize = 2097152, // Read in chunks of 2MB
6 chunks = Math.ceil(file.size / chunkSize),
7 currentChunk = 0,
8 spark = new SparkMD5.ArrayBuffer(),
9 fileReader = new FileReader()
10 fileReader.onload = function (e) {
11 console.log('read chunk nr', currentChunk + 1, 'of', chunks)
12 spark.append(e.target.result) // Append array buffer
13 currentChunk++
14 if (currentChunk < chunks) {
15 loadNext()
16 } else {
17 let blockMd5 = ''
18 blockMd5 = spark.end()
19 callback(null, blockMd5)
20 }
21 }
22 fileReader.onerror = function () {
23 callback('oops, something went wrong.')
24 }
25 function loadNext () {
26 var start = currentChunk * chunkSize,
27 end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
28 fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
29 }
30 loadNext()
31 }
文件分片上传方法,验证总分片信息后,把每个分片进行md5加密并上传校验,这里有写进度条相关的控制,不一一展示
1 chunkCheckStatus (md5, fileName, callback) {
2 this.$http({
3 url: this.$http.adornUrl('/biz/upload/getFileBlockStatus'),
4 method: 'get',
5 params: this.$http.adornParams({
6 md5: md5,
7 fileName: fileName
8 })
9 }).then(({ data }) => {
10 if (data && data.code === 0) {
11 if (data.list != null) {
12 this.uploading = false
13 this.chunkCheckData = []
14 data.list.map((item, index) => {
15 if (item.isUpload == true) {
16 this.count++
17 this.chunkCheckData.push(item)
18 }
19 })
20 callback(this.uploading, this.chunkCheckData)
21 return
22 }
23 this.uploading = true
24 callback(this.uploading)
25 } else {
26 this.$message.error(data.msg)
27 this.loading = false
28 this.computeStatus = false
29 return false
30 }
31 })
32 }
总结
上图可以清晰的说明文件加密上传的传输流程,在文件上传加密及Content-Type格式需要与后台协商一致,通常有base64、multipart/form-data两种类型,要清晰了解分片算法及md5,自定义上传文件异步代码,除此之外,可拖拽上传文件、进度条控制等可更好的丰富文件上传体验。
参考文章:http://blog.ncmem.com/wordpress/2023/12/19/vue-js%e5%ae%9e%e7%8e%b0%e5%a4%a7%e6%96%87%e4%bb%b6%e5%88%86%e7%89%87md5%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/
欢迎入群一起讨论