From 065fba7cf8aea58e51e8cd1ead01e93c58b4ba8f Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Wed, 16 Sep 2026 17:32:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(media-upload):=20H5=20pathToFile=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20multipart=20filename=20=E7=BC=BA=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-FRONTEND-MEDIA-UPLOAD-FIX-HOTFIX (2026-09-16) 诊断: 直接打后端 (curl) 验证后端 ext_rule 完全 OK, 所有 jpg/png/heic 标准 mime 都返回 code:0, 只有 filename=空 (无扩展名) 才会被拒. 所以问题在前端. 定位: common/js/media-upload.js pathToFile() H5 分支: 原代码 filePath.split('/').pop() 对 blob:http://xxx/uuid 取出 'uuid' (无扩展名) → new File([blob], 'uuid', ...) 没有 .jpg/.png 后缀 → multipart Content-Disposition: filename='uuid' → 后端 fileExt 规则检查 'uuid' 不在白名单 → 拒绝 修复: 提取 extractExt() 到模块顶层 (之前内联在 uploadViaSts 里), pathToFile 调用 extractExt(filePath, blob.type) 三段兜底: 1) filePath 最后一段正则 2) filePath.name 兜底 3) blob.type mimeHint 反查 EXT_MIME_MAP → png/jpg/heic 等 修复后 multipart filename = 'test-img1.png' (有扩展名), 后端 ext_rule 通过, 上传成功. E2E 验证 (verify_filename2.py): - 修复前: filename='test-img2' → 后端拒绝 - 修复后: filename='test-img2.png' → 后端接受 教训: PR-FRONTEND-MEDIA-UPLOAD-FIX 加 extractExt 时只修了 STS 路径, PHP 路径漏修; H5 blob URL + multipart 是双盲点 (前端不知道后端需要 filename 扩展名, 后端拿到 filename 才检查). 任何 file.toBlob() 或 URL.createObjectURL 出来的 file 都要在 File 构造时补扩展名. --- common/js/media-upload.js | 45 +++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/common/js/media-upload.js b/common/js/media-upload.js index 9f5063c..1649855 100644 --- a/common/js/media-upload.js +++ b/common/js/media-upload.js @@ -227,13 +227,19 @@ async function uploadViaPhp(filePath, kind, addon, agentId, knownSize = 0) { * { provider, bucket, region, accessKeyId, accessKeySecret, securityToken, * key_prefix_tpl, site_id, host? (single-URL 模式), endpoint? (qiniu 7 参兼容) } */ -async function uploadViaSts(filePath, kind, addon, agentId, config, knownSize = 0) { - // PR-FRONTEND-MEDIA-UPLOAD-FIX (2026-09-16): 用更稳的扩展名提取 - // H5 blob URL (blob:https://xxx/uuid) 无扩展名 → split('.').pop() 会拿整条 URL - // 优先 file.name (H5 file / APP tempFile.name), 都没有就 blob URL → 'bin' +/** + * PR-FRONTEND-MEDIA-UPLOAD-FIX (2026-09-16): 三段兜底扩展名提取 + * 1) filePath 最后一段正则匹配 .xxx + * 2) filePath.name 兜底 (H5 File 对象 / APP tempFile.name) + * 3) mimeHint 反查 EXT_MIME_MAP (H5 blob URL 没扩展名但 blob.type 通常是 image/png) + * 4) 最终 'bin' (防 URL 片段被误当扩展名) + * + * PR-FRONTEND-MEDIA-UPLOAD-FIX-HOTFIX (2026-09-16): 提到模块顶层, + * 之前只在 uploadViaSts 里, pathToFile 引用不到 → "extractExt is not defined" + */ +function extractExt(filePath, mimeHint = '') { let ext = 'bin' if (typeof filePath === 'string') { - // 1) 优先从 filePath 里找 .xxx (路径最后一段) const lastSeg = filePath.split('?')[0].split('/').pop() || '' const m = lastSeg.match(/\.([a-z0-9]+)$/i) if (m) ext = m[1].toLowerCase() @@ -242,7 +248,22 @@ async function uploadViaSts(filePath, kind, addon, agentId, config, knownSize = const m2 = String(filePath.name).match(/\.([a-z0-9]+)$/i) if (m2) ext = m2[1].toLowerCase() } - if (!/^[a-z0-9]{2,5}$/.test(ext)) ext = 'bin' // 防止 ext 是 'https' 这种 URL 片段 + // 3) mime→ext 反查 (PR-FRONTEND-MEDIA-UPLOAD-FIX-HOTFIX): blob URL 无扩展名但 + // blob.type 是 image/png 这种, 反查 EXT_MIME_MAP → png + if (ext === 'bin' && mimeHint) { + for (const [e, m] of Object.entries(EXT_MIME_MAP)) { + if (m === mimeHint) { ext = e; break } + } + } + if (!/^[a-z0-9]{2,5}$/.test(ext)) ext = 'bin' + return ext +} + +async function uploadViaSts(filePath, kind, addon, agentId, config, knownSize = 0) { + // PR-FRONTEND-MEDIA-UPLOAD-FIX (2026-09-16): 用更稳的扩展名提取 + // H5 blob URL (blob:https://xxx/uuid) 无扩展名 → split('.').pop() 会拿整条 URL + // 优先 file.name (H5 file / APP tempFile.name), 都没有就 blob URL → 'bin' + const ext = extractExt(filePath) // 1. 拿 STS 凭证 (addon 模式 URL) const stsUrl = (addon === 'aikefu' ? '/aikefu/api/upload/' : '/aiagent/api/upload/') + 'ossSts' @@ -393,7 +414,17 @@ async function pathToFile(filePath, kind) { // #ifdef H5 const res = await fetch(filePath) const blob = await res.blob() - return new File([blob], filePath.split('/').pop() || `${kind}.bin`, { type: blob.type }) + // PR-FRONTEND-MEDIA-UPLOAD-FIX-HOTFIX (2026-09-16): H5 blob URL 路径修复扩展名 + // 原代码 `filePath.split('/').pop()` 对 blob:http://xxx/uuid 取出 'uuid' (无扩展名) + // → 后端 multipart filename 缺失扩展名 → ext_rule 拒绝 "上传文件后缀不允许" + // extractExt 三段兜底: 1) lastSegment 正则 2) file.name 3) 'bin' 最终兜底 + // 若 ext='bin', 走 guessMime(EXT_MIME_MAP) 看能不能从 mime 反推 jpg/png (mime→ext) + const rawName = filePath.split('/').pop() || `${kind}.bin` + const ext = extractExt(filePath, blob.type) + const finalName = ext !== 'bin' ? `${rawName}.${ext}` : rawName + // blob.type 通常是 image/png 或空; 空时用 guessMime 兜底 + const mime = blob.type || guessMime(ext) + return new File([blob], finalName, { type: mime }) // #endif }