parallel-file-uploader
Version:
高性能并行文件上传工具,支持大文件分片上传、断点续传、Web Worker多线程处理
888 lines • 34.8 kB
JavaScript
import { UploadStepEnum, ErrorType, UploaderError, } from './type';
import SparkMD5 from 'spark-md5';
import { FileManager, ChunkManager, SpeedLimiter, PerformanceMonitor, QueuePersistence, WorkerManager } from './modules';
/**
* 🔧 格式化文件大小
*/
function formatFileSize(bytes) {
if (bytes === 0)
return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* 🔧 格式化上传速度
*/
function formatSpeed(bytesPerSecond) {
return formatFileSize(bytesPerSecond) + '/s';
}
/**
* 并行文件上传器类
*
* 主要特性:
* - 支持多文件并发上传
* - 大文件自动分片上传
* - 使用Web Worker实现多线程处理
* - 支持断点续传
* - 自动失败重试
* - 完整的事件回调系统
* - 速度限制
* - 性能监控
* - 队列持久化
* - 🔧 增强的错误处理和兼容性
*
* @example
* ```typescript
* const uploader = new ParallelFileUploader({
* maxConcurrentFiles: 3,
* chunkSize: 5 * 1024 * 1024, // 5MB
* enableSpeedLimit: true,
* maxUploadSpeed: 1024 * 1024, // 1MB/s
* enablePerformanceMonitor: true,
* enableQueuePersistence: true,
* allowedFileTypes: ['*'], // 🔧 支持通配符
* debugMode: true, // 🔧 启用调试模式
* onFileProgress: (fileInfo) => {
* console.log(`${fileInfo.fileName}: ${fileInfo.progress}%`)
* }
* })
*
* uploader.addFiles(fileList)
* ```
*/
export class ParallelFileUploader {
/**
* 创建并行文件上传器实例
* @param options 配置选项
*/
constructor(options = {}) {
this.uploadPartUrl = '';
// 🔧 调试模式
this.debugMode = false;
// 🔧 启用调试模式
this.debugMode = options.debugMode || false;
// 设置基本配置
this.maxConcurrentFiles = options.maxConcurrentFiles || 3;
this.maxConcurrentChunks = options.maxConcurrentChunks || 3;
this.maxRetries = options.maxRetries || 3;
if (options.uploadPartUrl) {
this.uploadPartUrl = options.uploadPartUrl;
}
// 🔧 配置验证和优化建议
this.validateAndOptimizeConfig(options);
// 初始化模块
this.fileManager = new FileManager({
maxFileSize: options.maxFileSize,
allowedFileTypes: options.allowedFileTypes,
});
this.chunkManager = new ChunkManager(options.chunkSize || 1024 * 1024 * 5);
this.speedLimiter = new SpeedLimiter(options.maxUploadSpeed || 0, options.enableSpeedLimit || false);
this.performanceMonitor = new PerformanceMonitor(options.enablePerformanceMonitor || false);
this.queuePersistence = new QueuePersistence(options.enableQueuePersistence || false, options.persistenceKey);
this.workerManager = new WorkerManager();
this.workerManager.setMessageHandler(this.handleWorkerMessage.bind(this));
// 设置回调
this.onFileAdded = options.onFileAdded;
this.onFileProgress = options.onFileProgress;
this.onFileSuccess = options.onFileSuccess;
this.onFileError = options.onFileError;
this.onFileComplete = options.onFileComplete;
this.onAllComplete = options.onAllComplete;
this.onFileRejected = options.onFileRejected;
this.onPerformanceUpdate = options.onPerformanceUpdate;
// 设置服务器交互回调
this.sendFileInfoToServer = options.sendFileInfoToServer;
this.sendFilePartToServer = options.sendFilePartToServer;
this.sendFileCompleteToServer = options.sendFileCompleteToServer;
this.getFilePartsFromServer = options.getFilePartsFromServer;
this.sendPauseToServer = options.sendPauseToServer;
// 启动性能监控定时器
if (this.performanceMonitor.isEnabled()) {
this.startPerformanceMonitoring();
}
// 加载持久化队列
this.loadPersistedQueue();
// 🔧 输出初始化完成信息
if (this.debugMode) {
this.logInitializationInfo();
}
}
/**
* 添加文件到上传队列
*/
addFiles(files) {
console.log(`📂 添加 ${files.length} 个文件到队列`);
try {
const addedFiles = this.fileManager.addFiles(files);
// 🔧 输出添加成功的文件信息
if (this.debugMode && addedFiles.length > 0) {
console.log('✅ 成功添加文件:', addedFiles.map(f => ({
name: f.fileName,
size: formatFileSize(f.fileSize),
type: f.mimeType || '未知'
})));
}
// 触发文件添加回调
for (const fileInfo of addedFiles) {
if (this.onFileAdded) {
this.onFileAdded(fileInfo);
}
}
// 保存队列状态
this.saveQueueState();
// 开始处理队列
this.processFileQueue();
}
catch (error) {
// 🔧 增强的错误处理
console.error('❌ 添加文件失败:', error);
if (error instanceof UploaderError && this.onFileRejected) {
// 尝试从错误信息中提取文件信息
const errorMessage = error.message || '未知错误';
// 创建一个虚拟文件对象用于回调
const dummyFile = new File([''], 'unknown');
// 如果可能,尝试从 files 中找到实际的文件
let rejectedFile = dummyFile;
if (files && files.length > 0) {
rejectedFile = Array.from(files)[0]; // 假设是第一个文件出错
}
this.onFileRejected(rejectedFile, errorMessage);
}
else {
// 如果没有 onFileRejected 回调,输出详细错误
console.error('❌ 文件验证失败,但未配置 onFileRejected 回调。错误详情:', {
error: error instanceof Error ? error.message : String(error),
files: Array.from(files).map(f => ({
name: f.name,
size: formatFileSize(f.size),
type: f.type || '未知'
}))
});
}
}
}
/**
* 处理文件队列
*/
async processFileQueue() {
while (this.fileManager.getActiveCount() < this.maxConcurrentFiles &&
this.fileManager.getQueueLength() > 0) {
const fileInfo = this.fileManager.getNextFile();
this.fileManager.addToActive(fileInfo);
try {
await this.initFileUpload(fileInfo);
this.uploadFile(fileInfo);
}
catch (error) {
this.handleFileError(fileInfo, error instanceof Error ? error : new Error(String(error)));
}
}
}
/**
* 初始化文件上传
*/
async initFileUpload(fileInfo) {
fileInfo.status = UploadStepEnum.beforeUpload;
// 🔧 输出初始化信息
if (this.debugMode) {
console.log(`🔄 初始化文件上传: ${fileInfo.fileName} (${formatFileSize(fileInfo.fileSize)})`);
}
// 准备分片队列
this.chunkManager.prepareChunkQueue(fileInfo);
if (this.sendFileInfoToServer) {
const result = await this.sendFileInfoToServer(fileInfo);
if (!result.isSuccess) {
throw new UploaderError('Failed to initialize file upload', ErrorType.FILE_INITIALIZATION_FAILED, fileInfo);
}
// 检查文件是否已存在(秒传)
if (result.data && result.data.skipUpload) {
fileInfo.status = UploadStepEnum.complete;
if (this.onFileSuccess) {
this.onFileSuccess({ fileInfo, data: result.data });
}
if (this.onFileComplete) {
this.onFileComplete({ fileInfo, data: result.data });
}
this.cleanupFile(fileInfo.fileId);
return;
}
// 断点续传
if (this.getFilePartsFromServer) {
try {
const partsResult = await this.getFilePartsFromServer(fileInfo);
if (partsResult.isSuccess && partsResult.data && partsResult.data.length > 0) {
const hasAllSameEtag = new Set(partsResult.data.map((part) => part.etag)).size === 1 &&
partsResult.data.length > 1;
if (!hasAllSameEtag) {
this.chunkManager.resumeFromExistingParts(fileInfo, partsResult.data);
}
}
}
catch (error) {
console.warn('⚠️ 获取已有分片失败:', error);
}
}
}
fileInfo.status = UploadStepEnum.upload;
}
/**
* 开始上传文件
*/
uploadFile(fileInfo) {
if (this.debugMode) {
console.log(`📤 开始上传文件: ${fileInfo.fileName}`);
}
this.processChunkQueue(fileInfo.fileId);
}
/**
* 处理分片队列
*/
async processChunkQueue(fileId) {
const fileInfo = this.fileManager.getActiveFile(fileId);
if (!fileInfo || fileInfo.status !== UploadStepEnum.upload) {
return;
}
// 检查是否所有分片都已完成
if (this.chunkManager.isAllChunksCompleted(fileId)) {
await this.completeFileUpload(fileInfo);
return;
}
// 计算可用的上传槽位
const pendingCount = this.chunkManager.getPendingChunkCount(fileId);
const availableSlots = this.maxConcurrentChunks - pendingCount;
if (availableSlots <= 0) {
return;
}
// 启动新的分片上传
for (let i = 0; i < availableSlots && this.chunkManager.getRemainingChunkCount(fileId) > 0; i++) {
const chunk = this.chunkManager.getNextChunk(fileId);
console.log(`Starting upload for chunk #${chunk.partNumber}`);
this.chunkManager.addToPending(fileId, chunk.partNumber);
// 使用Worker或直接上传
if (this.workerManager.hasAvailableWorker()) {
this.uploadChunkWithWorker(fileInfo, chunk);
}
else {
this.uploadChunkDirect(fileInfo, chunk);
}
}
}
/**
* 使用Web Worker上传分片
*/
uploadChunkWithWorker(fileInfo, chunkInfo) {
const { fileId, file } = fileInfo;
const { start, end, partNumber } = chunkInfo;
const worker = this.workerManager.getAvailableWorker();
if (!worker) {
this.uploadChunkDirect(fileInfo, chunkInfo);
return;
}
this.workerManager.markWorkerBusy(worker);
// 准备分片数据
const chunk = file.slice(start, end);
const chunkFile = new File([chunk], '');
if (!this.sendFilePartToServer) {
console.error('No sendFilePartToServer function provided');
this.handleChunkError(fileInfo, chunkInfo, new UploaderError('No sendFilePartToServer function provided', ErrorType.CHUNK_UPLOAD_FAILED, fileInfo));
this.workerManager.markWorkerIdle(worker);
return;
}
// 将Blob转换为ArrayBuffer后再传输到Worker
const reader = new FileReader();
reader.onload = (event) => {
if (event.target?.result) {
const arrayBuffer = event.target.result;
this.workerManager.postMessage(worker, {
type: 'data',
fileId,
chunkInfo: {
file: chunkFile,
start: chunkInfo.start,
end: chunkInfo.end,
partNumber: chunkInfo.partNumber,
partSize: chunkInfo.partSize,
},
}, [arrayBuffer]);
}
else {
this.handleChunkError(fileInfo, chunkInfo, new UploaderError('Failed to read file chunk', ErrorType.CHUNK_UPLOAD_FAILED, fileInfo));
this.workerManager.markWorkerIdle(worker);
}
};
reader.onerror = () => {
this.handleChunkError(fileInfo, chunkInfo, new UploaderError('Failed to read file chunk', ErrorType.CHUNK_UPLOAD_FAILED, fileInfo));
this.workerManager.markWorkerIdle(worker);
};
reader.readAsArrayBuffer(chunk);
}
/**
* 直接在主线程上传分片
*/
async uploadChunkDirect(fileInfo, chunkInfo) {
const { partNumber, partSize } = chunkInfo;
try {
// 速度限制
if (this.speedLimiter.isEnabled() && partSize) {
const delay = await this.speedLimiter.requestBytes(partSize);
if (delay > 0) {
await this.speedLimiter.wait(delay);
}
}
// 验证分片大小
if (!partSize || partSize <= 0) {
throw new UploaderError(`Invalid chunk size: ${partSize} bytes, chunk #${partNumber}`, ErrorType.CHUNK_UPLOAD_FAILED, fileInfo);
}
if (this.sendFilePartToServer) {
const result = await this.sendFilePartToServer(fileInfo, chunkInfo);
if (result.isSuccess) {
// 记录传输字节数
this.performanceMonitor.recordBytesTransferred(partSize);
// 保存etag信息
if (result.data && result.data.etag) {
const fileUploadInfo = fileInfo.uploadInfo || {};
const parts = fileUploadInfo.parts || [];
parts.push({
etag: result.data.etag,
partNumber: partNumber,
partSize: partSize,
});
fileUploadInfo.parts = parts;
fileInfo.uploadInfo = fileUploadInfo;
}
this.handleChunkSuccess(fileInfo, chunkInfo);
}
else {
throw new UploaderError(`Upload chunk #${partNumber} failed: ${result.message || 'Unknown error'}`, ErrorType.CHUNK_UPLOAD_FAILED, fileInfo);
}
}
else {
// 模拟上传成功
setTimeout(() => {
this.handleChunkSuccess(fileInfo, chunkInfo);
}, 500);
}
}
catch (error) {
this.handleChunkError(fileInfo, chunkInfo, error instanceof UploaderError ? error : new UploaderError(String(error), ErrorType.CHUNK_UPLOAD_FAILED, fileInfo));
}
}
/**
* 处理Worker消息
*/
handleWorkerMessage(event) {
const message = event.data;
const worker = event.target;
if (message.type === 'response' && message.fileId && message.chunkInfo) {
const fileInfo = this.fileManager.getActiveFile(message.fileId);
if (fileInfo && message.processed) {
this.sendProcessedChunk(fileInfo, message.chunkInfo);
}
}
else if (message.type === 'error' && message.fileId && message.chunkInfo) {
const fileInfo = this.fileManager.getActiveFile(message.fileId);
if (fileInfo) {
this.handleChunkError(fileInfo, message.chunkInfo, new UploaderError(message.error || 'Unknown error', ErrorType.CHUNK_UPLOAD_FAILED, fileInfo));
}
}
}
/**
* 发送Worker处理过的数据
*/
async sendProcessedChunk(fileInfo, chunkInfo) {
try {
if (this.sendFilePartToServer) {
// 速度限制
if (this.speedLimiter.isEnabled() && chunkInfo.partSize) {
const delay = await this.speedLimiter.requestBytes(chunkInfo.partSize);
if (delay > 0) {
await this.speedLimiter.wait(delay);
}
}
const result = await this.sendFilePartToServer(fileInfo, chunkInfo);
if (result.isSuccess) {
// 记录传输字节数
if (chunkInfo.partSize) {
this.performanceMonitor.recordBytesTransferred(chunkInfo.partSize);
}
// 保存etag信息
if (result.data && result.data.etag) {
const fileUploadInfo = fileInfo.uploadInfo || {};
const parts = fileUploadInfo.parts || [];
parts.push({
etag: result.data.etag,
partNumber: chunkInfo.partNumber,
partSize: chunkInfo.partSize,
});
fileUploadInfo.parts = parts;
fileInfo.uploadInfo = fileUploadInfo;
}
this.handleChunkSuccess(fileInfo, chunkInfo);
}
else {
throw new UploaderError(`Upload chunk #${chunkInfo.partNumber} failed: ${result.message || 'Unknown error'}`, ErrorType.CHUNK_UPLOAD_FAILED, fileInfo);
}
}
else {
throw new UploaderError('No sendFilePartToServer function provided', ErrorType.CHUNK_UPLOAD_FAILED, fileInfo);
}
}
catch (error) {
this.handleChunkError(fileInfo, chunkInfo, error instanceof UploaderError ? error : new UploaderError(String(error), ErrorType.CHUNK_UPLOAD_FAILED, fileInfo));
}
}
/**
* 处理分片上传成功
*/
handleChunkSuccess(fileInfo, chunkInfo) {
const { fileId } = fileInfo;
const { partNumber } = chunkInfo;
// 更新分片状态
this.chunkManager.markChunkCompleted(fileId, partNumber);
// 更新文件进度
this.updateFileProgress(fileInfo);
// 保存状态
this.saveQueueState();
// 继续处理队列
this.processChunkQueue(fileId);
}
/**
* 处理分片上传错误
*/
handleChunkError(fileInfo, chunkInfo, error) {
const { fileId } = fileInfo;
const { partNumber } = chunkInfo;
// 从待处理集合中移除
this.chunkManager.removeFromPending(fileId, partNumber);
// 检查是否需要重试
if ((chunkInfo.retryCount || 0) < this.maxRetries) {
this.chunkManager.requeueChunk(fileId, chunkInfo);
this.processChunkQueue(fileId);
}
else {
// 超过最大重试次数
this.fileManager.updateFileStatus(fileId, UploadStepEnum.error);
fileInfo.errorMessage = error.message;
if (this.onFileError) {
this.onFileError(fileInfo, error);
}
this.cleanupFile(fileId);
this.processFileQueue();
}
}
/**
* 更新文件上传进度
*/
updateFileProgress(fileInfo) {
const { fileId } = fileInfo;
const uploadedSize = this.chunkManager.calculateUploadedSize(fileInfo);
this.fileManager.updateFileProgress(fileId, uploadedSize);
if (this.onFileProgress) {
this.onFileProgress(fileInfo);
}
}
/**
* 完成文件上传
*/
async completeFileUpload(fileInfo) {
const { fileId } = fileInfo;
let result = {};
try {
fileInfo.status = UploadStepEnum.complete;
if (this.sendFileCompleteToServer) {
// 确保分片按partNumber排序
if (fileInfo.uploadInfo && fileInfo.uploadInfo.parts) {
fileInfo.uploadInfo.parts.sort((a, b) => a.partNumber - b.partNumber);
}
result = await this.sendFileCompleteToServer(fileInfo);
if (!result.isSuccess) {
throw new UploaderError('Complete file upload failed: ' + (result.message || 'Unknown error'), ErrorType.SERVER_ERROR, fileInfo);
}
}
if (this.onFileSuccess) {
this.onFileSuccess({ fileInfo, data: result.data });
}
if (this.onFileComplete) {
this.onFileComplete({ fileInfo, data: result.data });
}
this.cleanupFile(fileId);
this.processFileQueue();
// 检查是否所有文件都已完成
if (this.fileManager.getActiveCount() === 0 && this.fileManager.getQueueLength() === 0) {
if (this.onAllComplete) {
this.onAllComplete();
}
}
}
catch (error) {
this.handleFileError(fileInfo, error instanceof UploaderError ? error : new UploaderError(String(error), ErrorType.SERVER_ERROR, fileInfo));
}
}
/**
* 🔧 增强的错误处理方法
*/
handleFileError(fileInfo, error) {
const { fileId } = fileInfo;
this.fileManager.updateFileStatus(fileId, UploadStepEnum.error);
fileInfo.errorMessage = error.message;
// 🔧 输出详细的错误信息
console.error(`❌ 文件上传失败: ${fileInfo.fileName}`, {
fileId,
fileName: fileInfo.fileName,
fileSize: formatFileSize(fileInfo.fileSize),
error: error.message,
progress: fileInfo.progress
});
if (this.onFileError) {
this.onFileError(fileInfo, error);
}
this.cleanupFile(fileId);
this.processFileQueue();
}
/**
* 清理文件资源
*/
cleanupFile(fileId) {
this.fileManager.removeFromActive(fileId);
this.chunkManager.cleanup(fileId);
this.queuePersistence.removeFile(fileId);
}
/**
* 保存队列状态
*/
saveQueueState() {
if (this.queuePersistence.isEnabled()) {
const allFiles = [
...Array.from({ length: this.fileManager.getQueueLength() }),
...this.fileManager.getAllActiveFiles()
];
this.queuePersistence.saveQueue(this.fileManager.getAllActiveFiles());
}
}
/**
* 加载持久化队列
*/
loadPersistedQueue() {
if (this.queuePersistence.isEnabled()) {
const persistedFiles = this.queuePersistence.loadQueue();
console.log(`Loaded ${persistedFiles.length} persisted files`);
// 这里可以实现队列恢复逻辑
}
}
/**
* 启动性能监控
*/
startPerformanceMonitoring() {
if (this.performanceTimer) {
clearInterval(this.performanceTimer);
}
this.performanceTimer = setInterval(() => {
if (this.performanceMonitor.isEnabled()) {
// 计算剩余字节数
const activeFiles = this.fileManager.getAllActiveFiles();
let remainingBytes = 0;
for (const file of activeFiles) {
remainingBytes += file.fileSize - file.uploadedSize;
}
// 更新性能监控数据
this.performanceMonitor.setActiveConnections(this.workerManager.getStats().busy);
this.performanceMonitor.setFileStats(this.fileManager.getActiveCount(), this.fileManager.getActiveCount() + this.fileManager.getQueueLength());
const performanceData = this.performanceMonitor.getPerformanceData(remainingBytes);
if (this.onPerformanceUpdate) {
this.onPerformanceUpdate(performanceData);
}
}
}, 1000); // 每秒更新一次
}
// 公共API方法保持不变...
/**
* 暂停指定文件的上传
*/
pauseFile(fileId) {
const fileInfo = this.fileManager.getActiveFile(fileId);
if (fileInfo && fileInfo.status === UploadStepEnum.upload) {
this.fileManager.updateFileStatus(fileId, UploadStepEnum.pause);
if (this.sendPauseToServer) {
this.sendPauseToServer(fileInfo).catch(console.error);
}
}
}
/**
* 暂停所有文件的上传
*/
pauseAll() {
for (const fileInfo of this.fileManager.getAllActiveFiles()) {
if (fileInfo.status === UploadStepEnum.upload) {
this.pauseFile(fileInfo.fileId);
}
}
}
/**
* 恢复指定文件的上传
*/
resumeFile(fileId) {
const fileInfo = this.fileManager.getActiveFile(fileId);
if (fileInfo && fileInfo.status === UploadStepEnum.pause) {
this.fileManager.updateFileStatus(fileId, UploadStepEnum.upload);
this.processChunkQueue(fileId);
}
}
/**
* 恢复所有暂停的文件上传
*/
resumeAll() {
for (const fileInfo of this.fileManager.getAllActiveFiles()) {
if (fileInfo.status === UploadStepEnum.pause) {
this.resumeFile(fileInfo.fileId);
}
}
}
/**
* 取消指定文件的上传
*/
cancelFile(fileId) {
const fileInfo = this.fileManager.getActiveFile(fileId);
if (fileInfo) {
this.cleanupFile(fileId);
this.processFileQueue();
}
}
/**
* 取消所有文件的上传
*/
cancelAll() {
const activeFileIds = this.fileManager.getAllActiveFiles().map(f => f.fileId);
this.fileManager.clearQueue();
for (const fileId of activeFileIds) {
this.cancelFile(fileId);
}
}
/**
* 获取上传状态统计
*/
getStats() {
return this.fileManager.getStats();
}
/**
* 获取性能数据
*/
getPerformanceData() {
const activeFiles = this.fileManager.getAllActiveFiles();
let remainingBytes = 0;
for (const file of activeFiles) {
remainingBytes += file.fileSize - file.uploadedSize;
}
return this.performanceMonitor.getPerformanceData(remainingBytes);
}
/**
* 设置速度限制
*/
setSpeedLimit(bytesPerSecond, enabled = true) {
this.speedLimiter.setMaxBytesPerSecond(bytesPerSecond);
this.speedLimiter.setEnabled(enabled);
}
/**
* 启用/禁用性能监控
*/
setPerformanceMonitoring(enabled) {
this.performanceMonitor.setEnabled(enabled);
if (enabled) {
this.startPerformanceMonitoring();
}
else if (this.performanceTimer) {
clearInterval(this.performanceTimer);
this.performanceTimer = undefined;
}
}
/**
* 启用/禁用队列持久化
*/
setQueuePersistence(enabled) {
this.queuePersistence.setEnabled(enabled);
}
/**
* 销毁上传器实例
*/
destroy() {
this.cancelAll();
this.workerManager.destroy();
if (this.performanceTimer) {
clearInterval(this.performanceTimer);
}
}
/**
* 计算文件MD5值(静态方法)
*/
static calculateFileMD5(file, chunkSize = 2097152, onProgress) {
return new Promise((resolve, reject) => {
try {
const blobSlice = File.prototype.slice;
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
const loadNext = () => {
const start = currentChunk * chunkSize;
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
};
fileReader.onload = (e) => {
if (e.target && e.target.result) {
spark.append(e.target.result);
currentChunk++;
if (onProgress) {
const progress = Math.round((currentChunk / chunks) * 100);
onProgress(progress);
}
if (currentChunk < chunks) {
loadNext();
}
else {
const md5 = spark.end();
resolve(md5);
}
}
};
fileReader.onerror = () => {
reject(new Error('文件读取失败'));
};
loadNext();
}
catch (error) {
reject(error);
}
});
}
/**
* 获取性能指标(兼容性方法)
* @deprecated 使用 getPerformanceData() 代替
*/
getPerformanceMetrics() {
const performanceData = this.getPerformanceData();
const workerStats = this.workerManager.getStats();
return {
workerPoolSize: workerStats.total,
activeConnections: performanceData.activeConnections,
averageSpeed: performanceData.averageSpeed,
currentSpeed: performanceData.currentSpeed,
memoryUsage: performanceData.memoryUsage
};
}
/**
* 获取队列统计(兼容性方法)
* @deprecated 使用 getStats() 代替
*/
getQueueStats() {
const stats = this.getStats();
return {
total: stats.active + stats.queued + stats.completed + stats.failed,
active: stats.active,
queued: stats.queued,
completed: stats.completed,
failed: stats.failed
};
}
/**
* 🔧 配置验证和优化建议
*/
validateAndOptimizeConfig(options) {
const warnings = [];
const suggestions = [];
// 检查分片大小
const chunkSize = options.chunkSize || 1024 * 1024 * 5;
if (chunkSize < 1024 * 1024) {
warnings.push(`分片大小过小 (${formatFileSize(chunkSize)}),可能影响上传性能`);
suggestions.push('建议使用 1MB 以上的分片大小');
}
else if (chunkSize > 1024 * 1024 * 50) {
warnings.push(`分片大小过大 (${formatFileSize(chunkSize)}),可能导致内存占用过高`);
suggestions.push('建议使用 50MB 以下的分片大小');
}
// 检查并发数
if (options.maxConcurrentFiles && options.maxConcurrentFiles > 10) {
warnings.push(`并发文件数过多 (${options.maxConcurrentFiles}),可能影响浏览器性能`);
suggestions.push('建议将并发文件数控制在 10 以内');
}
if (options.maxConcurrentChunks && options.maxConcurrentChunks > 10) {
warnings.push(`并发分片数过多 (${options.maxConcurrentChunks}),可能导致网络拥塞`);
suggestions.push('建议将并发分片数控制在 10 以内');
}
// 检查文件类型配置
if (options.allowedFileTypes) {
const hasWildcard = options.allowedFileTypes.includes('*');
const hasEmptyStrings = options.allowedFileTypes.some(type => !type || type.trim() === '');
if (hasEmptyStrings) {
warnings.push('文件类型配置中包含空字符串,已自动过滤');
}
if (hasWildcard && this.debugMode) {
console.log('📁 检测到通配符 "*",文件类型验证已禁用');
}
}
// 输出警告和建议
if (warnings.length > 0 && this.debugMode) {
console.warn('⚠️ 配置警告:', warnings);
}
if (suggestions.length > 0 && this.debugMode) {
console.info('💡 优化建议:', suggestions);
}
}
/**
* 🔧 输出初始化信息
*/
logInitializationInfo() {
const config = this.fileManager.getConfiguration();
console.log('🚀 ParallelFileUploader 初始化完成', {
version: '2.0.1',
config: {
maxConcurrentFiles: this.maxConcurrentFiles,
maxConcurrentChunks: this.maxConcurrentChunks,
chunkSize: formatFileSize(this.chunkManager.getChunkSize()),
maxFileSize: config.maxFileSize || '无限制',
allowedFileTypes: config.supportedTypesDescription,
features: {
speedLimit: this.speedLimiter.isEnabled(),
performanceMonitor: this.performanceMonitor.isEnabled(),
queuePersistence: this.queuePersistence.isEnabled(),
workerSupport: this.workerManager.isSupported()
}
}
});
}
/**
* 🔧 获取详细的配置信息
*/
getConfiguration() {
return {
fileManager: this.fileManager.getConfiguration(),
chunkManager: {
chunkSize: formatFileSize(this.chunkManager.getChunkSize())
},
features: {
speedLimit: this.speedLimiter.isEnabled(),
performanceMonitor: this.performanceMonitor.isEnabled(),
queuePersistence: this.queuePersistence.isEnabled(),
workerSupport: this.workerManager.isSupported()
},
limits: {
maxConcurrentFiles: this.maxConcurrentFiles,
maxConcurrentChunks: this.maxConcurrentChunks,
maxRetries: this.maxRetries
}
};
}
/**
* 🔧 启用/禁用调试模式
*/
setDebugMode(enabled) {
this.debugMode = enabled;
if (enabled) {
console.log('🔍 调试模式已启用');
console.log('📊 当前配置:', this.getConfiguration());
}
}
}
// 导出格式化工具函数
export { formatFileSize, formatSpeed };
//# sourceMappingURL=index.js.map