注意:本文仅介绍前端使用方法,后端实现方式请查看我的另一篇文章:* Spring Boot 实现大文件分片上传、断点续传及秒传 。
vue-simple-uploader 简介
vue-simple-uploader
是基于 simple-uploader.js
封装的 Vue 上传组件。
其 GitHub 地址:simple-uploader/vue-uploader,
文档:https://github.com/simple-uploader/vue-uploader/blob/master/README_zh-CN.md
simple-uploader.js 文档:https://github.com/simple-uploader/Uploader/blob/develop/README_zh-CN.md
特性:
- 支持文件、多文件、文件夹上传
- 支持拖拽文件、文件夹上传
- 统一对待文件和文件夹,方便操作管理
- 可暂停、继续上传
- 错误处理
- 支持“快传”,通过文件判断服务端是否已存在从而实现“快传”
- 上传队列管理,支持最大并发上传
- 分块上传
- 支持进度、预估剩余时间、出错自动重试、重传等操作
效果图:
文件上传流程
使用
- 安装
vue-simple-uploader
:
npm install vue-simple-uploader --save
- 在
main.js
中引用:
import uploader from 'vue-simple-uploader'
Vue.use(uploader)
- 在页面中使用
<template>
<div class="container">
<uploader
ref="uploader"
:options="options"
:file-status-text="fileStatusText"
@file-added="onFileAdded"
@file-success="onFileSuccess"
@file-error="onFileError"
@file-progress="onFileProgress"
class="uploader-example">
<uploader-unsupport></uploader-unsupport>
<uploader-drop>
<p>拖动文件到这里上传</p>
<uploader-btn>选择文件</uploader-btn>
<uploader-btn :attrs="attrs">选择图片</uploader-btn>
<uploader-btn :directory="true">选择文件夹</uploader-btn>
</uploader-drop>
<uploader-list></uploader-list>
</uploader>
</div>
</template>
vue-simple-uploader
的options
配置
// 分片大小,10MB
const CHUNK_SIZE = 10 * 1024 * 1024
export default {
data () {
return {
options: {
// 上传地址
target: 'http://127.0.0.1:8025/upload',
// 是否开启服务器分片校验。默认为 true
testChunks: true,
// 分片大小
chunkSize: CHUNK_SIZE,
// 并发上传数,默认为 3
simultaneousUploads: 3,
// 分片校验函数,判断分片是否上传,秒传和断点续传基于此方法
checkChunkUploadedByResponse: (chunk, message) => {
let messageObj = JSON.parse(message)
let dataObj = messageObj.data
if (dataObj.uploaded !== undefined) {
return dataObj.uploaded
}
// 判断文件或分片是否已上传,已上传返回 true,这里的 uploadedChunks 是后台返回
return (dataObj.uploadedChunks || []).indexOf(chunk.offset + 1) >= 0
}
},
attrs: {
accept: 'image/*'
},
// 修改上传状态
fileStatusTextObj: {
success: '上传成功',
error: '上传错误',
uploading: '正在上传',
paused: '停止上传',
waiting: '等待中'
},
// 上传并发数
simultaneousUploads: 3,
uploadIdInfo: null,
uploadFileList: [],
fileChunkList: []
}
}
}
- 添加
vue-simple-uploader
相关的方法。
methods: {
// 用于文件校验,忽略该文件则返回 false,文件不会添加到上传列表中
onFileAdded(file, event) {
// 1. todo 判断文件类型是否允许上传
// 2. 计算文件 MD5 并请求后台判断是否已上传,是则取消上传
console.log('校验MD5')
this.getFileMD5(file, md5 => {
if (md5 != '') {
// 修改文件唯一标识
file.uniqueIdentifier = md5
// 恢复上传
file.resume()
}
})
},
// 上传成功事件
onFileSuccess(rootFile, file, response, chunk) {
alert('上传成功')
},
// 上传过程出错处理
onFileError(rootFile, file, message, chunk) {
console.log('上传出错:' + message)
},
// 文件上传进度
onFileProgress(rootFile, file, chunk) {
console.log(`当前进度:${Math.ceil(file._prevProgress * 100)}%`)
},
fileStatusText(status, response) {
if (status === 'md5') {
return '校验MD5'
} else {
return this.fileStatusTextObj[status]
}
},
saveFileUploadId(data) {
localStorage.setItem(FILE_UPLOAD_ID_KEY, data)
}
}
分片上传
vue-simple-uploader
自动将文件进行分片,通过配置 options
中的 chunkSize
来指定分片大小。
如果选择中配置了 testChunks: true
,则在每次上传前,vue-simple-uploader
会先发送一个 GET
请求,来判断文件或分片是否已经上传,如下图所示:
基于此,我们可以用于实现断点续传和秒传。
文件MD5计算
由于断点续传和秒传的基础是文件的 MD5 值,作为文件的唯一标识,通过文件 MD5 值查询后台判断是秒传还是断点续传。
我使用的加密工具是 spark-md5
,需要先安装:
npm install spark-md5 --save
在页面上使用:
import SparkMD5 from 'spark-md5'
// 分片大小,10MB
const CHUNK_SIZE = 10 * 1024 * 1024
export default {
methods: {
getFileMD5(file, callback) {
let spark = new SparkMD5.ArrayBuffer()
let fileReader = new FileReader()
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
let currentChunk = 0
let chunks = Math.ceil(file.size / CHUNK_SIZE)
let startTime = new Date().getTime()
file.pause()
loadNext()
fileReader.onload = function(e) {
spark.append(e.target.result)
if (currentChunk < chunks) {
currentChunk++
loadNext()
} else {
let md5 = spark.end()
console.log(`MD5计算完毕:${md5},耗时:${new Date().getTime() - startTime} ms.`)
callback(md5)
}
}
fileReader.onerror = function() {
this.$message.error('文件读取错误')
file.cancel()
}
function loadNext() {
const start = currentChunk * CHUNK_SIZE
const end = ((start + CHUNK_SIZE) >= file.size) ? file.size : start + CHUNK_SIZE
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
}
}
}
}
由于需要提交文件 MD5 ,我们可以直接将文件 MD5 赋值给 File 的 uniqueIdentifier
属性,修改文件的唯一标识。
这个步骤的操作可以写在 onFileAdded
方法里:
onFileAdded(file, event) {
// 计算文件 MD5
this.getFileMD5(file, md5 => {
if (md5 != '') {
// 修改文件唯一标识
file.uniqueIdentifier = md5
// 恢复上传
file.resume()
}
})
}
断点续传和秒传
后台服务根据前端传的文件 MD5 查询判断是断点续传或秒传:
后台服务查询该文件已上传,则直接返回秒传信息及文件 URL;
后台服务查询该文件已上传分片,则返回已上传的分片信息,前端根据返回的分片信息来确定哪些分片继续上传。
这个步骤在 vue-simple-uploader
配置 options
的 checkChunkUploadedByResponse
方法实现:
options: {
target: 'http://127.0.0.1:8086/api/file/upload',
testChunks: true,
chunkSize: CHUNK_SIZE,
// 判断分片是否上传,秒传和断点续传基于此方法
checkChunkUploadedByResponse: (chunk, message) => {
let messageObj = JSON.parse(message)
let dataObj = messageObj.data
// 判断文件或分片是否已上传,已上传返回 true
if (dataObj.uploaded !== undefined) {
return dataObj.uploaded
}
// 这里的 uploadedChunks 是后台返回的已上传分片列表,自己根据情况修改
return (dataObj.uploadedChunks || []).indexOf(chunk.offset + 1) >= 0
}
}
后台返回的数据格式为:
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"uploadedChunks": [
1,
2,
4,
5,
6
]
}
}
总结
vue-simple-uploader
的使用还是很简单的,它帮我们实现了很多操作,比如文件分片上传、支持秒传、上传进度、出错自动重试等。我们只需配置好参数,协调好后端接口,就可以很快的解决上传问题。
博客中仅介绍了 vue-simple-uploader
最基本的用法,实际开发中还要考虑文件类型白名单、上传接口的 Token 校验、重试机制等问题,请勿直接用于生产项目中。更多的使用方法请查看官方文档,结合自身需求做相应的配置和修改。
后端实现方式请查看我的另一篇文章:Spring Boot 实现大文件分片上传、断点续传及秒传
本项目源码:lanweihong/vue-simple-uploader-sample
待改进
- 实现多文件上传,考虑使用
web-worker
和requestIdleCallback
来实现计算大文件 MD5 - 封装成独立组件,方便使用
由于最近没有时间,就先到这吧,以后有时间再继续完善此上传组件。