{"record":{"id":"d0de4f6572d49df1","repo":"yikart/AiToEarn","slug":"error-d0de4f","errorCode":null,"errorMessage":"初始化失败，缺少必要的上传参数","messagePattern":"初始化失败，缺少必要的上传参数","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts","lineNumber":522,"sourceCode":"      description?: string;\n      privacyStatus?: string;\n      disableComment?: boolean;\n      disableDuet?: boolean;\n      disableStitch?: boolean;\n      videoCoverTimestampMs?: number;\n      tags?: string[];\n    },\n    pollInterval: number = 2000,  \n    maxRetries: number = 30\n  ): Promise<any> {\n    try {\n      // 1. 第一步：初始化视频发布\n      this.logger.debug('第一步：初始化视频发布...');\n      const videoSize = videoBuffer.length;\n      const initResult = await this.initVideoPublish(accessToken, videoSize, videoInfo);\n      \n      if (!initResult.publish_id || !initResult.upload_url) {\n        throw new BadRequestException('初始化失败，缺少必要的上传参数');\n      }\n      \n      const { publish_id } = initResult;\n      \n      // 2. 第二步：上传视频文件\n      this.logger.debug(`第二步：上传视频文件，publish_id: ${publish_id}...`);\n      await this.directUploadVideo(accessToken, videoBuffer, initResult);\n      \n      // 3. 第三步：轮询视频发布状态 // 每分钟不超过30次\n      this.logger.debug(`第三步：轮询视频发布状态，publish_id: ${publish_id}...`);\n      \n      // 存储发布记录\n      const maxRecord = await this.pubRecordModel.findOne().sort({ id: -1 });\n      const newId = maxRecord ? maxRecord.id + 1 : 1;\n      \n      const pubRecord = await this.pubRecordModel.create({\n        id: newId,\n        userId,","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts#L504-L540","documentation":"Thrown by uploadAndPublishVideo (tiktok.service.ts:522) after initVideoPublish returns a result that lacks either publish_id or upload_url. The TikTok Content Posting API's init endpoint (/v2/post/publish/video/init/) is expected to return both a publish_id and a chunked upload URL; if either is missing, the three-step upload cannot proceed. This is a defensive invariant check against malformed/unexpected TikTok API responses.","triggerScenarios":"TikTok's /v2/post/publish/video/init/ responds 2xx but the body's data object omits publish_id or upload_url — e.g. when the app lacks the video.publish scope, the file size exceeds the allowed range, the draft/PULL_FROM_URL vs FILE_UPLOAD mode is mismatched, or the access token's user has not authorized video posting.","commonSituations":"Apps approved without the video.upload/video.publish scopes hitting init with a valid-looking 200; oversized or zero-length video buffers rejected silently; TikTok API version drift changing response field names (e.g. data nested differently); sandbox vs production environment differences in returned fields.","solutions":["Log the full initResult before the throw to see exactly which field is missing and what TikTok actually returned.","Verify the access token has video.publish and video.upload scopes and that the user re-authorized after scope changes.","Confirm videoSize (videoBuffer.length) is within TikTok's accepted range (non-zero, under the max) before calling init.","Check that initVideoPublish targets the correct endpoint and parses data.data from the response, not data.","Add explicit handling for TikTok's partial-success/error codes in the init response body."],"exampleFix":"// before\nif (!initResult.publish_id || !initResult.upload_url) {\n  throw new BadRequestException('初始化失败，缺少必要的上传参数');\n}\n// after\nthis.logger.warn(`initVideoPublish result: ${JSON.stringify(initResult)}`);\nif (!initResult?.publish_id) {\n  throw new BadRequestException(`初始化失败：缺少 publish_id（响应: ${JSON.stringify(initResult)}）`);\n}\nif (!initResult?.upload_url) {\n  throw new BadRequestException(`初始化失败：缺少 upload_url（响应: ${JSON.stringify(initResult)}`);\n}","handlingStrategy":"validation","validationCode":"const init = await tiktokService.initVideoPublish(token, buffer.length, info);\nif (!init?.publish_id || !init?.upload_url) {\n  throw new Error(`init incomplete: publish_id=${!!init?.publish_id}, upload_url=${!!init?.upload_url}, raw=${JSON.stringify(init)}`);\n}","typeGuard":"function hasInitFields(r: unknown): r is { publish_id: string; upload_url: string } {\n  const o = r as any;\n  return !!o && typeof o.publish_id === 'string' && o.publish_id.length > 0\n    && typeof o.upload_url === 'string' && o.upload_url.length > 0;\n}","tryCatchPattern":"try {\n  await tiktokService.uploadAndPublishVideo(token, userId, accountId, buffer, info);\n} catch (e) {\n  if (e.message.includes('缺少必要的上传参数')) {\n    // re-auth scopes / inspect init response, then retry once\n  } else throw e;\n}","preventionTips":["Assert video.publish and video.upload scopes at token acquisition time.","Validate buffer length is > 0 and within TikTok's size limits before upload.","Log raw init responses in a debug mode to detect TikTok API shape drift early.","Pin and review TikTok Content Posting API version notes regularly."],"tags":["tiktok","api-response","missing-field","upload"],"backgroundTag":"api-response-missing-required-field","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}