UNPKG

bridgecrate

Version:

一个提供数据传输和桥接功能的JavaScript工具包

199 lines (171 loc) 5.47 kB
class FileUploader { constructor(options = {}) { this.chunkSize = options.chunkSize || 1024 * 1024; // 默认1MB this.concurrent = options.concurrent || 3; // 并发数 this.uploadFunction = options.uploadFunction || this.defaultUploadFunction; this.onProgress = options.onProgress || (() => {}); this.onError = options.onError || (() => {}); this.onComplete = options.onComplete || (() => {}); } // 生成文件切片 async generateChunks(file) { const chunks = []; const totalChunks = Math.ceil(file.size / this.chunkSize); for (let i = 0; i < totalChunks; i++) { const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, file.size); const chunk = file.slice(start, end); // 异步生成hash const hash = await this.generateChunkHash(chunk, i, file.name, file.size); chunks.push({ index: i, chunk: chunk, start: start, end: end, size: end - start, hash: hash }); } return { chunks, totalChunks, fileInfo: { name: file.name, size: file.size, type: file.type, lastModified: file.lastModified } }; } // 生成chunk哈希 async generateChunkHash(chunk, index, fileName, fileSize) { try { // 使用 Web Crypto API 生成真正的 hash const buffer = await chunk.arrayBuffer(); const hashBuffer = await crypto.subtle.digest('SHA-256', buffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // 组合文件信息和chunk hash,确保唯一性 return `${fileName}_${fileSize}_${index}_${hashHex}`; } catch (error) { console.warn('Failed to generate hash, falling back to simple hash:', error); // 降级方案:如果hash生成失败,使用简单规则 return `${fileName}_${fileSize}_${index}_${Date.now()}`; } } // 默认上传函数 async defaultUploadFunction(chunkData) { const formData = new FormData(); formData.append('chunk', 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 }); if (!response.ok) { throw new Error(`Upload failed: ${response.statusText}`); } return await response.json(); } // 上传文件 async upload(file) { try { const { chunks, totalChunks, fileInfo } = await this.generateChunks(file); const uploadPromises = []; const results = []; let completedChunks = 0; // 控制并发上传 const semaphore = new Semaphore(this.concurrent); for (const chunkInfo of chunks) { const uploadPromise = semaphore.acquire().then(async (release) => { try { const chunkData = { ...chunkInfo, fileName: fileInfo.name, totalChunks: totalChunks, fileInfo: fileInfo }; const result = await this.uploadFunction(chunkData); completedChunks++; const progress = (completedChunks / totalChunks) * 100; this.onProgress({ progress, completedChunks, totalChunks, currentChunk: chunkInfo.index }); return result; } catch (error) { this.onError({ error, chunk: chunkInfo, fileName: fileInfo.name }); throw error; } finally { release(); } }); uploadPromises.push(uploadPromise); } const uploadResults = await Promise.all(uploadPromises); this.onComplete({ fileName: fileInfo.name, fileSize: fileInfo.size, totalChunks, results: uploadResults }); return { success: true, fileInfo, results: uploadResults }; } catch (error) { throw new Error(`File upload failed: ${error.message}`); } } // 合并chunks的辅助方法 async mergeChunks(fileName, totalChunks, mergeFunction) { if (!mergeFunction) { throw new Error('Merge function is required'); } return await mergeFunction({ fileName, totalChunks }); } } // 信号量类用于控制并发 class Semaphore { constructor(max) { this.max = max; this.current = 0; this.queue = []; } acquire() { return new Promise((resolve) => { if (this.current < this.max) { this.current++; resolve(this.createRelease()); } else { this.queue.push(() => { this.current++; resolve(this.createRelease()); }); } }); } createRelease() { return () => { this.current--; if (this.queue.length > 0) { const next = this.queue.shift(); next(); } }; } } export default FileUploader;