yikart/AiToEarn · error · HttpException

${err.response?.data?.error_description || '刷新令牌失败'}

Error message

${err.response?.data?.error_description || '刷新令牌失败'}

What it means

An HttpException thrown by refreshAccessToken's outer catch. When the refresh POST itself throws (network failure or non-200 status), it surfaces the upstream error_description and propagates the upstream status code, defaulting to 400. The fallback literal is the generic '刷新令牌失败'.

Source

Thrown at project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.auth.service.ts:478

        );
      }

      this.logger.log('刷新TikTok访问令牌成功');
      // 返回系统令牌用于前端重定向
      const userInfo = await this.userModel.findOne({_id: userId});
      const systemTokenInfo = {
        phone: userInfo?.phone ?? '',
        id: userId,
        name: userInfo.name,
        isManager: false,
        googleId: userInfo?.googleAccount?.googleId ?? ''
      };

      const systemToken = await this.authService.generateToken(systemTokenInfo);
      return { url: systemToken };
    } catch (err) {
      this.logger.error('刷新TikTok访问令牌失败:', err.response?.data || err.message);
      throw new HttpException(
        err.response?.data?.error_description || '刷新令牌失败',
        err.response?.status || HttpStatus.BAD_REQUEST
      );
    }
  }

  /**
   * 获取用户的TikTok访问令牌
   * @param accountId 账号ID
   * @returns 访问令牌
   */
  async getUserAccessToken(accountId: string): Promise<string> {
    this.logger.log(`获取TikTok访问令牌: accountId=${accountId}`);

    // 先检查Redis缓存
    const cachedToken = await this.redisService.get(`tiktok:accessToken:${accountId}`);
    if (cachedToken && cachedToken.access_token) {
      this.logger.log("从Redis获取到有效令牌");

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Check the logged err.response?.data for TikTok's error payload; fix the named parameter.
  2. Verify network connectivity/proxy configuration from the server to open.tiktokapis.com.
  3. Re-sync TIKTOK_CLIENT_KEY/TIKTOK_CLIENT_SECRET with the current values in the TikTok Developers console.
  4. Implement a bounded retry with backoff for transient network/5xx failures before surfacing the error.

Example fix

// before
throw new HttpException(
  err.response?.data?.error_description || '刷新令牌失败',
  err.response?.status || HttpStatus.BAD_REQUEST
);
// after
const retryable = !err.response || err.response.status >= 500 || err.code === 'ECONNABORTED';
if (retryable) {
  throw new HttpException('刷新令牌失败(网络临时故障,请重试)', HttpStatus.BAD_GATEWAY);
}
throw new HttpException(err.response?.data?.error_description || '刷新令牌失败', err.response?.status || HttpStatus.BAD_REQUEST);
Defensive patterns

Strategy: retry

Validate before calling

async function canReachTikTok(): Promise<boolean> {
  try { await axios.head('https://open.tiktokapis.com', { timeout: 5000 }); return true; }
  catch { return false; }
}

Type guard

function isNetworkError(e: unknown): boolean {
  const anyE = e as any;
  return !anyE?.response || ['ECONNABORTED', 'ECONNREFUSED', 'ENOTFOUND', 'ETIMEDOUT'].includes(anyE.code);
}

Try / catch

try {
  return await withRetry(() => api.refreshTikTokToken(token), { retries: 2, backoffMs: 1000 });
} catch (e) {
  if (isNetworkError(e)) throw new ServiceUnavailableError('TikTok token endpoint unreachable');
  throw e;
}

Prevention

When it happens

Trigger: The axios POST to TikTok's oauth/token/ endpoint rejects: connectivity failure, timeout, TLS error, or TikTok returned 400/401 (bad client credentials, malformed body) — i.e. cases where no { error } body was parsed into data.

Common situations: Egress firewall blocking open.tiktokapis.com, DNS issues, request timeout under load, client secret rotated on the TikTok dashboard while the server still has the old value.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/f80d5546a999a4c4. Report an issue: GitHub.