前言
开发一个文件管理系统模块时, 对文件上传进行的优化(极速上传, 分片断点续传功能)
流程概览
代码实现
// UploadFile
import React, { useRef } from 'react';
import { Upload, Button } from 'antd';
import tool from 'src/tool.js';
import api from 'src/App.api.js';
function UploadFile() {
const fileInfoRef = useRef(null);
const beforeUpload = (file) => {
// 文件上传前钩子
return tool.md5File(file).then(fileInfo => {
const { md5Key, fileSliceInfo } = fileInfo;
return api.checkMd5(md5Key).then(res => {
// 验证文件key值是否有对应的值
const { data } = res;
if (data.isDone) {
// 找到文件并且文件已经上传完成
return Promise.reject(res);
}
// 没有找到文件或上传被中断
fileInfoRef.current = {
...fileSliceInfo,
responseData: data,
}
return Promise.resolve();
})
})
}
const customRequest = () => {
// 覆盖Upload默认上传行为
const getApis = (list, existList) => {
return list.fillter(file => {
// 筛选出服务器端不存在的文件
return !existList.includes(file.key);
}).map((file, index) => {
const formData = new FormData();
formData.append('size', size); // 每次传输文件要带上文件总大小
formData.append('key', file.key);
formData.append('file', file.file);
return api.uploadFile(formData);
})
}
const { size, fileList, responseData } = fileInfoRef.current;
const existList = responseData.exist || [];
let apiList = getApis(fileList, existList);
Promise.all(apiList).then(res => {
console.log('上传成功');
fileInfoRef.current = null;
})
}
return <Upload
beforeUpload={beforeUpload}
customRequest={customRequest}
>
<Button>上传文件</Button>
</Upload>
}
// tool
import SparkMD5 from 'spark-md5';
/**
*
* @param {*} file 文件信息
* @param {*} size 文件分片大小
* @returns
* md5Key 文件加密后key值
* fileInfo 分片文件信息
*/
function md5File(file, size = 2 * 1024 * 1024) {
let fileList = [];
// 文件分片长度
const len = Math.ceil(file.size / size);
const blobSlice =
File.prototype.mozSlice ||
File.prototype.webkitSlice ||
File.prototype.slice;
let spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
let current = 0;
const loadNext = (size) => {
// 切片主要方法
let start = current * size;
let end = start + size >= file.size ? file.size : start + size;
let sliceFile = blobSlice.call(file, start, end);
// 将切片文件保存
fileList.push({
key: current,
file: sliceFile,
});
fileReader.readAsArrayBuffer(sliceFile);
};
return new Promise((resolve, reject) => {
try {
loadNext(size)
} catch (err) {
reject(err)
}
// 文件读取完毕之后的处理
fileReader.onload = (e) => {
try {
spark.append(e.target.result);
current += 1;
if (current < len) {
// 文件递归读取
loadNext(size)
} else {
// 文件全部读取完, 返回对应信息;
const res = {
md5Key: spark.end(), // 文件加密key值
fileInfo: {
size: file.size, //文件总大小
fileList, // 切片文件列表
}
}
resolve(res);
}
} catch (err) {
reject(err)
}
}
})
}