白留白WHITE SPACE
返回所有文章
NestJS / 后端开发

NestJS 实战 09:Swagger 文档、限流与缓存

生成可访问的 API 文档页面。对全局请求启用限流保护,核心接口可单独调优。对热点查询接口加缓存,减少重复计算。

本文属于 NestJS BBS 实战教程,共 11 节,建议按顺序学习。

1. 这节课要实现的功能是什么

  • 生成可访问的 API 文档页面。
  • 对全局请求启用限流保护,核心接口可单独调优。
  • 对热点查询接口加缓存,减少重复计算。

2. 这节课涉及的 NestJS 知识点有哪些(先讲概念)

2.1 Swagger 的意义

Swagger 不只是“文档页面”,它是团队 API 契约。前端、测试、后端可以基于同一份定义协作。

2.2 限流到底在限制什么

限流限制的不是“接口功能”,而是“单位时间内允许请求多少次”。

例如:

  • 1 分钟最多请求 30 次;
  • 超过之后,请求会被框架直接拒绝;
  • 目的通常是防刷、防爆破、防误操作把系统打满。

所以限流本质上是在保护系统的吞吐边界,而不是在做业务判断。

2.3 限流的分层策略

  • 全局限流:兜底保护系统;
  • 路由限流:对高风险接口单独配置(如登录、发帖)。

2.4 默认是按什么维度限流

在当前项目使用的 @nestjs/throttler 版本里,默认追踪键来自 req.ip。
你可以先把它理解成:默认按 IP 限流。

这意味着同一个 IP 在时间窗口内请求次数过多时,会一起被限制。

2.5 如果不想按 IP,而想按用户维度限流怎么办

可以,自定义 ThrottlerGuard,重写它的追踪逻辑即可。

常见几种策略:

  • 按 IP:适合未登录接口;
  • 按 userId:适合登录后的个人配额控制;
  • 按 IP + userId:更稳妥,既区分用户,也保留来源信息。

真正要改的不是业务代码,而是“限流键”的生成规则。

2.6 多个 Guard 的执行顺序为什么很重要

Guard 不是乱序执行的。Nest 有明确顺序:

  1. 先全局 Guard;
  2. 再 Controller 级 Guard;
  3. 最后方法级 Guard;
  4. 同一层内按声明顺序执行。

这条规则在限流里非常重要。
因为如果一个 Guard 依赖另一个 Guard 先往 req 上写入数据,那么它们的执行顺序就必须明确。

2.7 为什么“全局按用户限流”比“全局按 IP 限流”更难一点

因为按用户 ID 限流通常依赖 req.user,而 req.user 只有在 JWT 鉴权完成后才会存在。

如果限流 Guard 是全局的,而 JwtAuthGuard 只是方法级的,那么执行顺序一定是:

  • 先全局限流;
  • 后方法级 JWT 鉴权。

这时全局限流根本拿不到 req.user,自然也没法按用户 ID 限流。

所以想做“全局 IP + userId 限流”,通常要先把 JWT 鉴权也放到全局层,再让限流 Guard 排在它后面。

2.8 缓存该加在哪里

优先加在读多写少的列表接口。写操作后要考虑失效策略,这样缓存才可靠。

2.9 这些能力为什么放在框架层

因为它们都是“横切工程能力”,不应耦合到具体业务 Service。

3. 实际开发中这些 NestJS 知识点被如何使用(详细代码)

3.1 src/main.ts - 启用 Swagger

import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

const swaggerConfig = new DocumentBuilder()
  .setTitle('NestJS BBS API')
  .setDescription('BBS tutorial APIs')
  .setVersion('1.0.0')
  .addBearerAuth()
  .build();

const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('api-docs', app, swaggerDocument);

3.2 src/app.module.ts - 全局缓存与限流

import { CacheModule } from '@nestjs/cache-manager';
import { APP_GUARD } from '@nestjs/core';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';

