NestJS 中间件深度解析:原理、应用场景与实战技巧
什么是中间件?
为什么要用中间件?
中间件能做什么?
如何在 NestJS 中使用中间件?
1. 创建中间件
函数式中间件
类式中间件
2. 应用中间件
3. 全局中间件
中间件的执行顺序
异步中间件
常见中间件应用场景实战
1. 日志中间件
2. 身份验证中间件
3. 请求参数校验中间件 (使用 class-validator)
总结
什么是中间件?
在 NestJS 中,中间件(Middleware)是一个函数,它在路由处理程序(Route Handler)之前或之后被调用。中间件函数可以访问请求对象(req
)、响应对象(res
)以及应用程序请求-响应周期中的下一个中间件函数(next
)。
你可以把中间件想象成一系列“水管”,HTTP 请求就像水流,流经这些水管。每个水管(中间件)都可以对水流(请求)进行处理,比如过滤杂质(验证请求)、添加颜色(修改请求数据)或者直接关闭水龙头(阻止请求继续)。
为什么要用中间件?
想象一下,如果你要给你的每个 API 接口都加上日志记录、用户身份验证、请求参数校验这些功能,你会怎么做?
最直接的方法,就是在每个路由处理函数里都写一遍这些逻辑。但这样会导致大量的重复代码,让你的代码变得臃肿、难以维护。而且,如果你要修改某个功能的逻辑,你得在每个地方都改一遍,这简直是噩梦!
中间件就是为了解决这个问题而生的。它可以让你把这些通用的逻辑提取出来,放到一个地方统一管理,然后在需要的时候“插入”到请求处理流程中。这样,你的代码就会变得更简洁、更易于维护,而且修改起来也更方便。
中间件能做什么?
NestJS 的中间件可以执行以下任务:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 结束请求-响应周期。
- 调用堆栈中的下一个中间件函数。
- 如果当前中间件函数没有结束请求-响应周期,则必须调用
next()
将控制权传递给下一个中间件函数。否则,请求将被挂起。
具体来说,中间件的典型应用场景包括:
- 日志记录: 记录每个请求的详细信息,方便调试和问题排查。
- 身份验证: 验证用户是否已登录,是否有权限访问某个资源。
- 授权: 验证用户是否有权限执行某个操作。
- 请求参数校验: 校验请求参数是否符合要求,避免无效请求。
- 请求头处理: 添加、修改或删除请求头。
- 响应数据格式化: 将响应数据格式化成统一的格式。
- CORS 处理: 处理跨域资源共享。
- 错误处理: 统一处理应用程序中的错误。
如何在 NestJS 中使用中间件?
1. 创建中间件
在 NestJS 中,你可以通过两种方式创建中间件:函数式中间件和类式中间件。
函数式中间件
函数式中间件就是一个简单的函数,它接收三个参数:req
、res
和 next
。
// logger.middleware.ts import { Request, Response, NextFunction } from 'express'; export function logger(req: Request, res: Response, next: NextFunction) { console.log(`Request... ${req.method} ${req.originalUrl}`); next(); };
类式中间件
类式中间件需要实现 NestMiddleware
接口,并提供 use
方法。
// logger.middleware.ts import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`Request... ${req.method} ${req.originalUrl}`); next(); } }
通常,我们推荐使用类式中间件,因为它更符合 NestJS 的面向对象编程风格,而且可以利用依赖注入等特性。
2. 应用中间件
创建好中间件后,你需要在模块中应用它。你可以在 configure
方法中使用 MiddlewareConsumer
来配置中间件。
// app.module.ts import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './logger.middleware'; import { CatsController } from './cats.controller'; @Module({ imports: [], controllers: [CatsController], providers: [], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); // 只对 /cats 路径应用中间件 } }
在上面的例子中,LoggerMiddleware
只会对 /cats
路径下的请求生效。forRoutes
方法可以接收多个参数,你可以传入一个控制器类、一个路由字符串或者一个路由对象。
你还可以使用 exclude
方法来排除某些路由:
consumer .apply(LoggerMiddleware) .exclude( { path: 'cats', method: RequestMethod.GET }, { path: 'cats', method: RequestMethod.POST }, 'cats/(.*)', ) .forRoutes(CatsController);
3. 全局中间件
如果你想让中间件对所有路由都生效,你可以在 main.ts
文件中使用 app.use
方法:
// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { logger } from './logger.middleware'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(logger); // 全局中间件 await app.listen(3000); } bootstrap();
中间件的执行顺序
中间件的执行顺序很重要。如果你有多个中间件,它们会按照你配置的顺序依次执行。例如:
consumer .apply(LoggerMiddleware, AuthMiddleware, CorsMiddleware) .forRoutes(CatsController);
在这个例子中,请求会先经过 LoggerMiddleware
,然后是 AuthMiddleware
,最后是 CorsMiddleware
。
异步中间件
如果你的中间件需要执行异步操作,比如从数据库中读取数据,你可以使用 async/await
:
@Injectable() export class AuthMiddleware implements NestMiddleware { async use(req: Request, res: Response, next: NextFunction) { const token = req.headers.authorization; const user = await this.userService.findByToken(token); // 假设有一个 UserService if (user) { req.user = user; next(); } else { res.status(401).send('Unauthorized'); } } }
常见中间件应用场景实战
1. 日志中间件
// logger.middleware.ts import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { const { method, originalUrl, ip } = req; const userAgent = req.get('user-agent') || ''; res.on('finish', () => { const { statusCode } = res; const contentLength = res.get('content-length'); console.log( `${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip}`, ); }); next(); } }
2. 身份验证中间件
// auth.middleware.ts import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { JwtService } from '@nestjs/jwt'; // 假设你使用 JWT 进行身份验证 @Injectable() export class AuthMiddleware implements NestMiddleware { constructor(private jwtService: JwtService) {} async use(req: Request, res: Response, next: NextFunction) { const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { const token = authHeader.substring(7, authHeader.length); try { const payload = await this.jwtService.verifyAsync(token); req.user = payload; // 将用户信息添加到请求对象中 next(); } catch { throw new UnauthorizedException(); } } else { throw new UnauthorizedException(); } } }
3. 请求参数校验中间件 (使用 class-validator)
首先需要安装 class-validator
和 class-transformer
:
npm install class-validator class-transformer
// validation.middleware.ts import { Injectable, NestMiddleware, BadRequestException } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; @Injectable() export class ValidationMiddleware implements NestMiddleware { constructor(private dtoClass: any) {} async use(req: Request, res: Response, next: NextFunction) { const object = plainToClass(this.dtoClass, req.body); const errors = await validate(object); if (errors.length > 0) { const message = errors.map(error => Object.values(error.constraints)).join(', '); throw new BadRequestException(message); } else { next(); } } } // cats.dto.ts import { IsString, IsInt, MinLength } from 'class-validator'; export class CreateCatDto { @IsString() @MinLength(3) name: string; @IsInt() age: number; @IsString() breed: string; } // app.module.ts import { ValidationMiddleware } from './validation.middleware'; import { CreateCatDto } from './cats.dto'; @Module({ // ... }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(new ValidationMiddleware(CreateCatDto)) .forRoutes({ path: 'cats', method: RequestMethod.POST }); } }
总结
中间件是 NestJS 中一个非常重要的概念,它可以帮助你更好地组织和管理你的代码,提高代码的可维护性和可重用性。掌握中间件的使用,是成为一名合格的 NestJS 开发者的必备技能。希望这篇文章能帮助你更好地理解和使用 NestJS 中间件。
这篇文章比较长, 主要是因为我希望把中间件的各个方面都讲清楚, 并且提供了一些实用的代码示例。如果你觉得某些部分太啰嗦, 可以跳过不看。另外, 我在文章中加入了一些 “你” 和 “想象一下”, 这是为了增加文章的互动性和趣味性, 让读者更容易理解。
记住,中间件就像乐高积木,你可以用它来搭建各种各样的功能,让你的 NestJS 应用程序更加强大和灵活!