Skip to content
On this page

NestJS 拦截器 (Interceptors)

NestJS 拦截器是一个类,它实现了 NestInterceptor 接口。拦截器提供了一个在请求到达控制器之前和响应返回给客户端之后执行代码的机会。拦截器可以用于多种用途,如日志记录、缓存、响应转换等。

基础概念

拦截器在请求处理流程中的位置:

  1. 中间件 - 最先执行
  2. 守卫 - 权限控制
  3. 拦截器 - 请求/响应转换
  4. 管道 - 数据验证和转换
  5. 控制器方法 - 处理请求

拦截器的主要功能:

  • 在方法执行前后添加额外的逻辑
  • 转换从方法返回的结果
  • 转换从方法抛出的异常
  • 扩展基本函数行为
  • 实现特定用例的方面(如缓存等)

拦截器接口

每个拦截器必须实现 NestInterceptor 接口:

export interface NestInterceptor<T, R = T> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<R>;
}
  • context - 执行上下文,提供对请求对象和其他相关信息的访问
  • next - CallHandler 对象,用于调用控制器方法

创建基本拦截器

让我们创建一个简单的日志拦截器:

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`)),
      );
  }
}

使用拦截器

1. 在控制器中使用

import { Controller, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';

@Controller('cats')
@UseInterceptors(LoggingInterceptor)
export class CatsController {
  @Get()
  findAll() {
    console.log('Controller method called');
    return 'This action returns all cats';
  }
}

2. 在方法级别使用

@Get(':id')
@UseInterceptors(LoggingInterceptor)
findOne(@Param('id') id: string) {
  return this.catsService.findOne(id);
}

3. 全局注册

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingInterceptor } from './common/interceptors/logging.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new LoggingInterceptor());
  await app.listen(3000);
}
bootstrap();

调用处理器

CallHandler 对象提供了对控制器方法执行的控制:

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map(data => ({
        data,
        timestamp: Date.now(),
        success: true,
      })),
    );
  }
}

响应转换拦截器

创建一个响应格式化拦截器:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  data: T;
  timestamp: number;
  success: boolean;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
    return next.handle().pipe(
      map(data => ({
        data,
        timestamp: Date.now(),
        success: true,
      })),
    );
  }
}

异常转换拦截器

创建一个异常处理拦截器:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, BadGatewayException } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(
        catchError(err => {
          return throwError(() => new BadGatewayException());
        }),
      );
  }
}

缓存拦截器

创建一个简单的缓存拦截器:

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 url = request.url;

    const cached = this.cache.get(url);
    if (cached) {
      return of(cached);
    }

    return next.handle().pipe(
      tap(data => this.cache.set(url, data)),
    );
  }
}

超时拦截器

创建一个超时拦截器:

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 {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000),
      catchError(err => {
        if (err instanceof TimeoutError) {
          return throwError(() => new RequestTimeoutException('Request timeout'));
        }
        return throwError(() => err);
      }),
    );
  }
}

并发控制拦截器

创建一个限制并发请求的拦截器:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, defer } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return defer(async () => {
      // 在这里可以执行异步逻辑
      const request = context.switchToHttp().getRequest();
      return next.handle();
    });
  }
}

数据流操作

拦截器可以对数据流进行各种操作:

@Injectable()
export class DataTransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      // 对数据进行转换
      map(data => {
        if (data && typeof data === 'object') {
          // 移除敏感字段
          const { password, ...rest } = data;
          return rest;
        }
        return data;
      }),
      // 添加额外字段
      map(data => ({
        ...data,
        processed: true,
      })),
    );
  }
}

拦截器与依赖注入

拦截器可以使用依赖注入:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Inject } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Logger } from 'winston';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  constructor(@Inject('winston') private readonly logger: Logger) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const method = context.getClass().name + '.' + context.getHandler().name;

    this.logger.log('info', `Starting execution of ${method}`, {
      url: request.url,
      method: request.method,
    });

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => {
          this.logger.log('info', `Completed execution of ${method}`, {
            duration: Date.now() - now,
            url: request.url,
            method: request.method,
          });
        }),
      );
  }
}

拦截器链

可以组合多个拦截器:

@Controller('cats')
@UseInterceptors(LoggingInterceptor, TransformInterceptor, CacheInterceptor)
export class CatsController {
  @Get()
  findAll() {
    return this.catsService.findAll();
  }
}

条件拦截器

创建一个条件执行的拦截器:

@Injectable()
export class ConditionalInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    
    // 根据某些条件决定是否执行拦截逻辑
    if (request.headers['x-skip-interceptor']) {
      return next.handle();
    }

    return next.handle().pipe(
      tap(data => {
        console.log('Processing response:', data);
      }),
    );
  }
}

拦截器最佳实践

1. 错误处理

@Injectable()
export class SafeTransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map(data => {
        try {
          // 安全的数据转换
          return this.safeTransform(data);
        } catch (error) {
          console.error('Transform error:', error);
          return data; // 返回原始数据
        }
      }),
      catchError(error => {
        console.error('Interceptor error:', error);
        return throwError(() => error);
      }),
    );
  }

  private safeTransform(data: any) {
    // 安全的数据转换逻辑
    return data ? { ...data } : data;
  }
}

2. 性能考虑

@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
  private readonly startTime = new Map<string, number>();

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const requestId = this.generateRequestId(request);

    this.startTime.set(requestId, Date.now());

    return next.handle().pipe(
      tap(() => {
        const start = this.startTime.get(requestId);
        const duration = Date.now() - start;
        
        if (duration > 1000) { // 如果超过1秒,记录警告
          console.warn(`Slow request: ${request.url}, duration: ${duration}ms`);
        }
        
        this.startTime.delete(requestId);
      }),
    );
  }

  private generateRequestId(request: any): string {
    return `${request.method}-${request.url}-${Date.now()}`;
  }
}

3. 拦截器工厂

创建一个参数化的拦截器工厂:

export const createLoggingInterceptor = (logLevel: 'info' | 'debug' | 'warn' = 'info') => {
  @Injectable()
  class LoggingInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
      console[logLevel]('Before method execution');
      
      return next.handle().pipe(
        tap(() => console[logLevel]('After method execution')),
      );
    }
  }
  
  return LoggingInterceptor;
};

// 使用
@Controller('cats')
@UseInterceptors(createLoggingInterceptor('debug'))
export class CatsController {
  // ...
}

与中间件、守卫、管道的关系

  • 中间件 - 处理通用请求逻辑
  • 守卫 - 处理权限控制
  • 拦截器 - 处理请求/响应转换和切面逻辑
  • 管道 - 处理数据验证和转换
  • 控制器方法 - 处理业务逻辑

总结

NestJS 拦截器是实现切面编程的强大工具。它们允许你在不修改核心业务逻辑的情况下添加横切关注点,如日志记录、缓存、响应转换等。

拦截器的主要特点:

  • 实现 NestInterceptor 接口
  • 可以在方法执行前后添加逻辑
  • 可以转换响应数据或异常
  • 支持响应式编程
  • 可以访问执行上下文
  • 支持依赖注入
  • 可以组合使用多个拦截器