From 1181aea3b9371c233237f8e545dc8c6107189232 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 13 Oct 2026 20:44:09 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E5=87=AD=E8=AF=81=E5=88=86=E7=89=87?= =?UTF-8?q?=E9=81=87=20HTTP=20=E9=A2=84=E7=AD=BE=E5=90=8D=E6=97=B6?= =?UTF-8?q?=E5=9B=9E=E9=80=80=E6=9C=8D=E5=8A=A1=E7=AB=AF=E4=BB=A3=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- src/api/business/voucher-manage.js | 11 +++++++ src/views/business/voucher-manage-detail.vue | 22 ++++++++++--- src/views/business/voucher-manage.vue | 34 ++++++++++++++------ 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/api/business/voucher-manage.js b/src/api/business/voucher-manage.js index 2b65bfe..c8c7a79 100644 --- a/src/api/business/voucher-manage.js +++ b/src/api/business/voucher-manage.js @@ -52,6 +52,17 @@ export const createFileTask = data => request({ url: `${fileTaskBaseUrl}/createFileTask`, method: 'post', data }); export const getPartUploadUrl = (id, partNumber) => request({ url: `${fileTaskBaseUrl}/getUploadUrl`, method: 'get', params: { id, partNumber } }); +export const uploadPartByProxy = (id, partNumber, partBlob) => { + const formData = new FormData(); + formData.append('file', partBlob, `part-${partNumber}.bin`); + return request({ + url: `${fileTaskBaseUrl}/uploadPart`, + method: 'post', + params: { id, partNumber }, + data: formData, + timeout: 600000, + }); +}; export const updateFileTask = data => request({ url: `${fileTaskBaseUrl}/updateFileTask`, method: 'post', data }); export const pauseFileTask = ids => diff --git a/src/views/business/voucher-manage-detail.vue b/src/views/business/voucher-manage-detail.vue index 3d77c96..478069d 100644 --- a/src/views/business/voucher-manage-detail.vue +++ b/src/views/business/voucher-manage-detail.vue @@ -96,11 +96,25 @@ const uploadReplaceFile = async file => { if (!task?.id || !task.objectKey) throw new Error('创建分片上传任务失败'); replaceProgress.value = 0; for (let partNumber = 1; partNumber <= task.chunkTotal; partNumber += 1) { - const urlRes = await api.getPartUploadUrl(task.id, partNumber); const part = file.slice((partNumber - 1) * chunkSize, Math.min(partNumber * chunkSize, file.size)); - const response = await fetch(urlRes.data?.data, { method: 'PUT', body: part }); - if (!response.ok) throw new Error(`分片${partNumber}上传失败`); - const etag = String(response.headers.get('etag') || '').replaceAll('"', ''); + const urlRes = await api.getPartUploadUrl(task.id, partNumber); + const url = urlRes.data?.data; + let etag = ''; + const needProxy = + typeof window !== 'undefined' && + window.location.protocol === 'https:' && + String(url || '') + .trim() + .toLowerCase() + .startsWith('http:'); + if (!url || needProxy) { + const proxyRes = await api.uploadPartByProxy(task.id, partNumber, part); + etag = String(proxyRes.data?.data || '').replaceAll('"', ''); + } else { + const response = await fetch(url, { method: 'PUT', body: part }); + if (!response.ok) throw new Error(`分片${partNumber}上传失败`); + etag = String(response.headers.get('etag') || '').replaceAll('"', ''); + } if (!etag) throw new Error('未获取到分片 ETag'); await api.updateFileTask({ id: task.id, partNumber, etag }); replaceProgress.value = Math.round((partNumber / task.chunkTotal) * 100); diff --git a/src/views/business/voucher-manage.vue b/src/views/business/voucher-manage.vue index 8bfbe45..79645e0 100644 --- a/src/views/business/voucher-manage.vue +++ b/src/views/business/voucher-manage.vue @@ -837,6 +837,30 @@ const calculateMd5 = async file => { const buffer = await file.arrayBuffer(); return md5.base64(buffer); }; +const needsUploadProxy = url => + typeof window !== 'undefined' && + window.location.protocol === 'https:' && + String(url || '') + .trim() + .toLowerCase() + .startsWith('http:'); +const uploadPartWithFallback = async (taskId, partNumber, part, signal) => { + const urlRes = await api.getPartUploadUrl(taskId, partNumber); + if (signal?.aborted) throw new DOMException('上传已暂停', 'AbortError'); + const url = urlRes.data?.data; + // HTTPS 页面直连 http://IP MinIO 会被 Mixed Content 拦截,回退服务端代传 + if (!url || needsUploadProxy(url)) { + const proxyRes = await api.uploadPartByProxy(taskId, partNumber, part); + return String(proxyRes.data?.data || '').replaceAll('"', ''); + } + const response = await fetch(url, { + method: 'PUT', + body: part, + signal, + }); + if (!response.ok) throw new Error(`分片${partNumber}上传失败`); + return String(response.headers.get('etag') || '').replaceAll('"', ''); +}; const uploadParts = async (file, task, sessionId) => { uploading.value = true; activeUploadTaskId.value = task.id; @@ -851,21 +875,13 @@ const uploadParts = async (file, task, sessionId) => { ) { ensureUploadSession(sessionId, task.id); if (abortController.signal.aborted) throw new DOMException('上传已暂停', 'AbortError'); - const urlRes = await api.getPartUploadUrl(task.id, partNumber); ensureUploadSession(sessionId, task.id); if (abortController.signal.aborted) throw new DOMException('上传已暂停', 'AbortError'); - const url = urlRes.data?.data; const part = file.slice( (partNumber - 1) * chunkSize, Math.min(partNumber * chunkSize, file.size) ); - const response = await fetch(url, { - method: 'PUT', - body: part, - signal: abortController.signal, - }); - if (!response.ok) throw new Error(`分片${partNumber}上传失败`); - const etag = String(response.headers.get('etag') || '').replaceAll('"', ''); + const etag = await uploadPartWithFallback(task.id, partNumber, part, abortController.signal); if (!etag) throw new Error('MinIO 未返回分片 ETag,请确认跨域配置已暴露 ETag 响应头'); await api.updateFileTask({ id: task.id, partNumber, etag }); ensureUploadSession(sessionId, task.id);