{"record":{"id":"da88080a29fc9e12","repo":"yikart/AiToEarn","slug":"playlistid-playlistitemsids","errorCode":null,"errorMessage":"只能选择一个参数: playlistId, playlistItemsIds","messagePattern":"只能选择一个参数: playlistId, playlistItemsIds","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"project/aitoearn-electron/server/src/modules/plat/youtube/youtube.controller.ts","lineNumber":488,"sourceCode":"  }\n\n  @ApiOperation({ summary: '获取播放列表项' })\n  // @Public()\n  @Get('playlist/items/list')\n  async getPlayItemsList(\n    @GetToken() token: TokenInfo,\n    @Query('accountId') accountId: string,\n    @Query('playlistId') playlistId: string,\n    @Query('id') playlistItemsIds: string,\n    @Query('maxResults') maxResults?: string,\n    @Query('pageToken') pageToken?: string,\n    ) {\n    // 校验确保只有一个参数传递\n    const params = [playlistId, playlistItemsIds];\n    const nonEmptyParams = params.filter(param => param !== undefined && param !== null && param !== '');\n\n    if (nonEmptyParams.length > 1) {\n      throw new BadRequestException('只能选择一个参数: playlistId, playlistItemsIds');\n    }\n    const accessToken = await this.youtubeAuthService.getUserAccessToken(accountId);\n    return this.youtubeService.getPlayItemsList(accessToken, playlistId, playlistItemsIds, maxResults, pageToken);\n  }\n\n  @ApiOperation({ summary: '添加播放列表项' })\n  @Post('playlist/items/insert')\n  async PlayItemsInsert(\n    @GetToken() token: TokenInfo,\n    @Body('accountId') accountId: string,\n    @Body('snippet') snippet?: Record<string, any>,\n    @Body('contentDetails') contentDetails?: Record<string, any>,\n  ) {\n    const accessToken = await this.youtubeAuthService.getUserAccessToken(accountId);\n    return this.youtubeService.insertPlayItems(accessToken, snippet, contentDetails);\n  }\n\n  @ApiOperation({ summary: '更新播放列表项' })","sourceCodeStart":470,"sourceCodeEnd":506,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/youtube/youtube.controller.ts#L470-L506","documentation":"The getPlayItemsList endpoint requires exactly one lookup mode: `playlistId` (list items of a playlist) or `playlistItemsIds` (fetch specific playlist items). The controller filters out undefined/null/'' values and throws BadRequestException when more than one is present. Thrown locally before any YouTube API request or token refresh.","triggerScenarios":"GET playlistItems with both ?playlistId=PL...&playlistItemsIds=PI1,PI2, or an SDK layer that always includes playlistId as a default while also passing item IDs.","commonSituations":"Wrappers with hardcoded default playlistId plus dynamic item IDs; UI forms that keep a previously selected playlist while switching to 'fetch by item ids' mode; batch scripts concatenating options.","solutions":["Send only playlistId OR playlistItemsIds, never both.","Strip empty defaults in the request builder so only the intended param is serialized.","Make two calls if both a playlist listing and specific items are required.","Log the outgoing query string to spot accidental always-on defaults."],"exampleFix":"// before\ngetPlayItemsList(accountId, playlistId, itemIds, maxResults);\n// after\ngetPlayItemsList(accountId, itemIds?.length ? undefined : playlistId, itemIds?.length ? itemIds : undefined, maxResults);","handlingStrategy":"validation","validationCode":"if ((playlistId ? 1 : 0) + (playlistItemsIds?.length ? 1 : 0) !== 1) {\n  throw new Error('Provide exactly one of playlistId or playlistItemsIds');\n}","typeGuard":"function hasExactlyOne(a?: string, b?: string[]): boolean {\n  return [a !== undefined && a !== '', Array.isArray(b) && b.length > 0].filter(Boolean).length === 1;\n}","tryCatchPattern":"try {\n  const items = await client.getPlayItemsList(accountId, playlistId, undefined, 50);\n} catch (e) {\n  if (String(e.message).includes('playlistId, playlistItemsIds')) console.warn('Mixed lookup modes; adjust caller');\n  throw e;\n}","preventionTips":["Remove always-on default playlistId from wrappers/SDK options.","Treat the two lookups as separate API methods to make mixing impossible.","Validate params at the call site before invoking the client.","Log the final query string during development."],"tags":["bad-request","input-validation","youtube-api","mutually-exclusive-params"],"backgroundTag":"mutually-exclusive-params","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}