跟踪显示上传进度
要想发出一个带有进度事件的请求,你可以创建一个 HttpRequest
实例,并把 reportProgress
选项设置为 true 来启用对进度事件的跟踪。
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true
});
具体操作可以在官网看到angular.cn/guide/http#…
生成文件hash值
[SparkMD5] www.npmjs.com/package/spa… 计算文件hash值
import * as SparkMD5 from 'spark-md5';
/**
* Generate file hash
*
* @param fileChunkList 分片文件列表
* @param callback 回调函数
*/
calculateHash(fileChunkList, callback) {
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) {
const hash = spark.end();
callback(hash); //回调函数
} else {
loadNext(count);
}
};
};
loadNext(0);
}
完整的文件上传代码:
import * as SparkMD5 from 'spark-md5';
const SIZE = 10 * 1024 * 1024; // file slice size
const concurrentNumber = 4 ;// Number of concurrent requests
private reUploadOptions = new Map<string, any>();
// 文件分片上传
// option 配置参数
//
sliceUpload(option, file, data) {
if (!file) {
return;
}
const fileChunkList = this.createFileChunk(file);
const key = data[option.attrKey];// 一般是上传的对象的id
const options = {fileChunkList, file, data, key};
try{
this.calculateHash(options, this.mergeRequest);
} catch(e){
// Don't work on the browser that is not Google Chrome
// can not find file
this.handleFileError(options, file.size);
return;
}
// 成功的上传的index集合
options.successIndexes = [];
this.handleUpload(options);
}
handleUpload(options){
const fileChunks = this.chunkFile(options);
this.concurrentUpload(options, fileChunks, 0);
}
// 根据concurrentNumber 区分一次上传的请求数 并预处理一下fileChunkList
private chunkFile(options) {
const fileChunkList = [];
// Exclude already uploaded fileChunk
_.forEach(options.fileChunkList,(fileChunk, index) => {
if (!_.includes(options.successIndexes, index + 1)) {
const data ={
index,
chunk: fileChunk.file,
size: fileChunk.file.size
};
fileChunkList.push(data);
}
});
return _.chunk(fileChunkList, concurrentNumber) ;
}
/**
* Generate file slices
*
* @param file
* @param size
*/
private 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;
}
/**
* Generate file hash
*
* @param options
* @param callback
*/
private calculateHash(options, callback) {
const spark = new SparkMD5.ArrayBuffer();
let count = 0;
const loadNext = index => {
const reader = new FileReader();
reader.readAsArrayBuffer(options.fileChunkList[index].file);
reader.onload = e => {
count++;
spark.append(e.target.result);
if (count === options.fileChunkList.length) {
options.data.hash = spark.end();
callback(options);
} else {
loadNext(count);
}
};
};
loadNext(0);
}
// Concurrent request
// Sending all requests together will block next other request.
private concurrentUpload(options, fileChunks, index) {
const uploadRequests = [];
// preprocess request body
_.forEach(fileChunks[index], data => {
const url = this.utilService.constructUrlWithOptions(options.uploadUrl, {...data, ...options.data});
const formData = new FormData();
formData.append(options.fileAttr, data.chunk);
uploadRequests.push(this.httpService.post(url,formData).pipe(
map(()=> data.index),
catchError(() => of(null))
));
});
let count = 0;
const uploadSubscription = merge(...uploadRequests).subscribe( (res: number) => {
if(res !== null) {
count++;
options.successIndexes.push(res + 1);
//计算上传进度
const progress = Math.round(options.successIndexes.length / options.fileChunkList.length * 100);
... //显示进度
} else {
//上传失败
if(!options.file.size) { // 本地文件被修改报错
// Don't work on the browser that is not Google Chrome
this.handleFileError(options, options.file.size);
} else {
uploadSubscription.unsubscribe();
// Handle error
this.reUploadOptions.set(options.key, options); // 保存下失败的数据,方便文件续传
}
}
// All success
if (options.successIndexes.length === options.fileChunkList.length) {
this.mergeRequest(options);
} else if (count === fileChunks[index].length) {
uploadSubscription.unsubscribe();
this.concurrentUpload(options, fileChunks, index+1);
}
});
}
/**
* Notify the server to merge slices
*
* @param options
*/
private mergeRequest(options) {
// 等所有文件都上传成功后并且hash计算完成,通知服务器
if (options.successIndexes.length !== options.fileChunkList.length
|| !options.data.hash) {
return;
}
this.mergeFile(options);
}
文件续传
resumeBreakpoint(key) {
const options = this.reUploadOptions.get(key);
if (options) {
if (!options.data.hash) {
this.calculateHash(options, this.mergeRequest);
}
const progress = Math.round(options.successIndexes.length / options.fileChunkList.length * 100);
... //显示进度
this.handleUpload(options);
}
}
注意
js上传文件,不能刷新浏览器或退出浏览器。 最好在上传时给用户提示。