136 lines
5.4 KiB
JavaScript
136 lines
5.4 KiB
JavaScript
"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;
|
||
}
|