{"record":{"id":"38ee0f0cb6967632","repo":"yikart/AiToEarn","slug":"id-38ee0f","errorCode":null,"errorMessage":"无效的视频上传结果，缺少视频ID","messagePattern":"无效的视频上传结果，缺少视频ID","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts","lineNumber":631,"sourceCode":"   * 发布视频（原始方式）\n   * @param accessToken 访问令牌\n   * @param userId 用户ID\n   * @param accountId TikTok账号ID\n   * @param videoDto 视频发布参数\n   * @param uploadResult 上传结果，包含视频ID和初始化数据\n   * @returns 发布结果\n   */\n  async publishVideo(\n    accessToken: string,\n    userId: string,\n    accountId: string,\n    videoDto: CreateVideoDto,\n    uploadResult: any\n  ): Promise<any> {\n    try {\n      const videoId = uploadResult.video_id || (uploadResult.init_data?.video_id);\n      if (!videoId) {\n        throw new BadRequestException('无效的视频上传结果，缺少视频ID');\n      }\n\n      const params: any = {\n        video_id: videoId,\n        text: videoDto.description,\n        disable_comment: false,\n        disable_duet: false,\n        privacy_level: videoDto.private ? 'private' : 'public'\n      };\n\n      if (videoDto.hashtags && videoDto.hashtags.length > 0) {\n        // 添加话题标签\n        const hashtags = videoDto.hashtags.map(tag => `#${tag.replace(/^#/, '')}`).join(' ');\n        params.text = `${params.text} ${hashtags}`;\n      }\n\n      // 如果有初始化数据，添加必要的发布参数\n      if (uploadResult.init_data && uploadResult.init_data.publish_params) {","sourceCodeStart":613,"sourceCodeEnd":649,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts#L613-L649","documentation":"Thrown by publishVideo (tiktok.service.ts:631) when the uploadResult passed in contains neither video_id nor init_data.video_id. publishVideo is step two of the legacy publish flow: it needs the video id produced by the upload step to attach to the /v2/video/publish/ call. An uploadResult missing these fields means the preceding upload did not actually yield a usable video.","triggerScenarios":"Caller passes the raw response of a failed or differently-shaped upload (e.g. { success: true } with no data, or a direct-upload response whose id lives under data.data.video_id), so uploadResult.video_id and uploadResult.init_data?.video_id are both undefined.","commonSituations":"Mixing responses between the new three-step API and the legacy upload flow; passing the whole Axios response instead of response.data; upload actually failed upstream but its error was swallowed; TikTok response-shape changes nesting video_id differently.","solutions":["Inspect the actual uploadResult object (log it) to find where the video id really lives and pass that shape.","Ensure you pass response.data of the upload call, not the Axios response wrapper.","Check the upload step's success status before calling publishVideo; abort if it reported failure.","Align the upload method with the legacy flow expected by publishVideo, or switch entirely to the three-step uploadAndPublishVideo.","Validate uploadResult in the caller before invoking publishVideo to fail earlier with a clearer message."],"exampleFix":"// before\nawait tiktokService.publishVideo(token, userId, accountId, dto, uploadResponse);\n// after\nconst uploadData = uploadResponse?.data?.data ?? uploadResponse?.data;\nif (!uploadData?.video_id && !uploadData?.init_data?.video_id) {\n  throw new Error(`upload returned no video_id: ${JSON.stringify(uploadData)}`);\n}\nawait tiktokService.publishVideo(token, userId, accountId, dto, uploadData);","handlingStrategy":"type-guard","validationCode":"const vid = uploadResult?.video_id ?? uploadResult?.init_data?.video_id;\nif (!vid) {\n  throw new Error(`no video_id in upload result: ${JSON.stringify(uploadResult)}`);\n}","typeGuard":"function hasVideoId(r: unknown): r is { video_id?: string; init_data?: { video_id: string } } {\n  const o = r as any;\n  return typeof o?.video_id === 'string' && o.video_id.length > 0\n    || typeof o?.init_data?.video_id === 'string' && o.init_data.video_id.length > 0;\n}","tryCatchPattern":"try {\n  await tiktokService.publishVideo(token, userId, accountId, dto, uploadResult);\n} catch (e) {\n  if (e.message.includes('缺少视频ID')) {\n    logger.error('upload result shape mismatch', uploadResult);\n    // fix normalization of the upload response, then retry\n  } else throw e;\n}","preventionTips":["Normalize upload responses (unwrap Axios .data) before passing to publishVideo.","Keep upload and publish steps from the same API generation — don't mix three-step and legacy flows.","Unit-test the upload response shape against a recorded fixture.","Fail fast in the caller when the upload step reported an error instead of swallowing it."],"tags":["tiktok","missing-field","api-response","validation"],"backgroundTag":"api-response-missing-required-field","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}