{"record":{"id":"66007202cdb98050","repo":"yikart/AiToEarn","slug":"error-660072","errorCode":null,"errorMessage":"无效的账号或刷新令牌丢失","messagePattern":"无效的账号或刷新令牌丢失","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/twitter/twitter.auth.service.ts","lineNumber":482,"sourceCode":"  /**\n   * 获取用户的Twitter访问令牌\n   * @param accountId 账号ID\n   * @returns 访问令牌\n   */\n  async getUserAccessToken(accountId: string): Promise<string> {\n    console.log(\"获取访问令牌，accountId:\", accountId);\n\n    // 先检查Redis缓存\n    const cachedToken = await this.redisService.get(`twitter:accessToken:${accountId}`);\n    if (cachedToken && cachedToken.access_token) {\n      console.log(\"从Redis获取到有效令牌\");\n      return cachedToken.access_token;\n    }\n\n    // 如果缓存中没有，尝试刷新\n    const accountTokenInfo = await this.accountTokenModel.findOne({accountId: accountId});\n    if (!accountTokenInfo || !accountTokenInfo.refreshToken) {\n      throw new BadRequestException('无效的账号或刷新令牌丢失');\n    }\n\n    // 刷新并获取新令牌\n    const refreshResult = await this.refreshAccessToken(\n      accountTokenInfo.userId,\n      accountTokenInfo.accountId,\n      accountTokenInfo.refreshToken\n    );\n\n    // 刷新后再次从Redis获取\n    const newToken = await this.redisService.get(`twitter:accessToken:${accountId}`);\n    if (!newToken || !newToken.access_token) {\n      throw new BadRequestException('刷新令牌后未能获取访问令牌');\n    }\n\n    return newToken.access_token;\n  }\n","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/twitter/twitter.auth.service.ts#L464-L500","documentation":"getUserAccessToken() in twitter.auth.service.ts loads the cached Twitter access token from Redis; when absent it looks up the account's stored refresh token in the accountTokenModel MongoDB collection. If no AccountToken document exists for that accountId, or the document has no refreshToken field, the service cannot refresh the OAuth token and throws this BadRequestException (HTTP 400). It effectively means the Twitter account was never authorized through OAuth or its refresh token was removed.","triggerScenarios":"Calling any authenticated Twitter endpoint (timeline, createTweet, checkAuthStatus via isAuthorized, etc.) with an accountId that has no AccountToken record in MongoDB, or one whose refreshToken was unset (e.g. after revokeAuthorization's $unset or manual DB cleanup).","commonSituations":"Passing a wrong/typo'd accountId; testing with an account id from a different environment/database; account revoked via POST auth/revoke (which unsets refreshToken); Redis flushed while the DB record was also deleted; data migration losing the accountTokens collection.","solutions":["Complete the OAuth flow first: GET /plat/twitter/auth/url?mail=... then finish the callback so an AccountToken document with refreshToken is created","Verify the accountId exists: query the accountToken collection (findOne({accountId})) and confirm a non-empty refreshToken is stored","If the record exists but refreshToken is missing, re-authorize the account; revokeAuthorization permanently unsets refreshToken so the account must re-run OAuth","Re-run POST /plat/twitter/auth/refresh with valid userId/accountId/refreshToken to repopulate the token"],"exampleFix":"// before: calling with an arbitrary id\nawait twitterAuthService.getUserAccessToken('665f...guess');\n// after: ensure the account is authorized first\nif (!(await twitterAuthService.isAuthorized(accountId))) {\n  const { url } = await twitterAuthService.getAuthorizationUrl(userId, mail);\n  // redirect user to url to complete OAuth\n}\nconst token = await twitterAuthService.getUserAccessToken(accountId);","handlingStrategy":"validation","validationCode":"// Before calling any endpoint that needs the token, check authorization\nconst authorized = await api.get('/plat/twitter/auth/check', { params: { accountId } });\nif (!authorized) throw new Error(`Twitter account ${accountId} is not authorized; run the OAuth flow first`);","typeGuard":"function hasRefreshToken(t: { accountId: string; refreshToken?: string } | null): t is { accountId: string; refreshToken: string } {\n  return !!t && typeof t.refreshToken === 'string' && t.refreshToken.length > 0;\n}","tryCatchPattern":"try {\n  const token = await twitterAuthService.getUserAccessToken(accountId);\n} catch (e) {\n  if (e instanceof BadRequestException && e.message === '无效的账号或刷新令牌丢失') {\n    // surface re-authorization flow to the user\n    return { needsReauth: true, accountId };\n  }\n  throw e;\n}","preventionTips":["Always complete the OAuth callback flow before calling token-consuming endpoints","Never reuse accountIds across environments (dev/staging databases differ)","Remember revokeAuthorization unsets refreshToken permanently — plan re-auth after revocation","Monitor the accountToken collection for records missing refreshToken"],"tags":["oauth","twitter","bad-request","missing-token"],"backgroundTag":"missing-refresh-token","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}