{"record":{"id":"4f407dc9dbd5b17b","repo":"yikart/AiToEarn","slug":"videouploadvidnotfound","errorCode":"VideoUploadVidNotFound","errorMessage":"VideoUploadVidNotFound","messagePattern":"VideoUploadVidNotFound","errorType":"error_code","errorClass":"AppException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/volcengine/services/upload.service.ts","lineNumber":230,"sourceCode":"\n    this.logger.debug({\n      fileName,\n      fileExtension,\n      fileSize,\n      url,\n    }, '[downloadUrlAndUploadAsStream] 准备上传')\n\n    const uploadResult = await this.uploadMaterial({\n      SpaceName: this.config.spaceName,\n      Content: response.data,\n      FileSize: fileSize,\n      FileName: fileName,\n      FileExtension: fileExtension,\n    })\n\n    const vid = uploadResult.Data?.Vid\n    if (!vid) {\n      throw new AppException(ResponseCode.VideoUploadVidNotFound)\n    }\n\n    return vid\n  }\n\n  /**\n   * 批量上传 URL 并获取 vids\n   * 使用下载后流式上传的方式，比 URL 批量拉取更快\n   */\n  async batchUploadUrlsAndGetVids(\n    urlInputs: Array<{ url: string, options?: VideoUrlInput }>,\n  ): Promise<string[]> {\n    const results = await Promise.allSettled(\n      urlInputs.map(({ url, options }, index) =>\n        this.downloadUrlAndUploadAsStream(url, options)\n          .then((vid): UploadSuccess => ({ success: true, vid, url, index }))\n          .catch((error): UploadFailure => {\n            const errorMessage = getErrorMessage(error)","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/volcengine/services/upload.service.ts#L212-L248","documentation":"downloadUrlAndUploadAsStream 在调用 uploadMaterial 成功后，期望上传结果 uploadResult.Data.Vid 存在；若火山 VOD 返回的 Result 中没有 Vid，则抛出 VideoUploadVidNotFound，表示上传链路完成但未得到媒体 ID。","triggerScenarios":"UploadMaterial 响应无 Error 但 Data.Vid 缺失：SpaceName 配置错误/不存在、GetMeta Function 未返回媒体信息、响应结构变化、上传的实际上下文被 VOD 接受但未生成媒体记录。","commonSituations":"VOLCSPACE_NAME 环境变量指向错误或未初始化的空间；火山 VOD 服务未在该空间开通媒体上传；SDK 升级后 Result.Data 结构改变；上传了非视频内容导致无 Vid。","solutions":["记录 uploadResult 完整内容确认 Data 是否存在及结构。","核对 config.spaceName 与火山 VOD 控制台中的空间名一致且已开通。","确认上传 Functions 配置（GetMeta/AddOptionInfo）未被裁剪。","检查 @volcengine/openapi SDK 版本是否与响应结构匹配，必要时升级或降级。"],"exampleFix":"// before\nconst vid = await uploadService.downloadUrlAndUploadAsStream(url)\n// after\nlet vid: string\ntry {\n  vid = await uploadService.downloadUrlAndUploadAsStream(url)\n} catch (e) {\n  if ((e as any)?.code === ResponseCode.VideoUploadVidNotFound) {\n    logger.error({ spaceName: process.env.VOLCSPACE_NAME }, 'Upload ok but no Vid; check VOD space config')\n  }\n  throw e\n}","handlingStrategy":"validation","validationCode":"if (!process.env.VOLCSPACE_NAME) throw new Error('VOLCSPACE_NAME must be set')\n// verify space exists once at startup via ListSpace or console check","typeGuard":"function hasVid(r: unknown): r is { Data: { Vid: string } } {\n  return r != null && typeof r === 'object'\n    && typeof (r as any).Data?.Vid === 'string' && (r as any).Data.Vid.length > 0\n}","tryCatchPattern":"try {\n  vid = await downloadUrlAndUploadAsStream(url)\n} catch (e) {\n  if ((e as any)?.code === ResponseCode.VideoUploadVidNotFound) {\n    logger.error({ spaceName: config.spaceName }, 'No Vid returned; verify VOD space and SDK response shape')\n  }\n  throw e\n}","preventionTips":["启动时校验 SpaceName 与火山 VOD 控制台一致","保持 uploadMaterial 的 Functions（GetMeta/AddOptionInfo）完整","升级火山 SDK 后用单元用例断言 Data.Vid 存在","保留 uploadResult 完整响应日志以便排查"],"tags":["volcengine-vod","upload","missing-vid","config"],"backgroundTag":"missing-upload-result-id","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}