@Module({
  imports: [
    CacheModule.register({
      isGlobal: true,
      ttl: 10,
    }),
    ThrottlerModule.forRoot([
      {
        ttl: 60_000,
        limit: 30,
      },
    ]),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class AppModule {}

这一段全局限流的真实含义是:

  • 默认每个请求来源 1 分钟最多请求 30 次;
  • 超过之后,请求会被 ThrottlerGuard 拒绝;
  • 这是系统的兜底保护,不区分具体业务接口。

3.3 src/threads/threads.controller.ts - 路由级缓存与限流

import { CacheInterceptor } from '@nestjs/cache-manager';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { Throttle } from '@nestjs/throttler';

@ApiTags('threads')
@ApiBearerAuth()
@Controller('boards/:boardId/threads')
export class ThreadsController {
  @Get()
  @ApiOperation({ summary: 'List threads by board' })
  @UseInterceptors(CacheInterceptor)
  @Throttle({ default: { limit: 60, ttl: 60_000 } })
  list(
    @Param('boardId', ParseIntPipe) boardId: number,
    @Query() query: ListThreadsQueryDto,
  ) {
    return this.threadsService.list(boardId, query);
  }
}

这里对 list() 单独放宽了限流:

  • 全局默认是 1 分钟 30 次;
  • 但这个列表接口允许 1 分钟 60 次;
  • 原因通常是列表接口访问更频繁,业务上需要更高吞吐。

3.4 进阶方案:按 userId 或 IP + userId 限流

这不是当前分支已经落地的代码,而是你在真实项目里很常见的下一步扩展方案。

如果想按用户维度限流,可以自定义 Guard 并重写追踪键:

import { Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';

@Injectable()
export class UserIpThrottlerGuard extends ThrottlerGuard {
  protected async getTracker(req: Record<string, any>): Promise<string> {
    const userId = req.user?.userId;
    return userId ? `${req.ip}:${userId}` : req.ip;
  }
}

这段代码的意思是:

  • 已登录用户:按 IP + userId 生成限流键;
  • 未登录用户:退回按 IP 限流。

3.5 为什么这个进阶方案不能直接替换当前全局限流

因为你当前项目里:

  • 全局 Guard 是 ThrottlerGuard;
  • JwtAuthGuard 还是写在具体路由上的。

这会导致执行顺序是:

  1. 先全局限流;
  2. 再路由级 JWT 鉴权。

所以全局限流执行时,req.user 还没准备好。
这就是为什么“全局按用户限流”不是简单改一行代码,而是会牵涉 Guard 层级设计。

3.6 如果真的要做全局 UserIpThrottlerGuard,正确思路是什么

标准思路通常是:

  1. 把 JwtAuthGuard 也做成全局 Guard;
  2. 用 @Public() 标记注册、登录等公开接口;
  3. 把 UserIpThrottlerGuard 注册成第二个全局 Guard;
  4. 保证执行顺序是“先 JWT,后限流”。

示意代码:

providers: [
  {
    provide: APP_GUARD,
    useClass: JwtAuthGuard,
  },
  {
    provide: APP_GUARD,
    useClass: UserIpThrottlerGuard,
  },
];

这样之后,请求顺序才会变成:

  • 先恢复 req.user;
  • 再按 IP + userId 计算限流键。

3.7 本节落地价值

你的 API 从“能用”进入“可协作、可防刷、可优化”的工程化阶段。

4. 总结这节课知识点

  • 你掌握了 Swagger、限流、缓存三项高频工程能力。
  • 你理解了限流限制的是“时间窗口内的请求次数”。
  • 你知道了默认限流通常按 IP 生效,也知道了如何扩展到 userId 或 IP + userId。
  • 你理解了全局 Guard、Controller Guard、方法级 Guard 的执行顺序。
  • 你知道了全局策略和路由策略如何组合。
  • 你可以继续扩展到 Redis 缓存、多级限流和 API 分组文档。

5. 每节课分支

  • lesson-09-swagger-cache-throttle
  • 远程仓库:https://github.com/Shiuwn/nestjs-tutorial.git

返回课程目录

上一课:Interceptor 与 Exception Filter 统一响应风格

下一课:单元测试、E2E 测试与交付标准

— 感谢阅读 —