{"record":{"id":"ca582252f244c6d2","repo":"yikart/AiToEarn","slug":"error-ca5822","errorCode":null,"errorMessage":"上传封面失败,失败原因:","messagePattern":"上传封面失败,失败原因:","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"project/aitoearn-electron/electron/plat/shipinhao/index.ts","lineNumber":845,"sourceCode":"\n      const uploadId = uploadIdRes.UploadID;\n\n      // 开始上传\n      const uploadPartRes = await this.uploadFile(\n        this.uploadpartdfsUrl +\n          `?UploadID=${uploadId}&PartNumber=1&QuickUpload=2`,\n        fileData,\n        {\n          Authorization: uploadParams.authKey,\n          'X-Arguments': uploadArgumentsString,\n          'Content-Type': 'application/octet-stream',\n        },\n        undefined,\n        proxy,\n      );\n\n      if (!uploadPartRes.ETag) {\n        throw new Error(\n          '上传封面失败,失败原因:' + uploadPartRes.data?.['X-Errno'] ||\n            '未知错误',\n        );\n      }\n\n      // 上传成功\n      const uploadPartInfo = [\n        {\n          PartNumber: 1,\n          ETag: uploadPartRes.ETag,\n        },\n      ];\n\n      // 完成分片上传\n      const uploadCompleteRes = await this.uploadFile(\n        this.uploadCompleteUrl + `?UploadID=${uploadId}`,\n        Buffer.from(\n          JSON.stringify({","sourceCodeStart":827,"sourceCodeEnd":863,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/electron/plat/shipinhao/index.ts#L827-L863","documentation":"After uploading cover data, the service checks uploadPartRes.ETag; if absent it throws '上传封面失败,失败原因:' + X-Errno. Missing ETag means the server did not accept the part upload. Note a bug: the string concatenation binds before ||, so when X-Errno is missing the expression evaluates to the truthy prefix string and '未知错误' is never used.","triggerScenarios":"uploadCoverFile → part-upload (uploadFile) response without ETag: COS rejecting the chunk due to bad signature, wrong Content-MD5, session expiry, or an error body in data['X-Errno'].","commonSituations":"Expired upload session between apply and part upload, corrupted cover file bytes, proxy interrupting the PUT, or COS returning X-Errno (e.g. signature mismatch) instead of ETag.","solutions":["Read the X-Errno value from the response data to identify the COS rejection reason.","Re-request upload params and retry the cover upload from scratch.","Validate the cover image file (exists, non-empty, correct format) before upload.","Fix the operator-precedence bug so '未知错误' shows when X-Errno is absent: wrap the concatenation in parentheses."],"exampleFix":"// before\nthrow new Error(\n  '上传封面失败,失败原因:' + uploadPartRes.data?.['X-Errno'] ||\n    '未知错误',\n);\n// after\nthrow new Error(\n  '上传封面失败,失败原因:' + (uploadPartRes.data?.['X-Errno'] ?? '未知错误'),\n);","handlingStrategy":"type-guard","validationCode":"import fs from 'fs';\nif (!fs.existsSync(coverPath) || fs.statSync(coverPath).size === 0) {\n  throw new Error('invalid cover file');\n}","typeGuard":"function hasETag(res: unknown): res is { ETag: string } {\n  return typeof (res as any)?.ETag === 'string' && (res as any).ETag.length > 0;\n}","tryCatchPattern":"try {\n  await service.uploadCoverFile(/*...*/);\n} catch (e) {\n  if (e.message.includes('上传封面失败')) {\n    const errno = e.message.split('失败原因:')[1];\n    console.error('cover part upload rejected, X-Errno:', errno);\n  }\n}","preventionTips":["Check ETag on every part response before proceeding to complete","Note the operator-precedence bug: 'prefix' + x || y never yields y — use parentheses","Refresh upload params if the session may have expired mid-flow","Validate image integrity before sending bytes"],"tags":["upload","cover","shipinhao","cos","operator-precedence"],"backgroundTag":"multipart-part-upload-rejected","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}