后台管理系统中,牵扯到安装包管理。超级大的apk包和ipa包需要进行分片上传,上传前需要进行文件是否重复校验,文件md5计算等操作。
1. 文件分片
因为一般服务器一次可上传的文件大小有限。可以跟接口端讨论确认可传的分片大小。将几个G的文件按照固定的大小分成若干片。
js
// 文件分片
export const SIZE = 10 * 1024 * 1024; // 分片大小 10M
export const createFileChunk = (file, size=SIZE) => {
const fileChunkList = [];
let cur = 0;
while(cur < file.size) {
fileChunkList.push({
file: file.slice(cur, cur + size)
});
cur += size;
}
return fileChunkList;
};
2. 计算文件的MD5
选择sparkMD5
的三方库进行hash
值的计算。单个文件使用worker好似不大必要。但是后续如果扩展到文件夹上传,文件夹中如果包含非常多的大小文件,那么worker就会变得非常必要了。
js
import WorkerPath from "./1.worker.js";
// 计算大文件HASH
export const calculate = (fileChunkList) => {
return new Promise((resolve) => {
const worker = new WorkerPath();
worker.postMessage({ fileChunkList });
worker.onmessage = (e) => {
const { hash } = e.data;
if (hash) {
resolve(hash);
}
};
});
};
然而解析worker.js需要单独的loader来处理。
首先,需要安装 npm install worker-loader -D
其次需要在webpack中进行loader的配置
js
// 在rules中配置
{
// 匹配 *.worker.js
test: /\.worker\.js$/,
use: {
loader: "worker-loader",
},
}
然后,在worker中进行md5的计算
js
// 1.worker.js
importScripts("../static/spark-md5.min.js"); //引入本地的三方库
onmessage = function(event) {
const { fileChunkList } = event.data;
const spark = new SparkMD5.ArrayBuffer();
let count = 0;
const loadNext = (index) => {
const reader = new FileReader();
reader.readAsArrayBuffer(fileChunkList[index].file);
reader.onload = (e) => {
count++;
spark.append(e.target.result);
if (count === fileChunkList.length) {
this.postMessage({
hash: spark.end()
});
this.close();
}
loadNext(count);
};
};
loadNext(0);
};
3. 文件检查
调用接口的check方法检查该文件是否已经上传过。如果上传过则不再上传。如果未上传过则开始上传。
4. 文件上传
根据1,2获取到的分片和md5值,进行上传。所有的分片同时上传,浏览器自动限制6-8个请求。剩余的请求会pending。
js
// 所有的分片同时上传,浏览器自动限制6-8个请求
const promises = fileChunkList.forEach(({file: chunkFile}, index) =>{
const passedData = {
chunk: index + 1,
file: chunkFile,
name:formData.filename,
md5: hash,
totalChunk: fileChunkList.length,
position: index * SIZE,
totalSize: formData.file.size,
chunked: true,
};
// uploadPackFile是自己封装的上传方法
const promiseTemp = uploadPackFile("/upload", passedData,function() {
progressRef.current = progressRef.current + 1; // 记录上传进度
});
});
// 等待所有的请求结束
Promise.all(promises).then(result => {
// 进行上传结束后的一些操作
})
文件上传进度的加载状态
文件上传各个阶段都是需要等待的过程,比如"上传中..."。后面的三个点可以动态展示。
代码如下:
html
<span>上传中<span className='dot'>...</span></span>
样式如下:
css
span.dot {
display: inline-block;
height: 1em;
line-height: 1;
text-align: left;
vertical-align: -.25em;
overflow: hidden;
text-indent: 0;
}
span.dot::before {
display: block;
content: '...\A..\A.';
white-space: pre-wrap;
animation: dot 1s infinite step-start both;
}
@keyframes dot {
33% { transform: translateY(-2em); }
66% { transform: translateY(-1em); }
}