NestJS 拦截器实战:三个例子学会用
一、拦截器是什么
拦截器就是一个类,它可以在请求处理前后加上你自己的代码。这个想法来自面向切面编程(AOP),说白了就是把一些通用的事情抽出来统一处理。
要写一个拦截器,需要用 @Injectable() 装饰器,还要实现 NestInterceptor 接口。里面必须写一个 intercept() 方法,这个方法有两个参数:
ExecutionContext:当前请求的上下文,里面有请求信息、控制器信息、方法信息等
CallHandler:调用处理器,通过它的 handle() 方法来执行真正的路由处理程序
有一点要记住:如果你在拦截器里不写 next.handle(),路由处理程序就不会执行。
下面是一个简单的日志拦截器例子:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('请求进来了');
const now = Date.now();
return next.handle().pipe(
tap(() => console.log(`请求处理完,花了 ${Date.now() - now} 毫秒`)),
);
}
}二、拦截器能干什么
根据 NestJS 官方文档,拦截器可以做这些事:
在方法执行前后加额外的逻辑
改变函数返回的结果
改变函数抛出的异常
扩展函数的功能
根据条件完全重写函数(比如做缓存)
看到这里你应该明白了,拦截器能做的事情确实不少。
三、三个常用例子
例子一:记录请求日志
这是拦截器最常用的场景。我们可以写一个日志拦截器,记下每个接口的请求参数、返回结果和花了多长时间。
上面的 LoggingInterceptor 就是一个基础版本。在实际项目中,你还可以把日志存到数据库里。
有个开发者分享过一个真实经历:产品经理要求记录每个用户的增删改查操作。他用拦截器轻松搞定了,记下了用户的操作类型、IP地址、请求参数、浏览器信息等。
小提示:要把日志存数据库,可以在拦截器里注入 Service。因为 handle() 返回的是 RxJS 的 Observable,用 switchMap() 或 tap() 就能在响应返回后去存数据库。
例子二:统一返回格式
如果你的接口需要统一的返回格式,拦截器可以帮你自动处理。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
statusCode: number;
message: string;
data: T;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(
map(data => ({
statusCode: context.switchToHttp().getResponse().statusCode,
message: '操作成功',
data,
})),
);
}
}用了这个拦截器后,你每个接口的返回格式都会变成 { statusCode, message, data } 这种统一的样子。
例子三:处理超时
拦截器还可以配合 RxJS 的操作符来做请求超时控制和异常处理。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000), // 5秒超时
catchError(err => {
if (err instanceof TimeoutError) {
return throwError(() => new RequestTimeoutException('请求超时了'));
}
return throwError(() => err);
}),
);
}
}四、怎么用拦截器
1. 绑定到控制器或方法
用 @UseInterceptors() 装饰器可以指定拦截器在哪儿生效:
// 整个控制器都用
@Controller('cats')
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
// 只在这个方法上用
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {
return [];
}2. 全局注册
想让拦截器对所有路由都生效,可以在模块里注册成全局的:
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class AppModule {}3. 注入依赖
拦截器和普通服务一样,可以通过构造函数注入其他服务,这样功能就更强大了。
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
constructor(private readonly loggerService: LoggerService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
this.loggerService.log('收到请求');
return next.handle();
}
}五、拦截器和中间件有什么区别
很多初学者容易搞混这两个概念,我简单说一下:
| 特性 | 中间件 | 拦截器 |
|---|---|---|
| 执行时机 | 只在请求到达控制器之前 | 请求前后都可以 |
| 能不能改响应 | 不能 | 能(通过 RxJS) |
| 能不能访问执行上下文 | 不能 | 能 |
| 主要用途 | 身份验证、CORS、日志 | 响应转换、日志、缓存、异常处理 |
简单总结:中间件只能处理请求,拦截器既能处理请求也能处理响应。如果你需要改返回的数据格式,那就得用拦截器。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!