{"record":{"id":"7817189e380ee935","repo":"yikart/AiToEarn","slug":"uploadresponse-statustext","errorCode":null,"errorMessage":"上传失败: ${uploadResponse.statusText}","messagePattern":"上传失败: \\$\\{uploadResponse\\.statusText\\}","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"project/aitoearn-web/src/api/materials/material.api.ts","lineNumber":190,"sourceCode":"        xhr.open('PUT', uploadUrl)\n        xhr.setRequestHeader('Content-Type', contentType)\n        xhr.send(uploadFile)\n      })\n    }\n    else {\n      // 不使用进度回调的简单版本 (R2 使用 PUT 请求)\n\n      const uploadResponse = await fetch(uploadUrl, {\n        method: 'PUT',\n        body: uploadFile,\n        headers: {\n          'Content-Type': contentType,\n        },\n        signal: opts.signal,\n      })\n\n      if (!uploadResponse.ok) {\n        throw new Error(`上传失败: ${uploadResponse.statusText}`)\n      }\n\n      // 返回确认接口返回的最终访问URL\n      return confirmUpload(presignedData.id, presignedData.url, opts.publicUploadId)\n    }\n  }\n  catch (error) {\n    console.error('上传文件失败:', error)\n    throw error\n  }\n}\n\n/**\n * 批量删除媒体资源\n * 根据ID列表批量删除媒体资源。\n */\nexport function batchDeleteMedia(ids: string[]) {\n  return http.delete('media/ids', { ids })","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-web/src/api/materials/material.api.ts#L172-L208","documentation":" thrown inside uploadToOss when the direct PUT of the file to the presigned OSS/S3 URL returns a non-ok response. It surfaces uploadResponse.statusText, which for CORS-direct uploads is often generic (e.g. 'Forbidden'), since the browser cannot read the error body cross-origin.","triggerScenarios":"PUTting the file to the presigned URL when: presigned data expired (signature/expiration mismatch), Content-Type header doesn't exactly match the one signed, key doesn't match, bucket CORS blocks the request, or storage returns 5xx.","commonSituations":"Slow networks where the presigned URL expires before upload finishes; contentType changed between presign and PUT (e.g. after optimizeImageForUpload re-encodes the file to a different type); bucket CORS misconfiguration; clock skew invalidating signatures.","solutions":["Ensure the Content-Type sent in the PUT exactly equals the type used when requesting the presigned POST data.","Shorten presign-to-upload time or increase the presigned expiration; re-request presigned data on retry.","Verify the OSS/S3 bucket CORS configuration allows PUT from the web origin.","Log uploadResponse.status alongside statusText — statusText is often empty or generic in browsers.","Confirm optimizeImageForUpload hasn't changed the file type after presigning."],"exampleFix":"// before\nconst fileName = getUploadFileName(uploadFile, opts.publicUploadId)\nconst presignedData = await getPresignedPostData(...) // signed with old contentType\n// after (presign AFTER optimization, with the final type)\nconst uploadFile = await optimizeImageForUpload(file, { signal })\nconst contentType = uploadFile.type\nconst presignedData = await getPresignedPostData({ fileName, fileSize: uploadFile.size, contentType })","handlingStrategy":"retry","validationCode":"// before PUT: confirm the type matches the signed type and URL is fresh\nif (contentType !== presignedData.contentType) throw new Error('Content-Type 与签名不一致')\nif (Date.now() > presignedData.expiresAt) throw new Error('签名已过期，请重新获取')","typeGuard":"function isUploadHttpError(e: unknown): e is Error {\n  return e instanceof Error && e.message.startsWith('上传失败:')\n}","tryCatchPattern":"try {\n  const url = await uploadToOss(file, { signal })\n}\ncatch (e) {\n  if (e instanceof Error && e.message.startsWith('上传失败:')) {\n    const url = await uploadToOss(file, { signal }) // fresh presign inside retry\n  }\n  else throw e\n}","preventionTips":["Request presigned data AFTER image optimization so contentType matches the final file.","Set the exact same Content-Type header as signed; never let fetch infer it.","Configure bucket CORS for PUT from your web origin and verify with a browser test.","Refresh presigned URLs on retry instead of reusing possibly expired ones.","Watch for clock skew on clients in environments with expired-signature patterns."],"tags":["upload","oss","cors","presigned-url"],"backgroundTag":"presigned-upload-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}