HTTP拦截器 #

一、拦截器概述 #

HTTP拦截器可以拦截HTTP请求和响应,用于添加认证头、日志记录、错误处理等。

text
请求流程:
组件 → HttpClient → 拦截器1 → 拦截器2 → ... → 后端

响应流程:
后端 → ... → 拦截器2 → 拦截器1 → HttpClient → 组件

二、创建拦截器 #

2.1 使用CLI创建 #

bash
ng generate interceptor interceptors/auth

2.2 基本拦截器结构 #

typescript
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpInterceptorFn
} from '@angular/common/http';
import { Observable } from 'rxjs';

// 函数式拦截器(Angular 17+推荐)
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authToken = 'your-token';
  
  const authReq = req.clone({
    setHeaders: {
      Authorization: `Bearer ${authToken}`
    }
  });
  
  return next(authReq);
};

2.3 类拦截器(传统方式) #

typescript
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    const authToken = 'your-token';
    
    const authReq = request.clone({
      setHeaders: {
        Authorization: `Bearer ${authToken}`
      }
    });
    
    return next.handle(authReq);
  }
}

三、注册拦截器 #

3.1 注册函数式拦截器 #

typescript
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './interceptors/auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor])
    )
  ]
};

3.2 注册类拦截器 #

typescript
import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

四、认证拦截器 #

4.1 添加认证头 #

typescript
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);
  const token = authService.getToken();
  
  if (token) {
    const authReq = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
    return next(authReq);
  }
  
  return next(req);
};

4.2 处理Token过期 #

typescript
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { Router } from '@angular/router';
import { catchError, switchMap, throwError, from } from 'rxjs';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  const token = authService.getToken();
  
  let authReq = req;
  if (token) {
    authReq = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }
  
  return next(authReq).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        if (authService.hasRefreshToken()) {
          return from(authService.refreshToken()).pipe(
            switchMap((newToken) => {
              const newReq = req.clone({
                setHeaders: {
                  Authorization: `Bearer ${newToken}`
                }
              });
              return next(newReq);
            }),
            catchError(() => {
              authService.logout();
              router.navigate(['/login']);
              return throwError(() => error);
            })
          );
        } else {
          authService.logout();
          router.navigate(['/login']);
        }
      }
      return throwError(() => error);
    })
  );
};

五、日志拦截器 #

5.1 请求日志 #

typescript
import { HttpInterceptorFn } from '@angular/common/http';
import { tap } from 'rxjs/operators';

export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  const startTime = Date.now();
  
  console.log(`[请求] ${req.method} ${req.url}`);
  console.log('请求头:', req.headers);
  console.log('请求体:', req.body);
  
  return next(req).pipe(
    tap({
      next: (event) => {
        if (event.type === 4) {
          const duration = Date.now() - startTime;
          console.log(`[响应] ${req.url} - 耗时: ${duration}ms`);
        }
      },
      error: (error) => {
        const duration = Date.now() - startTime;
        console.error(`[错误] ${req.url} - 耗时: ${duration}ms`, error);
      }
    })
  );
};

六、错误处理拦截器 #

6.1 全局错误处理 #

typescript
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';
import { NotificationService } from '../services/notification.service';

export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  const notificationService = inject(NotificationService);
  
  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      let errorMessage = '发生未知错误';
      
      if (error.error instanceof ErrorEvent) {
        errorMessage = `客户端错误: ${error.error.message}`;
      } else {
        switch (error.status) {
          case 400:
            errorMessage = error.error?.message || '请求参数错误';
            break;
          case 401:
            errorMessage = '未授权,请重新登录';
            router.navigate(['/login']);
            break;
          case 403:
            errorMessage = '拒绝访问';
            break;
          case 404:
            errorMessage = '请求的资源不存在';
            break;
          case 500:
            errorMessage = '服务器内部错误';
            break;
          case 503:
            errorMessage = '服务暂时不可用';
            break;
          default:
            errorMessage = `服务器错误: ${error.status}`;
        }
      }
      
      notificationService.error(errorMessage);
      
      return throwError(() => new Error(errorMessage));
    })
  );
};

七、缓存拦截器 #

7.1 简单缓存 #

typescript
import { HttpInterceptorFn, HttpResponse } from '@angular/common/http';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';

const cache = new Map<string, HttpResponse<any>>();

export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
  if (req.method !== 'GET') {
    return next(req);
  }
  
  const cachedResponse = cache.get(req.urlWithParams);
  if (cachedResponse) {
    return of(cachedResponse);
  }
  
  return next(req).pipe(
    tap((event) => {
      if (event instanceof HttpResponse) {
        cache.set(req.urlWithParams, event);
      }
    })
  );
};

