一、需求
可实现文件上传,包括但不限于图片、文档、视频、音频等类型,当然具体类型的支持依赖于服务端接口的实现,并且约定好参数。
前端要做的就是:
- 文件切片
- 发送上传请求给服务端
- 上传进度计算
服务端要做的就是:
- 类型检查
- 接收切片
- 合并所有切片
二、具体实现
1、类型文件mimeType.js
先导出一个类型文件,后面会用到。
// mimeType.js
export default {
//视频类
'avi': 'video/x-msvideo',
'dv': 'video/x-dv',
'mp4': 'video/mp4',
'mpeg': 'video/mpeg',
'mpg': 'video/mpeg',
'mov': 'video/quicktime',
'wm': 'video/x-ms-wmv',
'flv': 'video/x-flv',
'mkv': 'video/x-matroska',
'rmvb': 'video/vnd.rn-realmedia-vbr',
'wmv': 'video/x-ms-wmv',
'vob': 'video/x-ms-vob',
//音频类
'mp3': 'audio/mpeg',
'mid': 'audio/midi',
'ogg': 'audio/ogg',
'mp4a': 'audio/mp4',
'wav': 'audio/wav',
'wma': 'audio/x-ms-wma',
'ape': 'audio/ape',
'flac': 'audio/flac',
'aac': 'audio/aac'
}
2、文件切片
定义默认切片大小 chunkSize = 2 * 1024 * 1024
。
文件分割使用文件对象 File
的方法 File.prototype.slice
。
代码实现:
// 导入上面的mimeType文件
import mimeType from './mimeType';
_slice() {
let totalSize = this.file.size;
let start = 0;
let end = start + this.chunkSize;
let chunks = [];
let mimetype = 'text/pdf'; // 默认文件类型
if (this.file.type.includes('video') || this.file.type.includes('audio')) {
mimetype = this.file.type
}
// rmvb文件类型需要处理
if (this.file.name.endsWith('rmvb') || this.file.name.endsWith('RMVB')) {
mimetype = mimeType['rmvb']
}
// this.file.type不存在时,使用文件后缀名
if (!this.file.type) {
let arr = this.file.name.split('.');
let ext = arr[arr.length - 1].toLowerCase();
if (/(avi|dv|mp4|mpeg|mpg|mov|wm|vob|flv|mkv|rmvb|wmv|mp3|mid|ogg|mp4a|wav|wma|ape|flac|aac)/.test(ext)) {
mimetype = mimeType[ext];
}
}
while (start < totalSize) {
// 根据长度截取每次需要上传的数据
let chunk = this.file.slice(start, end, mimetype);
chunks.push(chunk);
start = end;
end = start + this.chunkSize;
}
return chunks
}
以上返回切片后的文件。
3、发送请求到服务端
使用 axios
,一个基于promise的网络请求库,作用于node.js和浏览器中。
根据约定好的请求参数,组织数据 FormData
。
代码实现
import axios from 'axios';
_sendRequest(chunkIndex, chunk, params) {
let formData = new FormData();
// TODO 根据和服务端同学约定的接口组织请求参数
formData.append('file', chunk, this.file.name); // 切片后的文件
formData.append('dname', this.file.name); // 文件名
formData.append('dzchunkindex', chunkIndex); // 切片索引
formData.append('dztotalchunkcount', this.chunks.length); // 所有切片长度
formData.append('dztotalfilesize', this.file.size); // 切片大小
if (params && typeof params === 'object') {
for (let k in params) {
formData.append(k, params[k]);
}
}
return axios({
method: this.method,
baseURL: this.baseURL,
url: this.url,
data: formData,
}).then(res => {
return res.data
}).catch(err => {
return err;
});
}
在以上代码中,在上传切片的同时,也会告诉后端当前上传切片的 chunkIndex
,后端接收后,记录该 chunkIndex
以便在合并时知道切片的顺序。
4、上传处理(上传进度&成功/失败)
onStart
上传开始回调onSuccess
上传成功回调onError
上传失败回调onProgress
上传进度回调onCancel
上传取消回调
async upload(params) {
// 上传开始的回调
this.onStart && this.onStart();
for (let i = 0; i < this.chunks.length; i++) {
if (this.cancelled) {
// 上传取消处理
this.onCancel && this.onCancel();
break;
}
console.log(`---------发送文件${this.file.name}第${i + 1}个切片-------`);
let res = await this._sendRequest(i, this.chunks[i], params);
if (res.success === 1) {
// TODO 约定的上传成功的返回参数
console.log(`---------发送文件${this.file.name}第${i + 1}个切片成功-------`);
// 上传进度计算
this.onProgress && this.onProgress((((i + 1) / this.chunks.length) * 100).toFixed(1));
if (i === this.chunks.length - 1) {
// 切片是最后一片,上传成功处理
this.onSuccess && this.onSuccess(res);
break;
}
} else {
// 上传失败处理
this.onError && this.onError();
break;
}
}
}
当所有切片上传完成(即 当前切片索引 = 所有切片长度-1
)后,不再发送上传请求,即通知后端把所有切片进行合并,最终完成整个上传流程。
这样,一个基本的文件切片上传就实现了。