{"record":{"id":"0371202eb8715c64","repo":"yikart/AiToEarn","slug":"error-037120","errorCode":null,"errorMessage":"上传确认失败","messagePattern":"上传确认失败","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"project/aitoearn-web/src/api/materials/material.api.ts","lineNumber":94,"sourceCode":"  })\n\n  if (!res || res.code !== 0)\n    throw new Error(res?.message || '获取上传签名失败')\n\n  return res.data\n}\n\nasync function confirmUpload(assetId: string, fallbackUrl: string, publicUploadId?: string) {\n  const confirmResponse = await request<ConfirmUploadData>({\n    url: getConfirmRequestPath(assetId, publicUploadId),\n    method: 'POST',\n    data: {\n      id: assetId,\n    },\n  })\n\n  if (!confirmResponse || confirmResponse.code !== 0)\n    throw new Error(confirmResponse?.message || '上传确认失败')\n\n  return confirmResponse.data?.url || fallbackUrl\n}\n\n/**\n * 上传文件到OSS (前端直传 AWS S3)\n */\nexport async function uploadToOss(\n  file: File | Blob,\n  options?: UploadToOssOptions | ((prog: number) => void),\n): Promise<string> {\n  try {\n    const opts: UploadToOssOptions\n      = typeof options === 'function' ? { onProgress: options } : (options ?? {})\n\n    if (opts.signal?.aborted) {\n      throw new DOMException('上传已取消', 'AbortError')\n    }","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-web/src/api/materials/material.api.ts#L76-L112","documentation":" thrown by confirmUpload after a client-side direct OSS/S3 upload, when the confirm API response is missing or its code !== 0 (the app's success code). The backend uses this call to mark the asset uploaded and return its final URL; failure means the object may exist in storage but is not registered, so the function throws '上传确认失败' unless the server supplied a message.","triggerScenarios":"uploadToOss completes the PUT to the presigned URL and then calls confirmUpload, but the confirm endpoint fails: expired session, assetId not found, backend DB error, or the request layer returned undefined (network error).","commonSituations":"Long uploads outliving the auth token; confirm endpoint 5xx under load; assetId mismatch after the presign response shape changed; double-submit races deleting the asset record.","solutions":["Inspect confirmResponse.code/message for the server-side reason.","Re-authenticate and retry confirmUpload alone — the file is already in OSS, no need to re-upload.","Verify assetId comes from the presignedData response unchanged.","Check backend logs for the confirm endpoint failure (DB/asset-not-found)."],"exampleFix":"// before\nif (!confirmResponse || confirmResponse.code !== 0)\n  throw new Error(confirmResponse?.message || '上传确认失败')\n// after\nif (!confirmResponse || confirmResponse.code !== 0)\n  throw new Error(`上传确认失败: ${confirmResponse?.message ?? 'no response'} (assetId=${assetId})`)","handlingStrategy":"retry","validationCode":"// confirm only after a verified successful PUT\nif (!uploadResponse.ok) throw new Error('上传未完成，跳过确认')\n// then call confirmUpload(assetId, fallbackUrl)","typeGuard":"function isConfirmResult(r: unknown): r is { code: number; message?: string; data?: { url?: string } } {\n  const o = r as any\n  return !!o && typeof o.code === 'number'\n}","tryCatchPattern":"try {\n  const url = await confirmUpload(assetId, fallbackUrl)\n}\ncatch (e) {\n  // file already in OSS — safe to retry confirm alone\n  await sleep(1000)\n  const url = await confirmUpload(assetId, fallbackUrl)\n}","preventionTips":["Make confirmUpload idempotent so it can be retried safely after the PUT succeeded.","Persist assetId + fallbackUrl so confirm can be retried after page reloads.","Refresh auth tokens before long uploads complete to avoid expired-session confirms.","Alert on confirm-endpoint 5xx; separate presign/PUT/confirm failures in monitoring."],"tags":["upload","oss","api","confirm"],"backgroundTag":"presigned-upload-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}