7.2 带过期时间的缓存 #

typescript
interface CacheEntry {
  response: HttpResponse<any>;
  expiry: number;
}

@Injectable({ providedIn: 'root' })
export class CacheService {
  private cache = new Map<string, CacheEntry>();
  private defaultTTL = 5 * 60 * 1000;
  
  get(url: string): HttpResponse<any> | null {
    const entry = this.cache.get(url);
    
    if (!entry) return null;
    
    if (Date.now() > entry.expiry) {
      this.cache.delete(url);
      return null;
    }
    
    return entry.response;
  }
  
  set(url: string, response: HttpResponse<any>, ttl = this.defaultTTL) {
    this.cache.set(url, {
      response,
      expiry: Date.now() + ttl
    });
  }
  
  clear() {
    this.cache.clear();
  }
}

export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
  const cacheService = inject(CacheService);
  
  if (req.method !== 'GET') {
    return next(req);
  }
  
  const cachedResponse = cacheService.get(req.urlWithParams);
  if (cachedResponse) {
    return of(cachedResponse);
  }
  
  return next(req).pipe(
    tap((event) => {
      if (event instanceof HttpResponse) {
        cacheService.set(req.urlWithParams, event);
      }
    })
  );
};

八、加载状态拦截器 #

8.1 显示/隐藏加载状态 #

typescript
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { finalize } from 'rxjs/operators';
import { LoadingService } from '../services/loading.service';

export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
  const loadingService = inject(LoadingService);
  
  loadingService.show();
  
  return next(req).pipe(
    finalize(() => {
      loadingService.hide();
    })
  );
};

8.2 Loading服务 #

typescript
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class LoadingService {
  private loadingSubject = new BehaviorSubject<boolean>(false);
  loading$ = this.loadingSubject.asObservable();
  private requestCount = 0;
  
  show() {
    this.requestCount++;
    this.loadingSubject.next(true);
  }
  
  hide() {
    this.requestCount--;
    if (this.requestCount <= 0) {
      this.requestCount = 0;
      this.loadingSubject.next(false);
    }
  }
}

九、请求转换拦截器 #

9.1 添加基础URL #

typescript
import { HttpInterceptorFn } from '@angular/common/http';
import { environment } from '../../environments/environment';

export const baseUrlInterceptor: HttpInterceptorFn = (req, next) => {
  const apiReq = req.clone({
    url: `${environment.apiUrl}${req.url}`
  });
  
  return next(apiReq);
};

9.2 添加时间戳防止缓存 #

typescript
import { HttpInterceptorFn } from '@angular/common/http';

export const noCacheInterceptor: HttpInterceptorFn = (req, next) => {
  if (req.method === 'GET') {
    const customReq = req.clone({
      setParams: {
        _: Date.now().toString()
      }
    });
    return next(customReq);
  }
  
  return next(req);
};

十、多拦截器顺序 #

10.1 注册顺序 #

typescript
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([
        loadingInterceptor,     // 1. 显示加载状态
        authInterceptor,        // 2. 添加认证头
        baseUrlInterceptor,     // 3. 添加基础URL
        loggingInterceptor      // 4. 记录日志
      ])
    )
  ]
};

10.2 执行顺序 #

text
请求阶段: loading → auth → baseUrl → logging → 后端
响应阶段: 后端 → logging → baseUrl → auth → loading

十一、拦截器最佳实践 #

11.1 单一职责 #

typescript
// 好的做法:每个拦截器只做一件事
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  // 只处理认证
};

export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  // 只处理日志
};

// 不好的做法:一个拦截器处理所有事情
export const allInOneInterceptor: HttpInterceptorFn = (req, next) => {
  // 处理认证、日志、缓存、错误...
};

11.2 条件应用 #

typescript
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);
  
  if (req.url.includes('/public/')) {
    return next(req);
  }
  
  const token = authService.getToken();
  if (!token) {
    return next(req);
  }
  
  const authReq = req.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`
    }
  });
  
  return next(authReq);
};

11.3 跳过特定请求 #

typescript
import { HttpContextToken } from '@angular/common/http';

export const SKIP_AUTH = new HttpContextToken(() => false);

// 请求时设置
this.http.get('/api/public', {
  context: new HttpContext().set(SKIP_AUTH, true)
});

// 拦截器中检查
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  if (req.context.get(SKIP_AUTH)) {
    return next(req);
  }
  
  // 添加认证头
};

十二、总结 #

概念 说明
HttpInterceptorFn 函数式拦截器
HttpInterceptor 类拦截器
req.clone() 克隆请求修改
next.handle() 传递给下一个处理器
withInterceptors() 注册拦截器

下一步:RxJS基础

最后更新:2026-03-26