fix: 修复上传取消功能,确保 HTTP 请求正确中止

- 在 XMLHttpRequest 中添加 signal.aborted 检查
- 修复 useSliceUpload 中的 cancelFn 闭包问题
- 确保流式上传和分片上传都能正确取消
This commit is contained in:
2026-02-04 14:51:23 +08:00
parent f381d641ab
commit 17a62cd3c2
3 changed files with 57 additions and 9 deletions

View File

@@ -99,8 +99,14 @@ export function useFileSliceUpload(
if (!task) { if (!task) {
return; return;
} }
const { reqId, key } = task; const { reqId, key, controller } = task;
const { loaded, i, j, files, totalSize } = fileInfo; const { loaded, i, j, files, totalSize } = fileInfo;
// 检查是否已取消
if (controller.signal.aborted) {
throw new Error("Upload cancelled");
}
const formData = await buildFormData({ const formData = await buildFormData({
file: files[i], file: files[i],
i, i,
@@ -110,6 +116,7 @@ export function useFileSliceUpload(
let newTask = { ...task }; let newTask = { ...task };
await uploadChunk(key, formData, { await uploadChunk(key, formData, {
signal: controller.signal,
onUploadProgress: (e) => { onUploadProgress: (e) => {
const loadedSize = loaded + e.loaded; const loadedSize = loaded + e.loaded;
const curPercent = Number((loadedSize / totalSize) * 100).toFixed(2); const curPercent = Number((loadedSize / totalSize) * 100).toFixed(2);
@@ -141,9 +148,10 @@ export function useFileSliceUpload(
reqId, reqId,
isCancel: false, isCancel: false,
cancelFn: () => { cancelFn: () => {
task.controller.abort(); // 使用 newTask 的 controller 确保一致性
newTask.controller.abort();
cancelUpload?.(reqId); cancelUpload?.(reqId);
if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent)); if (newTask.updateEvent) window.dispatchEvent(new Event(newTask.updateEvent));
}, },
}; };
updateTaskList(newTask); updateTaskList(newTask);
@@ -153,8 +161,16 @@ export function useFileSliceUpload(
let loaded = 0; let loaded = 0;
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
// 检查是否已取消
if (newTask.controller.signal.aborted) {
throw new Error("Upload cancelled");
}
const { slices } = files[i]; const { slices } = files[i];
for (let j = 0; j < slices.length; j++) { for (let j = 0; j < slices.length; j++) {
// 检查是否已取消
if (newTask.controller.signal.aborted) {
throw new Error("Upload cancelled");
}
await uploadSlice(newTask, { await uploadSlice(newTask, {
loaded, loaded,
i, i,
@@ -219,9 +235,10 @@ export function useFileSliceUpload(
reqId, reqId,
isCancel: false, isCancel: false,
cancelFn: () => { cancelFn: () => {
task.controller.abort(); // 使用 newTask 的 controller 确保一致性
newTask.controller.abort();
cancelUpload?.(reqId); cancelUpload?.(reqId);
if (task.updateEvent) window.dispatchEvent(new Event(task.updateEvent)); if (newTask.updateEvent) window.dispatchEvent(new Event(newTask.updateEvent));
}, },
}; };
updateTaskList(newTask); updateTaskList(newTask);
@@ -232,13 +249,26 @@ export function useFileSliceUpload(
// 逐个处理文件 // 逐个处理文件
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
// 检查是否已取消
if (newTask.controller.signal.aborted) {
throw new Error("Upload cancelled");
}
const file = files[i]; const file = files[i];
console.log(`[useSliceUpload] Processing file ${i + 1}/${files.length}: ${file.name}`); console.log(`[useSliceUpload] Processing file ${i + 1}/${files.length}: ${file.name}`);
const result = await streamSplitAndUpload( const result = await streamSplitAndUpload(
file, file,
(formData, config) => uploadChunk(task.key, formData, config), (formData, config) => uploadChunk(task.key, formData, {
...config,
signal: newTask.controller.signal,
}),
(currentBytes, totalBytes, uploadedLines) => { (currentBytes, totalBytes, uploadedLines) => {
// 检查是否已取消
if (newTask.controller.signal.aborted) {
return;
}
// 更新进度 // 更新进度
const overallBytes = totalProcessedBytes + currentBytes; const overallBytes = totalProcessedBytes + currentBytes;
const curPercent = Number((overallBytes / totalSize) * 100).toFixed(2); const curPercent = Number((overallBytes / totalSize) * 100).toFixed(2);
@@ -260,9 +290,9 @@ export function useFileSliceUpload(
1024 * 1024, // 1MB chunk size 1024 * 1024, // 1MB chunk size
{ {
reqId, reqId,
hasArchive: task.hasArchive, hasArchive: newTask.hasArchive,
prefix: task.prefix, prefix: newTask.prefix,
signal: task.controller.signal, signal: newTask.controller.signal,
maxConcurrency: 3, maxConcurrency: 3,
} }
); );

View File

@@ -442,6 +442,11 @@ export async function streamSplitAndUpload(
* 上传单行内容 * 上传单行内容
*/ */
async function uploadLine(line: string, index: number): Promise<void> { async function uploadLine(line: string, index: number): Promise<void> {
// 检查是否已取消
if (signal?.aborted) {
throw new Error("Upload cancelled");
}
if (!line.trim()) { if (!line.trim()) {
skippedEmptyCount++; skippedEmptyCount++;
return; return;
@@ -455,6 +460,11 @@ export async function streamSplitAndUpload(
const slices = sliceFile(lineFile, DEFAULT_CHUNK_SIZE); const slices = sliceFile(lineFile, DEFAULT_CHUNK_SIZE);
const checkSum = await calculateSHA256(slices[0]); const checkSum = await calculateSHA256(slices[0]);
// 检查是否已取消(计算哈希后)
if (signal?.aborted) {
throw new Error("Upload cancelled");
}
const formData = new FormData(); const formData = new FormData();
formData.append("file", slices[0]); formData.append("file", slices[0]);
formData.append("reqId", reqId.toString()); formData.append("reqId", reqId.toString());

View File

@@ -92,6 +92,14 @@ class Request {
}); });
} }
// 监听 AbortSignal 来中止请求
if (config.signal) {
config.signal.addEventListener("abort", () => {
xhr.abort();
reject(new Error("上传已取消"));
});
}
// 监听上传进度 // 监听上传进度
xhr.upload.addEventListener("progress", function (event) { xhr.upload.addEventListener("progress", function (event) {
if (event.lengthComputable) { if (event.lengthComputable) {