文件上传 小文件(图片、文档、视频)上传可以直接使用很多ui框架封装的上传组件,或者自己写一个input 上传,利用FormData 对象提交文件数据,后端使用spring提供的MultipartFile进行文件的接收,然后写入即可。但是对于比较大的文件,比如上传2G左右的文件(http上传),就需要将文件分片上传(file.slice()),否则中间http长时间连接可能会断掉。
分片上传 分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
秒传 通俗的说,你把要上传的东西上传,服务器会先做MD5校验,如果服务器上有一样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让MD5改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5就变了,就不会秒传了.
断点续传 断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。本文的断点续传主要是针对断点上传场景。
vue-simple-uploader是基于 simple-uploader.js 封装的vue上传插件。它的优点包括且不限于以下几种:
建议先读一遍simple-uploader.js的文档,然后再读一下vue-simple-uploader的文档,了解一下各个参数的作用是什么
vue-simple-uploader文档
simple-uploader.js文档
安装
npm install vue-simple-uploader --save
使用:
// 在main.js中:
import uploader from 'vue-simple-uploader'
Vue.use(uploader)
相关概念
代码参考:
git代码
onFileAdded(file) {
this.panelShow = true
Bus.$emit('fileAdded')
// 将额外的参数赋值到每个文件上,以不同文件使用不同params的需求
file.params = this.params
// 计算MD5,完成后开始上传操作
this.computeMD5(file).then((result) => this.startUpload(result))
},
具体的MD5计算方法,会在下面讲,这里先简单引出。
上传过程中,会不断触发file-progress上传进度的回调
// 文件进度的回调
onFileProgress(rootFile, file, chunk) {
console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
},
onFileSuccess(rootFile, file, response, chunk) {
let res = JSON.parse(response);
// 服务器自定义的错误,这种错误是Uploader无法拦截的
if (!res.result) {
this.$message({ message: res.message, type: 'error' });
return
}
// 如果服务端返回需要合并
if (res.needMerge) {
api.mergeSimpleUpload({
tempName: res.tempName,
fileName: file.name,
...file.params,
}).then(data => {
// 文件合并成功
Bus.$emit('fileSuccess', data);
}).catch(e => {});
// 不需要合并
} else {
Bus.$emit('fileSuccess', res);
console.log('上传成功');
}
},
onFileError(rootFile, file, response, chunk) {
console.log(error)
},
需要注意的就是在最后文件上传成功的事件中,通过后台返回的字段,来判断是否要再给后台发送一个文件合并的请求。
/**
* 计算md5值,以实现断点续传及秒传
* @param file
* @returns Promise
*/
computeMD5(file) {
let fileReader = new FileReader()
let time = new Date().getTime()
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
let currentChunk = 0
const chunkSize = 10 * 1024 * 1000
let chunks = Math.ceil(file.size / chunkSize)
let spark = new SparkMD5.ArrayBuffer()
// 文件状态设为"计算MD5"
this.statusSet(file.id, 'md5')
file.pause()
// 计算MD5时隐藏”开始“按钮
this.$nextTick(() => {
document.querySelector(`.file-${file.id} .uploader-file-resume`).style.display = 'none'
})
loadNext()
return new Promise((resolve, reject) => {
fileReader.onload = (e) => {
spark.append(e.target.result)
if (currentChunk < chunks) {
currentChunk++
loadNext()
// 实时展示MD5的计算进度
this.$nextTick(() => {
const md5ProgressText ='校验MD5 '+ ((currentChunk/chunks)*100).toFixed(0)+'%'
document.querySelector(`.custom-status-${file.id}`).innerText = md5ProgressText
})
} else {
let md5 = spark.end()
// md5计算完毕
resolve({md5, file})
console.log(
`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${
new Date().getTime() - time
} ms`
)
}
}
fileReader.onerror = function () {
this.error(`文件${file.name}读取出错,请检查该文件`)
file.cancel()
reject()
}
})
function loadNext() {
let start = currentChunk * chunkSize
let end = start + chunkSize >= file.size ? file.size : start + chunkSize
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
}
},
// md5计算完毕,开始上传
startUpload({md5, file}) {
file.uniqueIdentifier = md5
file.resume()
this.statusRemove(file.id)
},
给file的uniqueIdentifier 属性赋值后,请求中的identifier即是我们计算出来的MD5
checkChunkUploadedByResponse: function (chunk, message) {
let objMessage = JSON.parse(message);
if (objMessage.skipUpload) {
return true;
}
return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
},
// 注:skipUpload 和 uploaded 是和后台商议的字段,要按照后台实际返回的字段名来。
整理了下vue-simple-uploader 常见的问题:
vue-simple-uploader 常见问题整理