本文属于 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 有明确顺序:
- 先全局 Guard;
- 再 Controller 级 Guard;
- 最后方法级 Guard;
- 同一层内按声明顺序执行。
这条规则在限流里非常重要。
因为如果一个 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还是写在具体路由上的。
这会导致执行顺序是:
- 先全局限流;
- 再路由级 JWT 鉴权。
所以全局限流执行时,req.user 还没准备好。
这就是为什么“全局按用户限流”不是简单改一行代码,而是会牵涉 Guard 层级设计。
3.6 如果真的要做全局 UserIpThrottlerGuard,正确思路是什么
标准思路通常是:
- 把
JwtAuthGuard也做成全局 Guard; - 用
@Public()标记注册、登录等公开接口; - 把
UserIpThrottlerGuard注册成第二个全局 Guard; - 保证执行顺序是“先 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