UNPKG

@zxhsure/file-uploader

Version:

A chunked file uploader with customizable upload functions

1,067 lines (869 loc) 32.3 kB
# File Uploader 一个功能强大的大文件切片上传 JavaScript 库,支持断点续传、并发控制、进度监控和真实内容 Hash 生成。 ## 🌟 特性 - 🚀 **大文件切片上传** - 支持任意大小文件的分片上传 - 🎯 **可自定义配置** - 灵活配置chunk大小、并发数等参数 - 🔧 **自定义上传函数** - 完全自定义上传逻辑,适配任何后端API - 📊 **实时进度监控** - 详细的上传进度回调 - 🛡️ **真实内容Hash** - 基于SHA-256的文件块内容hash,确保数据完整性 - 🔄 **断点续传支持** - 通过hash值支持断点续传逻辑 - 🚦 **并发控制** - 内置信号量机制控制并发上传数量 - 📦 **零依赖** - 纯原生JavaScript实现,无外部依赖 - 🌐 **浏览器兼容** - 支持现代浏览器的Web Crypto API ## 📦 安装 ```bash npm install @zxhsure/file-uploader ``` ## ⚡ 使用前必读 在开始使用之前,请注意以下关键点: 1. **📤 上传函数配置**: - 库提供了默认的上传函数,但仅用于演示 - **生产环境必须配置自定义的 `uploadFunction`** 来适配你的后端 API - 默认函数会向 `/api/upload/chunk` 发送 POST 请求 2. **🔗 文件合并**: - `mergeChunks()` 方法是否必需取决于你的后端实现 - 如果后端需要明确的合并指令,则必须调用此方法 - 建议即使后端自动合并也调用此方法来确认状态 3. **🌐 API 兼容性**: - 需要现代浏览器支持 Web Crypto API (用于生成真实 hash) - 如果不支持,会自动降级到简单 hash 生成 ## 🚀 快速开始 ```javascript import FileUploader from '@zxhsure/file-uploader'; const uploader = new FileUploader({ chunkSize: 2 * 1024 * 1024, // 2MB chunks concurrent: 5, // 5个并发上传 // ⚠️ 重要:必须配置适合你后端API的上传函数 uploadFunction: async (chunkData) => { const formData = new FormData(); formData.append('file', chunkData.chunk); formData.append('index', chunkData.index); formData.append('hash', chunkData.hash); formData.append('fileName', chunkData.fileName); formData.append('totalChunks', chunkData.totalChunks); const response = await fetch('https://your-api.com/upload/chunk', { method: 'POST', headers: { 'Authorization': 'Bearer your-token' // 根据需要添加认证 }, body: formData }); if (!response.ok) { throw new Error(`上传失败: ${response.statusText}`); } return await response.json(); }, onProgress: (info) => { console.log(`上传进度: ${info.progress.toFixed(2)}%`); }, onComplete: (info) => { console.log(`文件 ${info.fileName} 上传完成!`); } }); // 上传文件 const fileInput = document.getElementById('fileInput'); fileInput.addEventListener('change', async (event) => { const file = event.target.files[0]; if (file) { try { const result = await uploader.upload(file); console.log('上传成功:', result); // 📝 如果后端需要合并步骤,在这里调用合并 // await uploader.mergeChunks(file.name, result.results.length, mergeFn); } catch (error) { console.error('上传失败:', error); } } }); ``` > **🔍 注意事项:** > - **uploadFunction 是关键**:默认实现仅用于演示,生产环境必须自定义 > - **API 适配**:根据你的后端 API 调整 FormData 字段名和请求结构 > - **认证处理**:添加必要的认证头或参数 > - **错误处理**:确保正确处理 HTTP 错误状态 ## 📖 详细 API 文档 ### 构造函数 #### `new FileUploader(options)` 创建一个新的文件上传器实例。 **参数 (options):** | 参数 | 类型 | 默认值 | 必填 | 描述 | |------|------|--------|------|------| | `chunkSize` | `number` | `1024 * 1024` (1MB) | ❌ | 每个文件块的大小(字节) | | `concurrent` | `number` | `3` | ❌ | 同时上传的块数量 | | `uploadFunction` | `function` | `defaultUploadFunction` | ⚠️ | 自定义上传函数,**强烈建议自定义** | | `onProgress` | `function` | `() => {}` | ❌ | 进度更新回调函数 | | `onError` | `function` | `() => {}` | ❌ | 错误处理回调函数 | | `onComplete` | `function` | `() => {}` | ❌ | 上传完成回调函数 | > **⚠️ 重要提示:** > - **uploadFunction**: 虽然有默认实现,但默认函数只是向 `/api/upload/chunk` 发送 POST 请求,**实际使用中几乎总是需要根据你的后端 API 进行自定义** > - **默认上传函数**:仅用于测试和演示,生产环境请务必提供自定义的上传函数 **示例:** ```javascript const uploader = new FileUploader({ chunkSize: 5 * 1024 * 1024, // 5MB concurrent: 3, // 自定义上传函数 uploadFunction: async (chunkData) => { const formData = new FormData(); formData.append('file', chunkData.chunk); formData.append('index', chunkData.index); formData.append('hash', chunkData.hash); const response = await fetch('/api/upload', { method: 'POST', body: formData }); return await response.json(); }, // 进度回调 onProgress: ({ progress, completedChunks, totalChunks }) => { const progressBar = document.getElementById('progressBar'); progressBar.style.width = `${progress}%`; progressBar.textContent = `${completedChunks}/${totalChunks} chunks`; }, // 错误处理 onError: ({ error, chunk, fileName }) => { console.error(`文件 ${fileName} 的第 ${chunk.index} 块上传失败:`, error); // 可以在这里实现重试逻辑 }, // 完成回调 onComplete: ({ fileName, fileSize, totalChunks, results }) => { console.log(`文件 ${fileName} 上传完成!`); console.log(`文件大小: ${fileSize} bytes`); console.log(`总块数: ${totalChunks}`); // 触发合并请求等后续操作 } }); ``` --- ### 方法 #### `generateChunks(file)` 将文件分割成多个块,并为每个块生成基于内容的SHA-256哈希值。 **参数:** - `file` (`File`): 要分割的文件对象 **返回值:** ```javascript Promise<{ chunks: Array<{ index: number, // 块索引 (从0开始) chunk: Blob, // 文件块数据 start: number, // 在原文件中的起始位置 end: number, // 在原文件中的结束位置 size: number, // 块大小 hash: string // SHA-256哈希值 (格式: fileName_fileSize_index_sha256hex) }>, totalChunks: number, // 总块数 fileInfo: { name: string, // 文件名 size: number, // 文件大小 type: string, // 文件类型 lastModified: number // 最后修改时间 } }> ``` **使用场景:** 1. **预分析文件结构** - 在正式上传前分析文件将被分成多少块 2. **断点续传准备** - 生成文件块信息,检查哪些块已经上传 3. **分离关注点** - 将文件分块逻辑与上传逻辑分开 **示例:** ```javascript // 场景1: 预分析文件,显示将要上传的块信息 async function analyzeFile(file) { try { const { chunks, totalChunks, fileInfo } = await uploader.generateChunks(file); console.log(`文件: ${fileInfo.name}`); console.log(`大小: ${fileInfo.size} bytes`); console.log(`将分成 ${totalChunks} 块上传`); // 显示每个块的信息 chunks.forEach((chunk, i) => { console.log(`块 ${i}: ${chunk.size} bytes, hash: ${chunk.hash.slice(-8)}`); }); return { chunks, totalChunks, fileInfo }; } catch (error) { console.error('文件分析失败:', error); } } // 场景2: 断点续传 - 检查已上传的块 async function checkUploadedChunks(file) { const { chunks, fileInfo } = await uploader.generateChunks(file); // 向服务器查询已上传的块 const response = await fetch(`/api/upload/status/${fileInfo.name}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: fileInfo.name, fileSize: fileInfo.size, hashes: chunks.map(c => c.hash) }) }); const { uploadedHashes } = await response.json(); // 过滤出未上传的块 const remainingChunks = chunks.filter(chunk => !uploadedHashes.includes(chunk.hash) ); console.log(`还需上传 ${remainingChunks.length} 个块`); return remainingChunks; } // 场景3: 批量处理多个文件 async function batchAnalyzeFiles(files) { const analysisResults = []; for (const file of files) { try { const result = await uploader.generateChunks(file); analysisResults.push({ file: file.name, ...result }); } catch (error) { console.error(`分析文件 ${file.name} 失败:`, error); } } // 计算总上传量 const totalSize = analysisResults.reduce((sum, r) => sum + r.fileInfo.size, 0); const totalChunks = analysisResults.reduce((sum, r) => sum + r.totalChunks, 0); console.log(`总共 ${files.length} 个文件`); console.log(`总大小: ${totalSize} bytes`); console.log(`总块数: ${totalChunks}`); return analysisResults; } ``` --- #### `upload(file)` 执行文件的分块上传,包含进度监控和错误处理。 **参数:** - `file` (`File`): 要上传的文件对象 **返回值:** ```javascript Promise<{ success: boolean, // 上传是否成功 fileInfo: { name: string, // 文件名 size: number, // 文件大小 type: string, // 文件类型 lastModified: number // 最后修改时间 }, results: Array<any> // 每个块的上传结果数组 }> ``` **使用场景:** 1. **标准文件上传** - 最常见的使用场景 2. **大文件上传** - 处理GB级别的大文件 3. **批量文件上传** - 按顺序或并行上传多个文件 **示例:** ```javascript // 场景1: 标准文件上传 async function uploadSingleFile(file) { try { const result = await uploader.upload(file); if (result.success) { console.log(`文件 ${result.fileInfo.name} 上传成功!`); console.log(`文件大小: ${result.fileInfo.size} bytes`); console.log(`上传结果:`, result.results); // 通知服务器合并文件 await mergeFile(result.fileInfo.name, result.results.length); } } catch (error) { console.error('上传失败:', error.message); // 可以在这里实现重试逻辑 } } // 场景2: 大文件上传 (带详细进度显示) async function uploadLargeFile(file) { // 创建专门的大文件上传器 const largeFileUploader = new FileUploader({ chunkSize: 10 * 1024 * 1024, // 10MB chunks for large files concurrent: 2, // 降低并发避免网络拥塞 onProgress: ({ progress, completedChunks, totalChunks, currentChunk }) => { const progressElement = document.getElementById('progress'); const statusElement = document.getElementById('status'); progressElement.style.width = `${progress}%`; statusElement.textContent = `上传中: ${progress.toFixed(1)}% (${completedChunks}/${totalChunks} 块)`; // 估算剩余时间 const elapsed = Date.now() - uploadStartTime; const estimatedTotal = elapsed / (progress / 100); const remaining = estimatedTotal - elapsed; document.getElementById('timeRemaining').textContent = `预计剩余: ${Math.round(remaining / 1000)}秒`; } }); const uploadStartTime = Date.now(); try { const result = await largeFileUploader.upload(file); const uploadTime = (Date.now() - uploadStartTime) / 1000; const speed = file.size / uploadTime / 1024 / 1024; // MB/s console.log(`大文件上传完成! 用时: ${uploadTime.toFixed(2)}秒`); console.log(`平均速度: ${speed.toFixed(2)} MB/s`); return result; } catch (error) { console.error('大文件上传失败:', error); throw error; } } // 场景3: 批量文件上传 (串行) async function uploadMultipleFiles(files) { const results = []; let totalProgress = 0; for (let i = 0; i < files.length; i++) { const file = files[i]; console.log(`开始上传第 ${i + 1}/${files.length} 个文件: ${file.name}`); try { // 为每个文件创建独立的进度跟踪 const fileUploader = new FileUploader({ onProgress: ({ progress }) => { const overallProgress = (totalProgress + progress / files.length); updateOverallProgress(overallProgress); } }); const result = await fileUploader.upload(file); results.push({ file: file.name, success: true, result }); totalProgress += 100 / files.length; } catch (error) { console.error(`文件 ${file.name} 上传失败:`, error); results.push({ file: file.name, success: false, error: error.message }); } } console.log('批量上传完成:', results); return results; } // 场景4: 并行文件上传 (限制并发数) async function uploadFilesInParallel(files, maxConcurrent = 3) { const semaphore = new Semaphore(maxConcurrent); const uploadPromises = files.map(async (file) => { const release = await semaphore.acquire(); try { console.log(`开始上传: ${file.name}`); const result = await uploader.upload(file); console.log(`上传完成: ${file.name}`); return { file: file.name, success: true, result }; } catch (error) { console.error(`上传失败: ${file.name}`, error); return { file: file.name, success: false, error: error.message }; } finally { release(); } }); const results = await Promise.all(uploadPromises); const successful = results.filter(r => r.success).length; const failed = results.filter(r => !r.success).length; console.log(`并行上传完成: 成功 ${successful}, 失败 ${failed}`); return results; } ``` --- #### `mergeChunks(fileName, totalChunks, mergeFunction)` 合并服务器上已上传的文件块,通常在所有块上传完成后调用。 > **📝 关于文件合并:** > - **是否必需**:取决于你的后端实现 > - 如果后端在接收每个块后自动合并,则此方法**不是必需的** > - 如果后端需要明确的合并指令,则此方法**是必需的** > - **推荐做法**:即使后端自动合并,也建议调用此方法来确认合并状态和获取最终文件信息 > - **典型场景**:大多数分片上传实现都需要最后的合并步骤 **参数:** - `fileName` (`string`): 要合并的文件名 - `totalChunks` (`number`): 总块数 - `mergeFunction` (`function`): 自定义合并函数 **mergeFunction 参数:** ```javascript { fileName: string, // 文件名 totalChunks: number // 总块数 } ``` **返回值:** `Promise<any>` - 合并函数的返回结果 **使用场景:** 1. **标准文件合并** - 上传完成后合并文件 2. **验证合并** - 合并前验证所有块完整性 3. **批处理合并** - 批量合并多个文件 **示例:** ```javascript // 场景1: 标准文件合并 async function mergeUploadedFile(fileName, totalChunks) { try { const result = await uploader.mergeChunks( fileName, totalChunks, async ({ fileName, totalChunks }) => { const response = await fetch('/api/upload/merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName, totalChunks, timestamp: Date.now() }) }); if (!response.ok) { throw new Error(`合并失败: ${response.statusText}`); } return await response.json(); } ); console.log('文件合并成功:', result); return result; } catch (error) { console.error('文件合并失败:', error); throw error; } } // 场景2: 带验证的文件合并 async function mergeWithValidation(fileName, totalChunks, expectedHash) { const result = await uploader.mergeChunks( fileName, totalChunks, async ({ fileName, totalChunks }) => { // 第一步: 验证所有块都已上传 const validationResponse = await fetch('/api/upload/validate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName, totalChunks }) }); const validation = await validationResponse.json(); if (!validation.valid) { throw new Error(`文件块验证失败: ${validation.message}`); } // 第二步: 执行合并 const mergeResponse = await fetch('/api/upload/merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName, totalChunks, expectedHash }) }); const mergeResult = await mergeResponse.json(); // 第三步: 验证合并后文件的完整性 if (expectedHash && mergeResult.actualHash !== expectedHash) { throw new Error('合并后文件hash不匹配'); } return mergeResult; } ); console.log('验证合并完成:', result); return result; } // 场景3: 批量文件合并 async function batchMergeFiles(fileInfos) { const mergeResults = []; for (const fileInfo of fileInfos) { try { console.log(`开始合并文件: ${fileInfo.fileName}`); const result = await uploader.mergeChunks( fileInfo.fileName, fileInfo.totalChunks, async ({ fileName, totalChunks }) => { const response = await fetch('/api/upload/batch-merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName, totalChunks, batchId: fileInfo.batchId, priority: fileInfo.priority || 'normal' }) }); return await response.json(); } ); mergeResults.push({ fileName: fileInfo.fileName, success: true, result }); console.log(`文件 ${fileInfo.fileName} 合并完成`); } catch (error) { console.error(`文件 ${fileInfo.fileName} 合并失败:`, error); mergeResults.push({ fileName: fileInfo.fileName, success: false, error: error.message }); } } const successful = mergeResults.filter(r => r.success).length; console.log(`批量合并完成: ${successful}/${fileInfos.length} 个文件成功`); return mergeResults; } // 场景4: 带重试的合并 async function mergeWithRetry(fileName, totalChunks, maxRetries = 3) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`尝试合并文件 ${fileName} (第 ${attempt}/${maxRetries} 次)`); const result = await uploader.mergeChunks( fileName, totalChunks, async ({ fileName, totalChunks }) => { const response = await fetch('/api/upload/merge', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Retry-Attempt': attempt.toString() }, body: JSON.stringify({ fileName, totalChunks, retryAttempt: attempt }) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } ); console.log(`文件 ${fileName} 在第 ${attempt} 次尝试中合并成功`); return result; } catch (error) { lastError = error; console.warn(`第 ${attempt} 次合并尝试失败:`, error.message); if (attempt < maxRetries) { // 指数退避策略 const delay = Math.pow(2, attempt) * 1000; console.log(`等待 ${delay}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, delay)); } } } throw new Error(`合并失败,已重试 ${maxRetries} 次: ${lastError.message}`); } ``` --- ## 📋 完整使用示例 ### 基础示例 ```html <!DOCTYPE html> <html> <head> <title>File Uploader Demo</title> <style> .progress-container { width: 100%; background-color: #f0f0f0; border-radius: 5px; margin: 10px 0; } .progress-bar { width: 0%; height: 30px; background-color: #4CAF50; border-radius: 5px; text-align: center; line-height: 30px; color: white; transition: width 0.3s ease; } </style> </head> <body> <input type="file" id="fileInput" multiple> <div class="progress-container"> <div class="progress-bar" id="progressBar">0%</div> </div> <div id="status">请选择文件</div> <div id="results"></div> <script type="module"> import FileUploader from './src/file-upload.js'; const uploader = new FileUploader({ chunkSize: 2 * 1024 * 1024, // 2MB concurrent: 3, // 自定义上传函数 uploadFunction: async (chunkData) => { // 模拟网络延迟 await new Promise(resolve => setTimeout(resolve, 100)); // 这里替换为你的实际API const formData = new FormData(); formData.append('file', chunkData.chunk); formData.append('chunkIndex', chunkData.index); formData.append('fileName', chunkData.fileName); formData.append('chunkHash', chunkData.hash); formData.append('totalChunks', chunkData.totalChunks); const response = await fetch('/api/upload/chunk', { method: 'POST', body: formData }); return await response.json(); }, // 进度更新 onProgress: ({ progress, completedChunks, totalChunks }) => { const progressBar = document.getElementById('progressBar'); const status = document.getElementById('status'); progressBar.style.width = `${progress}%`; progressBar.textContent = `${progress.toFixed(1)}%`; status.textContent = `上传中: ${completedChunks}/${totalChunks} 块完成`; }, // 错误处理 onError: ({ error, chunk, fileName }) => { const status = document.getElementById('status'); status.textContent = `错误: ${error.message}`; status.style.color = 'red'; }, // 上传完成 onComplete: ({ fileName, fileSize, totalChunks }) => { const status = document.getElementById('status'); const results = document.getElementById('results'); status.textContent = `上传完成: ${fileName}`; status.style.color = 'green'; results.innerHTML += ` <div> <strong>${fileName}</strong> (${(fileSize / 1024 / 1024).toFixed(2)} MB, ${totalChunks} 块) </div> `; } }); // 文件选择处理 document.getElementById('fileInput').addEventListener('change', async (event) => { const files = Array.from(event.target.files); for (const file of files) { try { await uploader.upload(file); } catch (error) { console.error(`文件 ${file.name} 上传失败:`, error); } } }); </script> </body> </html> ``` ### 高级示例 (断点续传) ```javascript class AdvancedUploader { constructor() { this.uploader = new FileUploader({ chunkSize: 5 * 1024 * 1024, // 5MB concurrent: 3, uploadFunction: this.uploadChunk.bind(this), onProgress: this.handleProgress.bind(this), onError: this.handleError.bind(this), onComplete: this.handleComplete.bind(this) }); this.uploadState = new Map(); // 存储上传状态 } // 检查断点续传状态 async checkResumeStatus(file) { const { chunks, fileInfo } = await this.uploader.generateChunks(file); const hashes = chunks.map(c => c.hash); try { const response = await fetch('/api/upload/status', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: fileInfo.name, fileSize: fileInfo.size, hashes }) }); const { uploadedHashes = [] } = await response.json(); // 过滤出未上传的块 const remainingChunks = chunks.filter(chunk => !uploadedHashes.includes(chunk.hash) ); return { total: chunks.length, completed: chunks.length - remainingChunks.length, remaining: remainingChunks, canResume: remainingChunks.length < chunks.length }; } catch (error) { console.warn('无法检查续传状态,将进行完整上传:', error); return { total: chunks.length, completed: 0, remaining: chunks, canResume: false }; } } // 断点续传上传 async resumeUpload(file) { const resumeStatus = await this.checkResumeStatus(file); if (resumeStatus.canResume) { console.log(`检测到断点续传: 已完成 ${resumeStatus.completed}/${resumeStatus.total} 块`); } // 这里需要修改uploader以支持只上传特定块 // 实际实现中可能需要扩展FileUploader类 return await this.uploader.upload(file); } async uploadChunk(chunkData) { // 实现带重试的上传逻辑 const maxRetries = 3; let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const formData = new FormData(); formData.append('file', chunkData.chunk); formData.append('index', chunkData.index); formData.append('hash', chunkData.hash); formData.append('fileName', chunkData.fileName); formData.append('totalChunks', chunkData.totalChunks); const response = await fetch('/api/upload/chunk', { method: 'POST', body: formData, headers: { 'Upload-Attempt': attempt.toString() } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { lastError = error; console.warn(`块 ${chunkData.index} 第 ${attempt} 次上传失败:`, error.message); if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000) ); } } } throw lastError; } handleProgress(progressInfo) { const { progress, completedChunks, totalChunks, currentChunk } = progressInfo; console.log(`进度: ${progress.toFixed(2)}% (${completedChunks}/${totalChunks})`); // 更新UI this.updateProgressUI(progress, completedChunks, totalChunks); // 保存进度状态 this.saveProgressState(progressInfo); } handleError(errorInfo) { const { error, chunk, fileName } = errorInfo; console.error(`文件 ${fileName} 块 ${chunk.index} 上传失败:`, error); // 记录失败的块,用于重试 this.recordFailedChunk(fileName, chunk, error); } handleComplete(completeInfo) { const { fileName, fileSize, totalChunks } = completeInfo; console.log(`文件上传完成: ${fileName}`); // 触发文件合并 this.mergeFile(fileName, totalChunks); } async mergeFile(fileName, totalChunks) { try { const result = await this.uploader.mergeChunks( fileName, totalChunks, async ({ fileName, totalChunks }) => { const response = await fetch('/api/upload/merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName, totalChunks }) }); return await response.json(); } ); console.log(`文件合并完成: ${fileName}`, result); return result; } catch (error) { console.error(`文件合并失败: ${fileName}`, error); throw error; } } // 辅助方法 updateProgressUI(progress, completed, total) { // 实现进度UI更新 } saveProgressState(progressInfo) { // 实现进度状态保存(如localStorage) } recordFailedChunk(fileName, chunk, error) { // 记录失败块信息,用于重试 } } // 使用高级上传器 const advancedUploader = new AdvancedUploader(); document.getElementById('fileInput').addEventListener('change', async (event) => { const file = event.target.files[0]; if (file) { try { await advancedUploader.resumeUpload(file); } catch (error) { console.error('上传失败:', error); } } }); ``` ## ⚙️ 配置选项详解 ### 自定义上传函数 上传函数接收包含以下属性的 `chunkData` 对象: ```javascript { index: number, // 块索引 chunk: Blob, // 文件块 start: number, // 起始位置 end: number, // 结束位置 size: number, // 块大小 hash: string, // 块哈希值 fileName: string, // 文件名 totalChunks: number, // 总块数 fileInfo: { // 文件信息 name: string, size: number, type: string, lastModified: number } } ``` ### 回调函数详解 #### onProgress 回调 ```javascript { progress: number, // 进度百分比 (0-100) completedChunks: number, // 已完成块数 totalChunks: number, // 总块数 currentChunk: number // 当前上传的块索引 } ``` #### onError 回调 ```javascript { error: Error, // 错误对象 chunk: Object, // 失败的块信息 fileName: string // 文件名 } ``` #### onComplete 回调 ```javascript { fileName: string, // 文件名 fileSize: number, // 文件大小 totalChunks: number, // 总块数 results: Array<any> // 所有块的上传结果 } ``` ## 🛡️ 错误处理 库内置了多层错误处理机制: 1. **网络错误**: 自动重试机制 2. **文件读取错误**: 降级处理 3. **Hash生成错误**: 回退到简单hash 4. **并发控制**: 防止资源耗尽 ## 🔧 浏览器兼容性 - Chrome 37+ - Firefox 34+ - Safari 7.1+ - Edge 79+ 需要支持的Web APIs: - File API - Blob API - Web Crypto API (用于hash生成) - Promise - async/await ## 📈 性能建议 1. **chunk大小**: - 小文件: 1-2MB - 大文件: 5-10MB - 超大文件: 10-50MB 2. **并发数**: - 快速网络: 5-8 - 普通网络: 3-5 - 慢速网络: 1-2 3. **内存优化**: - 避免同时处理过多大文件 - 及时清理已完成的chunk引用 ## 📄 许可证 MIT License - 详见 [LICENSE](LICENSE) 文件