Angular后端联动04,深入浅出 Angular HTTP 拦截器:统一处理 Token 与响应

2025博客之星年度评选已开启 10w+人浏览 3.6k人参与

在 Angular 应用开发中,HTTP 请求是与后端交互的核心环节。实际项目中,我们常常需要为所有请求统一添加认证 Token、统一处理响应错误(如 401 未授权、500 服务器错误),如果每个请求都单独处理这些逻辑,不仅代码冗余,还容易出现遗漏。Angular 的 HTTP 拦截器(HTTP Interceptor)正是为解决这类问题而生的最佳方案。

本文将手把手教你实现一个实用的 HTTP 拦截器,完成两大核心功能:

  1. 为所有 HTTP 请求自动添加 Token 请求头
  2. 统一捕获和处理 HTTP 响应异常

一、HTTP 拦截器核心概念

HTTP 拦截器是 Angular 提供的一种中间件机制,能够拦截应用发出的所有 HTTP 请求和响应,允许我们在请求发送前、响应返回后(或出错时)插入自定义逻辑。

拦截器基于HttpInterceptor接口实现,核心是intercept方法,该方法接收两个参数:

  • req: HttpRequest<any>:待处理的请求对象(不可直接修改,需克隆后修改)
  • next: HttpHandler:请求处理链路的下一个处理器

二、实现步骤

1. 环境准备

确保你的 Angular 项目已正确配置,建议使用 Angular 12 + 版本(本文示例基于 Angular 16)。

2. 创建拦截器服务

首先创建一个拦截器文件http.interceptor.ts,放在core/interceptors目录下(符合 Angular 最佳实践):

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError, catchError } from 'rxjs';
import { Router } from '@angular/router';
import { ToastService } from '../services/toast.service'; // 自定义提示服务(可替换为你的提示组件)

@Injectable()
export class HttpTokenInterceptor implements HttpInterceptor {

  constructor(
    private router: Router,
    private toastService: ToastService // 用于全局提示错误信息
  ) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // 1. 请求拦截:统一添加Token请求头
    const modifiedReq = this.addTokenToRequest(request);
    
    // 2. 响应拦截:统一处理响应和错误
    return next.handle(modifiedReq).pipe(
      catchError((error: HttpErrorResponse) => this.handleResponseError(error))
    );
  }

  /**
   * 为请求添加Token请求头
   * @param request 原始请求对象
   * @returns 克隆后的新请求对象
   */
  private addTokenToRequest(request: HttpRequest<unknown>): HttpRequest<unknown> {
    // 从本地存储获取Token(实际项目中建议封装到AuthService)
    const token = localStorage.getItem('auth_token');
    
    // 如果有Token,则克隆请求并添加Authorization头
    if (token) {
      return request.clone({
        setHeaders: {
          'Authorization': `Bearer ${token}`, // JWT标准格式
          'Content-Type': 'application/json'  // 统一设置Content-Type
        }
      });
    }
    
    // 无Token则返回原始请求
    return request;
  }

  /**
   * 统一处理响应错误
   * @param error HTTP错误响应
   * @returns 抛出错误供订阅者处理
   */
  private handleResponseError(error: HttpErrorResponse): Observable<never> {
    let errorMessage = '未知错误,请稍后重试';

    // 根据错误类型分类处理
    if (error.error instanceof ErrorEvent) {
      // 客户端错误(如网络异常)
      errorMessage = `客户端错误:${error.error.message}`;
    } else {
      // 服务端错误(根据状态码处理)
      switch (error.status) {
        case 401:
          // 未授权:清除Token并跳转到登录页
          localStorage.removeItem('auth_token');
          this.toastService.error('登录已过期,请重新登录');
          this.router.navigate(['/login']);
          errorMessage = '登录已过期,请重新登录';
          break;
        case 403:
          errorMessage = '无权限访问该资源';
          break;
        case 404:
          errorMessage = '请求的资源不存在';
          break;
        case 500:
          errorMessage = '服务器内部错误,请联系管理员';
          break;
        default:
          errorMessage = `服务端错误 [${error.status}]:${error.message}`;
      }
    }

    // 全局提示错误信息
    this.toastService.error(errorMessage);
    
    // 抛出错误,让请求的订阅者也能捕获
    return throwError(() => new Error(errorMessage));
  }
}

3. 注册拦截器

创建完拦截器后,需要在 Angular 的根模块(app.module.ts)中注册,使其生效:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpTokenInterceptor } from './core/interceptors/http.interceptor';
import { ToastService } from './core/services/toast.service'; // 引入提示服务

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [
    ToastService,
    // 注册HTTP拦截器
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpTokenInterceptor,
      multi: true // 允许多个拦截器(重要!)
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

4. 补充说明:自定义提示服务(ToastService)

上述代码中用到了ToastService,这是一个自定义的全局提示服务,你可以根据项目实际情况替换为 NG-ZORRO、PrimeNG 等 UI 库的提示组件,或实现一个简易版:

// core/services/toast.service.ts
import { Injectable } from '@angular/core';
import { ToastrService } from 'ngx-toastr'; // 以ngx-toastr为例

@Injectable({
  providedIn: 'root'
})
export class ToastService {
  constructor(private toastr: ToastrService) {}

  // 错误提示
  error(message: string): void {
    this.toastr.error(message, '错误');
  }

  // 成功提示(可选)
  success(message: string): void {
    this.toastr.success(message, '成功');
  }
}

三、高级扩展技巧

1. 排除特定请求

某些请求(如登录、注册)不需要添加 Token,可在拦截器中排除:

private addTokenToRequest(request: HttpRequest<unknown>): HttpRequest<unknown> {
  // 排除登录接口
  const excludedUrls = ['/api/auth/login', '/api/auth/register'];
  if (excludedUrls.some(url => request.url.includes(url))) {
    return request;
  }

  const token = localStorage.getItem('auth_token');
  if (token) {
    return request.clone({
      setHeaders: {
        'Authorization': `Bearer ${token}`
      }
    });
  }
  return request;
}

2. 多个拦截器的执行顺序

当注册多个拦截器时,providers数组中的顺序即为拦截器的执行顺序:

  • 请求阶段:按数组顺序执行
  • 响应阶段:按数组逆序执行

例如:

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: InterceptorA, multi: true }, // 先执行
  { provide: HTTP_INTERCEPTORS, useClass: InterceptorB, multi: true }  // 后执行
]

请求时:A → B → 发送请求响应时:B → A → 组件

四、注意事项

  1. 请求对象不可变HttpRequest是不可变对象,必须通过clone()方法修改后再返回。
  2. 避免无限循环:拦截器中如果发起新的 HTTP 请求,务必排除该请求,否则会导致无限拦截。
  3. Token 安全存储:示例中使用localStorage存储 Token,生产环境建议结合HttpOnly Cookie或加密存储。
  4. 错误处理粒度:全局拦截器处理通用错误,特定请求的个性化错误仍需在订阅处单独处理。

总结

  1. Angular HTTP 拦截器通过实现HttpInterceptor接口,可统一拦截请求和响应,避免重复代码。
  2. 请求拦截核心是克隆HttpRequest并添加 Token 头,响应拦截通过catchError统一处理不同状态码的错误。
  3. 拦截器需在根模块注册,multi: true允许注册多个拦截器,执行顺序需根据业务需求合理设置。

通过本文的实现方案,你可以快速为 Angular 项目搭建起规范的 HTTP 请求处理体系,提升代码可维护性和用户体验。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canjun_wen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值