9. 拦截器:Nest.js 的请求和响应处理工具

437 阅读6分钟

9. 拦截器:Nest.js 的请求和响应处理工具

介绍

欢迎回来!在前几篇文章中,我们已经了解了如何创建控制器、服务、中间件、管道、异常过滤器和守卫,并使用它们来处理 HTTP 请求。在这篇文章中,我们将探讨 Nest.js 中的拦截器(Interceptors)。拦截器是处理请求和响应的强大工具,可以在请求到达控制器之前或响应发送到客户端之前执行一些操作。让我们一起深入了解拦截器的工作原理和使用方法。

什么是拦截器?

拦截器是一个类,它实现了 NestInterceptor 接口,并包含一个 intercept 方法。intercept 方法接收两个参数:执行上下文和一个调用处理器的函数。拦截器可以用于在请求到达控制器之前或响应发送到客户端之前执行一些操作,如日志记录、数据转换和缓存等。

创建一个拦截器

让我们通过一个实际例子来了解如何创建和使用拦截器。假设我们要创建一个日志拦截器,用于记录每个请求的详细信息。

首先,使用 Nest CLI 创建一个新的拦截器:

nest generate interceptor logging

这条命令会在 src 目录下生成一个 logging.interceptor.ts 文件。让我们看看这个文件的内容:

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`)),
      );
  }
}
  • @Injectable():将类标记为可注入的服务。
  • intercept 方法:拦截器的核心方法,接收执行上下文和一个调用处理器的函数。

使用拦截器进行日志记录

在这个例子中,我们在请求处理之前记录 "Before...",在请求处理之后记录 "After..." 以及处理时间。让我们将 LoggingInterceptor 应用到 BooksController 的所有路由。

打开 books.controller.ts 文件,修改如下:

import { Controller, Get, Post, Put, Delete, Param, Body, UseInterceptors } from '@nestjs/common';
import { BooksService } from './books.service';
import { CreateBookDto } from './create-book.dto';
import { LoggingInterceptor } from './logging.interceptor';

@Controller('books')
@UseInterceptors(LoggingInterceptor)
export class BooksController {
  constructor(private readonly booksService: BooksService) {}

  @Get()
  findAll(): string {
    return this.booksService.findAll();
  }

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

  @Post()
  create(@Body() createBookDto: CreateBookDto): string {
    return this.booksService.create(createBookDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateBookDto: CreateBookDto): string {
    return this.booksService.update(id, updateBookDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string): string {
    return this.booksService.remove(id);
  }
}

在这个例子中,我们使用 @UseInterceptors 装饰器将 LoggingInterceptor 应用到 BooksController。每次请求到达 BooksController 的方法之前和之后,都会经过 LoggingInterceptor

全局拦截器

如果你希望拦截器应用于所有路由,可以将其注册为全局拦截器。打开 main.ts 文件,修改如下:

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

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

在这个例子中,我们使用 app.useGlobalInterceptors 方法将 LoggingInterceptor 注册为全局拦截器。这样,所有的请求都会经过 LoggingInterceptor

数据转换拦截器

拦截器还可以用于数据转换。让我们创建一个新的拦截器,用于将响应数据转换为特定格式。

首先,使用 Nest CLI 创建一个新的拦截器:

nest generate interceptor transform

然后,修改 transform.interceptor.ts 文件如下:

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

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

在这个例子中,我们将响应数据转换为包含 datasuccesstimestamp 字段的对象。

要使用这个数据转换拦截器,我们可以将其应用到特定的控制器或全局范围内。让我们将 TransformInterceptor 应用到 BooksController

打开 books.controller.ts 文件,修改如下:

import { Controller, Get, Post, Put, Delete, Param, Body, UseInterceptors } from '@nestjs/common';
import { BooksService } from './books.service';
import { CreateBookDto } from './create-book.dto';
import { LoggingInterceptor } from './logging.interceptor';
import { TransformInterceptor } from './transform.interceptor';

@Controller('books')
@UseInterceptors(LoggingInterceptor, TransformInterceptor)
export class BooksController {
  constructor(private readonly booksService: BooksService) {}

  @Get()
  findAll(): string {
    return this.booksService.findAll();
  }

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

  @Post()
  create(@Body() createBookDto: CreateBookDto): string {
    return this.booksService.create(createBookDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateBookDto: CreateBookDto): string {
    return this.booksService.update(id, updateBookDto);
  }
  
  @Delete(':id')
  remove(@Param('id') id: string): string {
    return this.booksService.remove(id);
  }
}

在这个例子中,我们使用 @UseInterceptors 装饰器将 LoggingInterceptorTransformInterceptor 应用到 BooksController。每次请求到达 BooksController 的方法之前和之后,都会经过这两个拦截器。

缓存拦截器

拦截器还可以用于实现缓存机制。让我们创建一个新的拦截器,用于缓存 GET 请求的响应数据。

首先,使用 Nest CLI 创建一个新的拦截器:

nest generate interceptor cache

然后,修改 cache.interceptor.ts 文件如下:

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

    if (this.cache.has(key)) {
      return of(this.cache.get(key));
    }

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

在这个例子中,我们使用一个 Map 对象来存储缓存数据。如果缓存中存在请求的 URL 对应的数据,我们直接返回缓存数据;否则,我们将响应数据存储到缓存中。

要使用这个缓存拦截器,我们可以将其应用到特定的控制器或全局范围内。让我们将 CacheInterceptor 应用到 BooksController

打开 books.controller.ts 文件,修改如下:

import { Controller, Get, Post, Put, Delete, Param, Body, UseInterceptors } from '@nestjs/common';
import { BooksService } from './books.service';
import { CreateBookDto } from './create-book.dto';
import { LoggingInterceptor } from './logging.interceptor';
import { TransformInterceptor } from './transform.interceptor';
import { CacheInterceptor } from './cache.interceptor';

@Controller('books')
@UseInterceptors(LoggingInterceptor, TransformInterceptor, CacheInterceptor)
export class BooksController {
  constructor(private readonly booksService: BooksService) {}

  @Get()
  findAll(): string {
    return this.booksService.findAll();
  }

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

  @Post()
  create(@Body() createBookDto: CreateBookDto): string {
    return this.booksService.create(createBookDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateBookDto: CreateBookDto): string {
    return this.booksService.update(id, updateBookDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string): string {
    return this.booksService.remove(id);
  }
}

在这个例子中,我们使用 @UseInterceptors 装饰器将 LoggingInterceptorTransformInterceptorCacheInterceptor 应用到 BooksController。每次请求到达 BooksController 的方法之前和之后,都会经过这三个拦截器。

结论

在这篇文章中,我们深入探讨了 Nest.js 中的拦截器,并通过实际例子展示了如何创建和使用拦截器。我们还学习了如何将拦截器应用到特定的控制器或全局范围内,以及如何使用拦截器进行日志记录、数据转换和缓存。

拦截器是处理请求和响应的强大工具,可以在请求到达控制器之前或响应发送到客户端之前执行一些操作。通过使用拦截器,我们可以实现日志记录、数据转换、缓存等功能,使应用更加灵活和高效。

感谢你的阅读!如果你有任何问题或建议,欢迎在评论区留言。我们下次再见!

预告

在下一篇文章中,我们将探讨 Nest.js 中的模块(Modules)。模块是组织和管理应用程序代码的基本单位,可以帮助我们更好地组织和管理代码。敬请期待!