{"record":{"id":"2348abbcce9d4e2f","repo":"yikart/AiToEarn","slug":"userid-accountid","errorCode":null,"errorMessage":"userId和accountId是必须的","messagePattern":"userId和accountId是必须的","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/twitter/twitter.controller.ts","lineNumber":168,"sourceCode":"\n  /**\n   * 获取用户的Twitter时间线\n   */\n  @Get('timeline')\n  @ApiOperation({ summary: '获取用户的Twitter时间线' })\n  // @ApiQuery({ name: 'userId', required: true, description: '用户ID' })\n  @ApiQuery({ name: 'accountId', required: true, description: '账号ID' })\n  @ApiQuery({ name: 'maxResults', required: false, description: '最大结果数', type: 'number' })\n  async getUserTimeline(\n    @GetToken() systemToken: TokenInfo,\n    // @Query('userId') userId: string,\n    @Query('accountId') accountId: string,\n    @Query('maxResults') maxResults?: number,\n  ) {\n\n    const userId = systemToken.id;\n    if (!userId || !accountId) {\n      throw new BadRequestException('userId和accountId是必须的');\n    }\n\n    const accessToken = await this.twitterAuthService.getUserAccessToken(accountId);\n\n    return this.twitterService.getUserTimeline(accessToken, userId, accountId, maxResults);\n  }\n\n  /**\n   * 发布新推文\n   */\n  @Post('tweets/create')\n  @ApiOperation({ summary: '发布新推文' })\n  @ApiBody({\n    schema: {\n      type: 'object',\n      properties: {\n        // userId: { type: 'string', description: '用户ID' },\n        accountId: { type: 'string', description: 'Twitter账号ID' },","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/twitter/twitter.controller.ts#L150-L186","documentation":"GET /plat/twitter/timeline derives userId from the authenticated system JWT (@GetToken()) and takes accountId from the query. It throws BadRequestException('userId和accountId是必须的') when either is falsy — i.e. the request was unauthenticated/expired (no systemToken.id) or the accountId query param was omitted. After validation it fetches an access token via getUserAccessToken and calls the timeline service.","triggerScenarios":"Calling /plat/twitter/timeline without an Authorization header or with an expired JWT (userId undefined); omitting ?accountId=...; whitespace-only accountId.","commonSituations":"Cron jobs/scripts hitting the endpoint without a login token; token expiry mid-session; frontend calling before the selected account id resolves; test tools like curl missing the auth header.","solutions":["Re-authenticate to get a fresh system JWT and send it in the Authorization header","Add ?accountId=<id> to the request URL","Client-side guard: if (!authToken || !accountId) skip/redirect to login","If 401/empty-token errors follow (errors 450-452), re-authorize the Twitter account itself"],"exampleFix":"// before\nawait api.get('/plat/twitter/timeline');\n// after\nawait api.get('/plat/twitter/timeline', {\n  params: { accountId },\n  headers: { Authorization: `Bearer ${systemToken}` },\n});","handlingStrategy":"validation","validationCode":"if (!systemToken || !accountId) {\n  redirectToLogin(); // userId comes from the JWT; no token means the 400\n}\nawait api.get('/plat/twitter/timeline', { params: { accountId }, headers: { Authorization: `Bearer ${systemToken}` } });","typeGuard":"function isTimelineRequest(t: unknown, accountId: unknown): accountId is string {\n  return !!t && typeof (t as any).id === 'string' && (t as any).id.length > 0\n    && typeof accountId === 'string' && accountId.length > 0;\n}","tryCatchPattern":"try {\n  return await api.get('/plat/twitter/timeline', { params: { accountId } });\n} catch (e) {\n  if (e.response?.status === 400) {\n    // missing JWT-derived userId or accountId — re-login then retry\n    await relogin();\n    return await api.get('/plat/twitter/timeline', { params: { accountId } });\n  }\n  throw e;\n}","preventionTips":["Install a request interceptor that always attaches the system JWT","Redirect to login on 401/expired tokens instead of letting later calls 400","Only call timeline once an account is selected in the UI","Remember maxResults is optional and doesn't affect this validation"],"tags":["validation","auth","bad-request","query-params"],"backgroundTag":"missing-required-parameter","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}