{"record":{"id":"cebb06d773d2e02b","repo":"yikart/AiToEarn","slug":"error-response-data-error-message-cebb06","errorCode":null,"errorMessage":"分片上传视频失败: ${error.response?.data?.error?.message || error.message}","messagePattern":"分片上传视频失败: (.+?)","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts","lineNumber":367,"sourceCode":"            }\n          })\n        );\n        \n        uploadResponses.push(data);\n      }\n      \n      // 使用最后一个分片的响应作为最终响应\n      const finalResponse = uploadResponses[totalChunkCount - 1];\n      \n      return {\n        ...finalResponse.data,\n        publish_id,\n        video_id: finalResponse.data?.video_id || finalResponse.data?.id,\n        init_data: initData, // 返回初始化数据，可能在发布时需要\n      };\n    } catch (error) {\n      this.logger.error('分片上传TikTok视频失败:', error.response?.data || error.message);\n      throw new BadRequestException(`分片上传视频失败: ${error.response?.data?.error?.message || error.message}`);\n    }\n  }\n  \n  /**\n   * 方式2：直接上传视频（PUT方式）\n   * @param accessToken 访问令牌\n   * @param videoBuffer 视频文件缓冲区\n   * @param initData 初始化返回的数据，包含 publish_id 和 upload_url\n   * @returns 上传结果\n   */\n  async directUploadVideo(\n    accessToken: string,\n    videoBuffer: Buffer,\n    initData: any\n  ): Promise<any> {\n    try {\n      const { publish_id, upload_url } = initData;\n      ","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts#L349-L385","documentation":"This is the catch-all wrapper for uploadVideoChunked: any failure during chunked upload of a TikTok video (network error, HTTP 4xx/5xx from the upload_url, timeout, or even the internal 'missing publish_id' BadRequestException from line 316) is logged and re-thrown as BadRequestException('分片上传视频失败: ...') at tiktok.service.ts:367. The message embeds error.response.data.error.message for Axios errors, else error.message.","triggerScenarios":"Any of: one of the 5MB chunk POSTs to upload_url returns a non-2xx status; the upload_url has expired (TikTok upload URLs are time-limited); Content-Range/Content-Length headers rejected; network interruption mid-upload; or the internal missing publish_id/upload_url throw bubbling into this catch.","commonSituations":"Videos where upload takes longer than TikTok's URL TTL, expired or revoked access tokens (401 from TikTok), file exceeding TikTok size/format limits mid-way, transient network failures on large multi-chunk uploads, and retrying after the URL already consumed.","solutions":["Read the logged '分片上传TikTok视频失败' detail to identify which chunk and HTTP status failed; handle 401 by refreshing the access token and restarting the upload from a fresh init call.","If the error indicates an expired/invalid upload_url, call initVideoUpload again to obtain a new URL and retry the full chunked upload.","Add per-chunk retry with exponential backoff (chunks are idempotent per Content-Range only if TikTok supports resumable ranges — otherwise restart the upload).","Verify video format/size meet TikTok requirements (mp4, within size/duration limits) before uploading."],"exampleFix":"// before\n} catch (error) {\n  this.logger.error('分片上传TikTok视频失败:', error.response?.data || error.message);\n  throw new BadRequestException(`分片上传视频失败: ${error.response?.data?.error?.message || error.message}`);\n}\n// after\n} catch (error) {\n  this.logger.error('分片上传TikTok视频失败:', error.response?.data || error.message);\n  if (error instanceof BadRequestException) throw error; // preserve specific internal errors\n  if (error.response?.status === 401) {\n    throw new UnauthorizedException('TikTok 访问令牌已过期，请重新授权后重试');\n  }\n  throw new BadRequestException(`分片上传视频失败: ${error.response?.data?.error?.message || error.message}`);\n}","handlingStrategy":"retry","validationCode":"if (!Buffer.isBuffer(videoBuffer) || videoBuffer.length === 0) {\n  throw new Error('videoBuffer must be a non-empty Buffer');\n}\nif (videoBuffer.length <= 10 * 1024 * 1024) {\n  // skip chunked path entirely; use single-shot upload\n}","typeGuard":"function isVideoBuffer(v) {\n  return Buffer.isBuffer(v) && v.length > 0;\n}","tryCatchPattern":"for (let attempt = 1; attempt <= 3; attempt++) {\n  try {\n    return await uploadVideo(accessToken, videoBuffer, initData);\n  } catch (e) {\n    if (!e.message.includes('分片上传视频失败') || attempt === 3) throw e;\n    initData = await initVideoUpload(accessToken, videoBuffer.length); // fresh URL\n    await new Promise(r => setTimeout(r, 2 ** attempt * 1000));\n  }\n}","preventionTips":["Use files under 10MB where possible to avoid the chunked path.","Re-init to get a fresh upload_url on every retry — URLs expire.","Refresh the access token if it may be near expiry before a long upload.","Confirm mp4 format and TikTok size limits before uploading.","Monitor the logger output for the failing chunk index and HTTP status."],"tags":["tiktok","upload","network","http-error"],"backgroundTag":"chunked-upload-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}