You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 17 HttpInterceptorFn:Token续期时HTTP请求排队方案咨询

Angular 17 HttpInterceptorFn 并发Token续期问题解决思路

核心思路是通过单例状态管理控制Token刷新流程,用请求队列暂存刷新期间的待处理请求,避免重复刷新并确保所有请求使用最新Token。

1. 封装Token刷新状态管理服务

创建单例服务维护全局唯一的刷新状态与请求队列,统一处理Token续期逻辑:

import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';

@Injectable({ providedIn: 'root' })
export class TokenRefreshService {
  private isRefreshing = false;
  private requestQueue: Array<(newToken: string) => void> = [];

  constructor(private http: HttpClient) {}

  // 发起后端Token续期请求
  refreshAccessToken(): Observable<string> {
    return this.http.post<{ token: string }>('/api/auth/refresh', {})
      .pipe(
        tap(res => {
          // 更新本地存储的Token(根据项目实际存储方式调整)
          localStorage.setItem('authToken', res.token);
        }),
        catchError(err => {
          // 续期失败后重置状态,避免后续请求阻塞
          this.resetRefreshState();
          return throwError(() => err);
        })
      );
  }

  // 将待处理请求加入队列
  enqueueRequest(callback: (newToken: string) => void): void {
    this.requestQueue.push(callback);
  }

  // 遍历队列,用新Token重新发起所有待处理请求
  processQueue(newToken: string): void {
    this.requestQueue.forEach(callback => callback(newToken));
    this.requestQueue = [];
    this.isRefreshing = false;
  }

  // 重置刷新状态(错误或完成后调用)
  resetRefreshState(): void {
    this.isRefreshing = false;
    this.requestQueue = [];
  }

  // 获取当前刷新状态
  getIsRefreshing(): boolean {
    return this.isRefreshing;
  }

  // 设置刷新状态
  setIsRefreshing(state: boolean): void {
    this.isRefreshing = state;
  }
}

2. 在HttpInterceptorFn中实现队列调度逻辑

通过注入单例服务,拦截请求并处理Token过期、队列暂存与重试逻辑:

import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { TokenRefreshService } from './token-refresh.service';
import { catchError, switchMap } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const tokenService = inject(TokenRefreshService);
  const currentToken = localStorage.getItem('authToken');

  // 实现Token过期校验逻辑(示例:解析JWT的exp字段)
  const isTokenExpired = () => {
    if (!currentToken) return true;
    const payload = JSON.parse(atob(currentToken.split('.')[1]));
    return payload.exp * 1000 < Date.now();
  };

  // Token未过期,直接添加授权头发送请求
  if (!isTokenExpired()) {
    const authReq = req.clone({
      setHeaders: { Authorization: `Bearer ${currentToken}` }
    });
    return next(authReq);
  }

  // Token已过期,判断是否正在刷新
  if (tokenService.getIsRefreshing()) {
    // 正在刷新,将当前请求加入队列,等待新Token返回后重试
    return new Observable(observer => {
      tokenService.enqueueRequest((newToken) => {
        const retryReq = req.clone({
          setHeaders: { Authorization: `Bearer ${newToken}` }
        });
        next(retryReq).subscribe({
          next: res => observer.next(res),
          error: err => observer.error(err),
          complete: () => observer.complete()
        });
      });
    });
  }

  // 未在刷新,发起Token续期请求
  tokenService.setIsRefreshing(true);
  return tokenService.refreshAccessToken().pipe(
    switchMap(newToken => {
      // 处理队列中所有待请求
      tokenService.processQueue(newToken);
      // 重新发送当前请求
      const retryReq = req.clone({
        setHeaders: { Authorization: `Bearer ${newToken}` }
      });
      return next(retryReq);
    }),
    catchError(err => {
      // 续期失败,可在此处跳转登录页或处理全局错误
      return throwError(() => err);
    })
  );
};

关键细节说明

  • 单例服务的作用:确保isRefreshing状态和请求队列全局唯一,避免多个拦截器实例导致的状态冲突。
  • 队列回调设计:每个队列项是接收新Token的函数,用于重新发起原请求,保证所有待处理请求都使用最新Token。
  • 错误边界处理:Token续期失败后必须重置状态并清空队列,防止后续请求一直处于阻塞等待状态。

内容的提问来源于stack exchange,提问作者KurtGL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 09:01:28