"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isAbortError = isAbortError; exports.chunkUpload = chunkUpload; const tslib_1 = require("tslib"); const slice_1 = require("../files/slice"); const assert_1 = tslib_1.__importDefault(require("assert")); const Exception_1 = require("../../types/Exception"); function isAbortError(error) { return error instanceof DOMException && error.name === 'AbortError'; } /** * 分片上传通用方法,适用于所有类S3存储服务,如AWS,MinIO、阿里云OSS等 * @param options 参数 * @return */ async function chunkUpload(options) { const { extraFile, uploadFn, file, getPercent, onChunkSuccess } = options; const chunkInfo = extraFile.chunkInfo; const parallelism = options.parallelism || 5; const retryTimes = options.retryTimes || 5; const retryDelay = options.retryDelay || 1000; // 过滤出未完成的分片 const pendingParts = chunkInfo.parts.filter(part => !part.etag); if (pendingParts.length === 0) { return; // 所有分片已上传完成 } // 将文件分片 const chunks = await (0, slice_1.sliceFile)(file, chunkInfo.chunkSize, chunkInfo.partCount); const everyPercent = {}; // 用于记录每个分片的进度百分比 const updateChunkPercent = (partNumber, percent) => { everyPercent[partNumber] = percent; }; const updatePercentInterval = setInterval(() => { if (getPercent) { const totalPercent = Object.values(everyPercent).reduce((acc, val) => acc + val, 0) / chunkInfo.partCount; getPercent(totalPercent); } }, 500); // 上传单个分片的函数,带重试 const uploadPart = async (part, chunk) => { let lastError; for (let attempt = 0; attempt <= retryTimes; attempt++) { try { const response = await uploadFn(chunk, 'file', part.uploadUrl, part.formData || {}, true, (percent) => { // 更新每个分片的进度 updateChunkPercent(part.partNumber, percent); }, `${extraFile.id}:${part.partNumber}`, "PUT"); // 验证上传是否成功 let isSuccess = false; if (process.env.OAK_PLATFORM === 'wechatMp') { if (response.errMsg === 'request:ok') { const data = JSON.parse(response.data); isSuccess = !!(data.status === 204 || data.status === 200); } } else { isSuccess = !!(response.status === 200 || response.status === 204); } if (isSuccess) { // 标记该分片已完成 part.etag = response.headers?.get("ETag") || response.headers?.get("etag") || response.headers?.get("eTag"); (0, assert_1.default)(part.etag, `无法获取分片 ${part.partNumber} 的 ETag`); return; } throw new Exception_1.OakUploadException(`分片 ${part.partNumber} 上传失败`); } catch (err) { console.error(`分片 ${part.partNumber} 上传第 ${attempt + 1} 次失败:`, err); lastError = err; // 如果是OakUserException说明是用户主动中止上传,不进行重试 if (isAbortError(err)) { throw err; } if (attempt < retryTimes) { // 等待后重试 await new Promise(resolve => setTimeout(resolve, retryDelay)); } } } throw lastError || new Exception_1.OakUploadException(`分片 ${part.partNumber} 上传失败`); }; // 并行上传控制 const uploadTasks = pendingParts.map((part) => ({ part, chunk: chunks[part.partNumber - 1] })); // 使用并发控制执行上传 const executing = new Set(); const errors = []; for (const task of uploadTasks) { let promise; promise = (async () => { try { await uploadPart(task.part, task.chunk); } catch (err) { if (isAbortError(err)) { // 用户主动中止上传,抛到上层再处理 console.log(`分片 ${task.part.partNumber} 上传被用户中止`); } errors.push(err); throw err; } finally { if (promise) { executing.delete(promise); } } })(); executing.add(promise); // 当达到并发限制时,等待任意一个完成 if (executing.size >= parallelism) { await Promise.race(executing).catch(() => { }); } } // 等待所有任务完成 await Promise.allSettled([...executing]); clearInterval(updatePercentInterval); // 检查是否有错误 if (errors.length > 0) { throw errors[0]; } // 等待所有任务完成 await Promise.all(executing); // // 调用分片成功回调(所有分片完成后) // if (onChunkSuccess) { // await onChunkSuccess(chunkInfo); // } // 清理小程序环境下的临时文件 if (process.env.OAK_PLATFORM === 'wechatMp' && typeof file === 'string') { await (0, slice_1.cleanTempFiles)(chunks); } return; }