parallel-file-uploader
Version:
高性能并行文件上传工具,支持大文件分片上传、断点续传、Web Worker多线程处理
193 lines • 5.64 kB
JavaScript
import { v4 as uuid } from 'uuid';
import { UploadStepEnum, ErrorType, UploaderError } from '../type';
/**
* 文件管理器
* 负责文件队列管理、文件验证、状态管理等
*/
export class FileManager {
constructor(options = {}) {
this.fileQueue = [];
this.activeFiles = new Map();
this.maxFileSize = options.maxFileSize;
this.allowedFileTypes = options.allowedFileTypes;
}
/**
* 添加文件到队列
*/
addFiles(files) {
const fileArray = Array.from(files);
const addedFiles = [];
for (const file of fileArray) {
try {
this.validateFile(file);
const fileId = uuid();
const fileInfo = {
fileId,
fileName: file.name,
fileSize: file.size,
uploadedSize: 0,
progress: 0,
status: UploadStepEnum.beforeUpload,
file,
mimeType: file.type,
lastUpdated: Date.now(),
};
this.fileQueue.push(fileInfo);
addedFiles.push(fileInfo);
}
catch (error) {
// 验证失败的文件会抛出错误,由调用方处理
throw error;
}
}
return addedFiles;
}
/**
* 验证文件
*/
validateFile(file) {
// 验证文件类型
if (this.allowedFileTypes && this.allowedFileTypes.length > 0) {
const fileType = file.type || this.getFileExtension(file.name);
if (!this.isFileTypeAllowed(fileType)) {
throw new UploaderError(`File type not allowed: ${fileType}`, ErrorType.FILE_TYPE_NOT_ALLOWED);
}
}
// 验证文件大小
if (this.maxFileSize && file.size > this.maxFileSize) {
throw new UploaderError(`File size exceeds limit: ${file.size} > ${this.maxFileSize}`, ErrorType.FILE_TOO_LARGE);
}
}
/**
* 从队列中获取下一个文件
*/
getNextFile() {
return this.fileQueue.shift();
}
/**
* 添加文件到活动列表
*/
addToActive(fileInfo) {
this.activeFiles.set(fileInfo.fileId, fileInfo);
}
/**
* 从活动列表中移除文件
*/
removeFromActive(fileId) {
this.activeFiles.delete(fileId);
}
/**
* 获取活动文件
*/
getActiveFile(fileId) {
return this.activeFiles.get(fileId);
}
/**
* 获取所有活动文件
*/
getAllActiveFiles() {
return Array.from(this.activeFiles.values());
}
/**
* 更新文件状态
*/
updateFileStatus(fileId, status) {
const fileInfo = this.activeFiles.get(fileId);
if (fileInfo) {
fileInfo.status = status;
fileInfo.lastUpdated = Date.now();
}
}
/**
* 更新文件进度
*/
updateFileProgress(fileId, uploadedSize) {
const fileInfo = this.activeFiles.get(fileId);
if (fileInfo) {
fileInfo.uploadedSize = uploadedSize;
fileInfo.progress = Math.round((uploadedSize / fileInfo.fileSize) * 100);
fileInfo.lastUpdated = Date.now();
}
}
/**
* 获取队列长度
*/
getQueueLength() {
return this.fileQueue.length;
}
/**
* 获取活动文件数量
*/
getActiveCount() {
return this.activeFiles.size;
}
/**
* 清空队列
*/
clearQueue() {
this.fileQueue = [];
}
/**
* 清空活动文件
*/
clearActive() {
this.activeFiles.clear();
}
/**
* 获取统计信息
*/
getStats() {
const queued = this.fileQueue.length;
let active = 0;
let completed = 0;
let failed = 0;
let paused = 0;
for (const fileInfo of this.activeFiles.values()) {
switch (fileInfo.status) {
case UploadStepEnum.upload:
active++;
break;
case UploadStepEnum.complete:
completed++;
break;
case UploadStepEnum.error:
failed++;
break;
case UploadStepEnum.pause:
paused++;
break;
}
}
return { queued, active, completed, failed, paused };
}
/**
* 获取文件扩展名
*/
getFileExtension(filename) {
const lastDotIndex = filename.lastIndexOf('.');
return lastDotIndex !== -1 ? filename.substring(lastDotIndex + 1).toLowerCase() : '';
}
/**
* 检查文件类型是否允许
*/
isFileTypeAllowed(fileType) {
if (!this.allowedFileTypes || this.allowedFileTypes.length === 0) {
return true;
}
// 处理MIME类型
if (fileType.includes('/')) {
return this.allowedFileTypes.some((type) => {
// 完全匹配 (image/png)
if (type === fileType)
return true;
// 通配符匹配 (image/*)
if (type.endsWith('/*') && fileType.startsWith(type.split('/*')[0]))
return true;
return false;
});
}
// 处理扩展名
return (this.allowedFileTypes.includes(`.${fileType}`) || this.allowedFileTypes.includes(fileType));
}
}
//# sourceMappingURL=FileManager.js.map