记一次文件上传的曲折历史
工具
- Vue
- element-UI
- spark-md5
- axios
需求
上传pdf文件,为了节省时间,对文件进行切片处理,对上传进度进行监控(这里挺有趣)
实现过程
1 文件切片
思路:使用了File.slice()对文件进行切片处理,将每一份切片放到一个数组中,然后使用Array.map()方法配合axios将每一段切片上传至服务器
HTML主体(pug)
el-upload(
ref="resource",
drag
:file-list="resourcefileList",
:before-upload="resourceBeforeUpload",
accept=".pdf",
:limit="1",
action="",
:http-request="customUpload",
:on-remove="removeList"
)
i.el-icon-upload
.el-upload__text 将文件拖到此处,或
em 点击上传
使用element-ui的upload组件,使用自定义事件 http-request
,在customUpload
中对文件进行操作
customUpload(content) {
// 覆盖默认上传事件
let file = content.file
if (!file) return
// 将切片固定成10分,也可以固定大小上传
let axiosArray = []
let chunkList = []
let chunkSize = file.size / 10
let current = 0
let fileName = this.formData.identity + '.pdf'
while (current < 10) {
chunkList.push({
chunk: file.slice(current * chunkSize, (current + 1) * chunkSize),
fileName: current + "-" + fileName
})
current++
}
// 初始化数据
this.percentage = [0,0,0,0,0,0,0,0,0,0]
this.uploadOver = false
this.progressVisable = true
// 切片并发传给后端,要注意切片上传时请求头是 multipart/form-data 合并切片时请求头是x-www-form-urlencoded,只能上传键值对
chunkList.map((item) => {
const index = parseInt(item.fileName.split('-')[0])
let form = new FormData()
form.append("file", item.chunk, item.fileName)
form.append("fileName", this.formData.identity)
axiosArray.push(
this.$http.post("/upload/part", form, {
headers: { "Content-Type": "multipart/form-data" },
}).then(res=> {
res
// this.percentage += 10
})
)
})
// 所有切片上传成功后合并
Promise.all(axiosArray).then(res => {
res
this.$http.get(`/upload/merge?fileName=${this.formData.identity}`).then(res => {
if(res) {
// console.log(res);
// this.progressVisable = false
this.formData.resource = res.data.obj
this.uploadOver = true
this.overContent = '上传成功,感谢支持'
}
}).catch(err => {
err
this.uploadOver = true
this.overContent = '传输失败,请重试'
})
})
},
这段代码应该很清楚明白了,代码中的identity
是文件的MD5值,利用spark-md5计算,目的是为了防止重复文件的上传,计算方法如下
computeMd5(file) {
// 计算文件MD5
const fread = new FileReader()
const spark = new SparkMD5.ArrayBuffer()
return new Promise((resolve, reject) => {
if(!file) {
reject('no file')
}
fread.readAsArrayBuffer(file)
fread.onload = (e) => {
spark.append(e.target.result)
const md5 = spark.end()
resolve(md5)
}
})
},
这里有一个小插曲,fread.onload是一个异步函数,所以在计算md5值的时候会出现null的情况,这里返回一个Promise,配合调用时的async/await实现同步操作
2 进度条实现
原始思路:一共分了10片,每一片上传成功后将进度+10,所有文件上传完成之后就是100%了(你可能一眼看出了这里面的问题,所以我说的原是思路)
问题分析:由于http请求是并行的,所以,看图
上一秒所有的都没完成,进度一直卡在0%,下一秒所有的请求同时完成,瞬间进度100%,进度条俨然成了摆设
新思路:利用axios的onUploadProgress方法,创建一个长度为10的进度数组,然后更改对应的进度数据,利用computed计算属性返回10个分片进度的和
代码实现:
chunkList.map((item) => {
const index = parseInt(item.fileName.split('-')[0])
let form = new FormData()
form.append("file", item.chunk, item.fileName)
form.append("fileName", this.formData.identity)
axiosArray.push(
this.$http.post("/upload/part", form, {
headers: { "Content-Type": "multipart/form-data" },
onUploadProgress: (e) => {
this.$set(this.percentage, index , e.loaded / e.total * 10 | 0)
}
})
)
})
这里注意,computed并不能直接监听数组的响应式,需要使用
$set
这个API来设置元素的值,参数是(数组,下标,新的值)
上面计算的MD5值出了用于鉴别重复文件还可以用来实现秒传功能,两个文件的MD5相同那就表示是同一个文件,直接告诉用户上传完成,其实就是“欺骗用户”