关于对rxjs的描述,请参考这篇文章对rxjs的理解和基本使用。
interceptor都是和rxjs配合使用的,先看看在interceptor中有那些常用的操作符。
map
生成一个 interceptor:
nest g interceptor map-test --no-spec --flat
使用 map operator 来对 controller 返回的数据做一些修改:
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { map, Observable } from 'rxjs';
@Injectable()
export class MapTestInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map(data => {
return {
code: 200,
message: 'success',
data
}
}))
}
}
在 controller 里引入下:
现在返回的数据就变成了这样。
map 算是在 nest interceptor 里必用的 rxjs operator 了。
可以看到interceptor使用的第一个场景:转换请求 / 响应数据(最常用)。
上面其实展示了如何转换响应数据,下面再看看如何转换请求数据:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RequestTransformInterceptor implements NestInterceptor {
// 核心方法:拦截请求,处理入参后再执行控制器逻辑
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// 1. 获取 HTTP 上下文(拿到请求对象)
const ctx = context.switchToHttp();
const request = ctx.getRequest();
// 2. 统一处理入参:覆盖原始请求的 query/params/body
// 处理 URL 参数(如 /users/:id → id 从字符串转数字)
if (request.params) {
request.params = this.transformParams(request.params);
}
// 3. 执行控制器方法(此时入参已被转换)
return next.handle();
}
一般拦截器Interceptor是全局批量转换请求参数,单个参数的精准转换 + 验证,还是要使用管道 pipe。
tap
tap不会修改响应数据,而是执行一些额外逻辑,比如记录日志、更新缓存等
下面看一下记录日志的例子:
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> {
const now = Date.now();
const request = context.switchToHttp().getRequest();
const { method, url } = request; // 获取请求方法和路径
// 执行控制器方法,响应返回后触发 tap 回调
return next.handle().pipe(
tap(() => {
console.log(`[${method}] ${url} 耗时: ${Date.now() - now}ms`);
// 输出示例:[GET] /api/users 耗时: 15ms
})
);
}
}
对于高频访问、数据变更少的接口(比如首页配置、商品分类),可以用拦截器 + tap 来缓存响应结果,指定时间内重复请求直接返回缓存,提升性能。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
// 缓存容器
const cache = new Map<string, { data: any; expire: number }>();
const CACHE_TTL = 60 * 1000; // 缓存1分钟
@Injectable()
export class CacheInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const cacheKey = `${request.method}:${request.url}`; // 缓存键:方法+路径
// 检查缓存是否存在且未过期
const cached = cache.get(cacheKey);
if (cached && Date.now() < cached.expire) {
return of(cached.data); // 直接返回缓存数据,不执行控制器方法
}
// 执行控制器方法,缓存结果
return next.handle().pipe(
tap((data) => {
cache.set(cacheKey, {
data,
expire: Date.now() + CACHE_TTL,
});
})
);
}
}
可以看到,在 tap 中把请求结果缓存起来,下次再用时直接返回缓存。
catchError
controller 里很可能会抛出错误,这些错误会被 exception filter 处理,返回不同的响应,但在那之前,我们可以在 interceptor 里先处理下。
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable()
export class CatchErrorTestInterceptor implements NestInterceptor {
private readonly logger = new Logger(CatchErrorTestInterceptor.name)
intercept (context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(catchError(err => {
this.logger.error(err.message, err.stack)
return throwError(() => err)
}))
}
}
这里我们就是日志记录了一下,当然你也可以改成另一种错误,重新 throwError。
打印了两次错误:
一次是我们在 interceptor 里打印的,一次是 exception filter 打印的。
timeout
接口如果长时间没返回,要给用户一个接口超时的响应,这时候就可以用 timeout operator。
import { CallHandler, ExecutionContext, Injectable, NestInterceptor, RequestTimeoutException } from '@nestjs/common';
import { catchError, Observable, throwError, timeout, TimeoutError } from 'rxjs';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(3000),
catchError(err => {
if(err instanceof TimeoutError) {
console.log(err);
return throwError(() => new RequestTimeoutException());
}
return throwError(() => err);
})
)
}
}
imeout 操作符会在 3s 没收到消息的时候抛一个 TimeoutError。
然后用 catchError 操作符处理下,如果是 TimeoutError,就返回 RequestTimeoutException,这个有内置的 exception filter 会处理成对应的响应格式。
其余错误就直接 throwError 抛出去。
最后,再来看下全局的 interceptor:
因为这种是手动 new 的,没法注入依赖。
但很多情况下我们是需要全局 interceptor 的,而且还用到一些 provider,怎么办呢?
nest 提供了一个 token,用这个 token 在 AppModule 里声明的 interceptor,Nest 会把它作为全局 interceptor:
在这个 interceptor 里我们注入了 appService:
总结
rxjs 是一个处理异步逻辑的库,它的特点就是 operator 多,你可以通过组合 operator 来完成逻辑,不需要自己写。
nest 的 interceptor 就用了 rxjs 来处理响应,但常用的 operator 也就这么几个:
- tap: 不修改响应数据,执行一些额外逻辑,比如记录日志、更新缓存等
- map:对响应数据做修改,一般都是改成 {code, data, message} 的格式
- catchError:在 exception filter 之前处理抛出的异常,可以记录或者抛出别的异常
- timeout:处理响应超时的情况,抛出一个 TimeoutError,配合 catchErrror 可以返回超时的响应
总之,rxjs 的 operator 多,但是适合在 nest interceptor 里用的也不多。
此外,interceptor 也是可以注入依赖的,你可以通过注入模块内的各种 provider。
全局 interceptor 可以通过 APP_INTERCEPTOR 的 token 声明,这种能注入依赖,比 app.useGlobalInterceptors 更好。
interceptor 是 nest 必用功能,还是要好好掌握的。