NestJS拦截器(Interceptor) #
什么是拦截器? #
拦截器是用@Injectable()装饰器装饰的类,它实现了NestInterceptor接口。拦截器可以在方法执行前后添加额外逻辑,类似于AOP(面向切面编程)的概念。
拦截器的用途 #
- 方法执行前后添加额外逻辑
- 转换方法返回的结果
- 转换方法抛出的异常
- 扩展方法行为
- 缓存响应
创建拦截器 #
基本结构 #
typescript
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('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
执行流程 #
text
请求 → 守卫 → 拦截器(前) → 管道 → 控制器 → 拦截器(后) → 响应
应用拦截器 #
方法级别 #
typescript
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';
@Controller('users')
export class UsersController {
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {
return 'All users';
}
}
控制器级别 #
typescript
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UsersController {}
全局级别 #
typescript
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
响应转换拦截器 #
统一响应格式 #
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
code: number;
message: string;
data: T;
timestamp: string;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(
map(data => ({
code: 200,
message: 'Success',
data,
timestamp: new Date().toISOString(),
})),
);
}
}
使用响应转换 #
typescript
@Get()
findAll() {
return [{ id: 1, name: 'John' }];
}
响应:
json
{
"code": 200,
"message": "Success",
"data": [{ "id": 1, "name": "John" }],
"timestamp": "2024-01-01T00:00:00.000Z"
}
日志拦截器 #
详细日志 #
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url, body, query, params } = request;
const now = Date.now();
this.logger.log(
`Request: ${method} ${url}`,
JSON.stringify({ body, query, params }),
);
return next.handle().pipe(
tap({
next: data => {
const response = context.switchToHttp().getResponse();
this.logger.log(
`Response: ${method} ${url} ${response.statusCode} ${Date.now() - now}ms`,
);
},
error: error => {
this.logger.error(
`Error: ${method} ${url} ${Date.now() - now}ms`,
error.stack,
);
},
}),
);
}
}
异常映射拦截器 #
转换异常 #
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
NotFoundError,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class NotFoundInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError(error => {
if (error instanceof NotFoundError) {
return throwError(() => new NotFoundException(error.message));
}
return throwError(() => error);
}),
);
}
}
缓存拦截器 #
简单缓存 #
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
private cache = new Map<string, any>();
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const key = `${request.method}:${request.url}`;
if (this.cache.has(key)) {
return of(this.cache.get(key));
}
return next.handle().pipe(
tap(data => {
this.cache.set(key, data);
}),
);
}
}
使用@nestjs/cache-manager #
typescript
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';
@Controller('users')
@UseInterceptors(CacheInterceptor)
export class UsersController {
@Get()
@CacheKey('users_all')
@CacheTTL(60)
findAll() {
return this.usersService.findAll();
}
}
超时拦截器 #
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
RequestTimeoutException,
} from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { timeout, catchError } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
constructor(private readonly timeoutMs: number = 5000) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(this.timeoutMs),
catchError(error => {
if (error instanceof TimeoutError) {
return throwError(
() => new RequestTimeoutException('Request timeout'),
);
}
return throwError(() => error);
}),
);
}
}
性能监控拦截器 #
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const startTime = Date.now();
const request = context.switchToHttp().getRequest();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - startTime;
const { method, url } = request;
if (duration > 1000) {
console.warn(`Slow request: ${method} ${url} took ${duration}ms`);
}
}),
);
}
}
文件流拦截器 #
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { StreamableFile } from '@nestjs/common';
@Injectable()
export class FileInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const response = context.switchToHttp().getResponse();
return next.handle().pipe(
map(data => {
if (data instanceof StreamableFile) {
response.setHeader('Content-Type', 'application/octet-stream');
}
return data;
}),
);
}
}
多个拦截器 #
typescript
@Controller('users')
@UseInterceptors(LoggingInterceptor, TransformInterceptor, CacheInterceptor)
export class UsersController {
@Get()
findAll() {
return this.usersService.findAll();
}
}
执行顺序:
text
LoggingInterceptor(前) → TransformInterceptor(前) → CacheInterceptor(前) →
控制器 →
CacheInterceptor(后) → TransformInterceptor(后) → LoggingInterceptor(后)
条件拦截器 #
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class ConditionalInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const shouldCache = request.method === 'GET';
if (shouldCache) {
// 应用缓存逻辑
}
return next.handle();
}
}
拦截器最佳实践 #
1. 单一职责 #
每个拦截器只做一件事:
typescript
// 日志拦截器只负责日志
@Injectable()
export class LoggingInterceptor implements NestInterceptor {}
// 转换拦截器只负责转换
@Injectable()
export class TransformInterceptor implements NestInterceptor {}
2. 使用装饰器配置 #
typescript
import { SetMetadata } from '@nestjs/common';
export const CACHE_KEY = 'cache_key';
export const CacheConfig = (key: string, ttl: number = 60) =>
SetMetadata(CACHE_KEY, { key, ttl });
@Get()
@CacheConfig('users_all', 120)
findAll() {}
3. 错误处理 #
typescript
@Injectable()
export class ErrorHandlingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError(error => {
// 统一错误处理
return throwError(() => error);
}),
);
}
}
总结 #
本章学习了NestJS拦截器:
- 拦截器的概念和用途
- 响应转换拦截器
- 日志拦截器
- 缓存拦截器
- 超时拦截器
- 拦截器最佳实践
接下来,让我们学习 TypeORM集成。
最后更新:2026-03-28