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