- 五种实现方式:Middleware、Guard、Pipe、Interceptor、ExceptionFilter。
- Middleware:全局和路由级别的中间件。
- Guard:路由守卫,用于权限控制。
- Interceptor:拦截器,可以在目标 Controller 方法前后加入逻辑。
- Pipe:管道,用于参数验证和转换。
- ExceptionFilter:异常过滤器,处理抛出的异常并返回响应
一:Middleware
中间件是 Express 里的概念,Nest 的底层是 Express,所以自然也可以使用中间件,中间件可以用于执行各种任务,如日志记录、身份验证、请求修改等。NestJS 支持全局中间件和路由中间件两种形式。
1.创建中间件
使用Nest CLI可以快速创建一个中间件
nest g mi --no-spec --flat
--no-spec
:不生成测试文件。--flat
:不生成目录,直接生成文件。
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class LogMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log('before...');
next();
console.log('after...');
}
}
2.使用中间件
中间件需要通过NestJS的依赖注入系统来实例化。具体来说,需要在AppModule中实现NestModule接口,并配置中间件:
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PersonModule } from './person/person.module';
import { LogMiddleware } from './log.middleware';
@Module({
imports: [PersonModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LogMiddleware)
.forRoutes('*');//全局路由
}
}
二:Guard守卫
用于调用Controller之前判断权限,返回true或者false来决定是否放行
1.创建Guard
nest g guard person --no-spec
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class PersonGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
console.log('PersonGuard,检查权限' + context.switchToHttp().getRequest().url);
return true;
}
}
Guard 要实现 CanActivate 接口,实现 canActivate 方法,可以从 context 拿到请求的信息,然后做一些权限验证等处理之后返回 true 或者 false。
2.使用Guard
全局使用main.ts
使用app.useGlobalGuards(new PersonGuard())
const app = await NestFactory.create<NestExpressApplication>(AppModule);
//app.useStaticAssets(join(__dirname, 'public'), { prefix: '/static' });//此行会运行dist后的目录也就是distpublic而不是srcpublic
// console.log('Public path:', join(__dirname, 'public'));
app.useStaticAssets('public', { prefix: '/static' })
app.useGlobalGuards(new PersonGuard())
app.useGlobalInterceptors(new TimeInterceptor())
await app.listen(process.env.PORT ?? 3000);
}
局部使用代码,直接加在路由前@UseGuards()
@UseGuards(PersonGuard)
@Get(':id')
findOne(@Param('id') id: string) {
return this.personService.findOne(+id);
}
三:Interceptor拦截器
Interceptor 是拦截器的意思,可以在目标 Controller 方法前后加入一些逻辑:
1.创建intercept
nest g intercept time--no-spec
生成的intercept代码如下:
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class TimeInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const startTime = Date.now();
return next.handle().pipe(
tap(() => {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const endTime = Date.now();
const duration = endTime - startTime;
console.log(`${request.method} ${request.url} ${response.statusCode} ${duration}ms`);
}),
);
}
}
Interceptor 要实现 NestInterceptor 接口,实现 intercept 方法,调用 next.handle() 就会调用目标 Controller,可以在之前和之后加入一些处理逻辑。
2.使用interceptor
全局使用如上guard代码,使用 app.useGlobalInterceptors(new TimeInterceptor())
,局部使用
@UseInterceptors(TimeInterceptor)
@Controller('api/person')
export class PersonController {
// eslint-disable-next-line prettier/prettier
constructor(private readonly personService: PersonService) { }
除了路由的权限控制guard、目标 Controller 之前之后的处理interceptor这些都是通用逻辑外,对参数的处理也是一个通用的逻辑,所以 Nest 也抽出了对应的切面,也就是 Pipe:
四:Pipe 管道
Pipe 是管道的意思,用来对参数做一些检验和转换:
1.创建Pipe
nest g pi vaildate --no-spec --flat
Pipe 要实现 PipeTransform 接口,实现 transform 方法,里面可以对传入的参数值 value 做参数验证,比如格式、类型是否正确,不正确就抛出异常。也可以做转换,返回转换后的值。
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class VaildatePipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const parsedValue = parseInt(value)
if (isNaN(parsedValue)) {
throw new BadRequestException(`参数${metadata.data}必须是整数`)
}
return parsedValue * 2;
}
}
如果传参value不能转换数字抛出异常,否则*2返回再传入handler
2.使用 Pipe
直接在控制器方法参数中使用
@Get('age')
findAge(@Query("age", VaildatePipe) query: number) {
return query ;
}
使用 @UsePipes
装饰器
import { Controller, Post, Body, UsePipes } from '@nestjs/common';
import { VaildatePipe } from './vaildate.pipe';
@Controller('example')
export class ExampleController {
@Post('validate')
@UsePipes(VaildatePipe)
validateNumber(@Body('number') number: number) {
return { result: number };
}
}
返回结果
Nest 内置了一些 Pipe,从名字就能看出它们的意思:
管道名称 | 作用 |
---|---|
ValidationPipe | 验证传入的数据是否符合指定的 DTO 的结构和规则。 通常与类验证器(class-validator)和类转换器(class-transformer)一起使用。 |
ParseIntPipe | 将传入的参数解析为整数。如果参数不能转换为整数,则抛出异常。 |
ParseBoolPipe | 将传入的参数解析为布尔值。如果参数不能转换为布尔值,则抛出异常。 |
ParseArrayPipe | 将传入的参数解析为数组。可以指定数组中的元素类型和验证规则。 |
ParseUUIDPipe | 验证传入的参数是否为有效的 UUID。如果不是有效的 UUID,则抛出异常。 |
DefaultValuePipe | 如果传入的参数未提供或为undefined ,则使用默认值。 |
ParseEnumPipe | 验证传入的参数是否为指定枚举类型中的一个值。如果不是,则抛出异常。 |
ParseFloatPipe | 将传入的参数解析为浮点数。如果参数不能转换为浮点数,则抛出异常。 |
ParseFilePipe | 用于验证和处理文件上传。可以指定文件的类型、大小等验证规则。 |
不管是 Pipe、Guard、Interceptor 还是最终调用的 Controller,过程中都可以抛出一些异常,如何对某种异常做出某种响应呢?
这种异常到响应的映射也是一种通用逻辑,Nest 提供了 ExceptionFilter 来支持:
五:ExceptionFilter
1.创建filter
nest g f test --no-spec --flat
创建后改一下:
import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common';
@Catch(BadRequestException)
export class TestFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message
});
}
}
上述代码定义了一个异常过滤器 TestFilter
,用于捕获并处理 BadRequestException
异常。它将异常信息封装成 JSON 格式返回给客户端,包含状态码、时间戳、请求路径和错误信息。
exception
和 host
的作用
exception
:- 类型为
BadRequestException
,表示捕获到的异常对象。 - 包含异常信息,如状态码和错误消息。
- 类型为
host
:- 类型为
ArgumentsHost
,表示当前执行上下文的封装。 - 提供了访问 HTTP 请求和响应对象的方法,允许你在异常处理过程中与请求/响应进行交互。
- 类型为
2.使用filter
实现 ExceptionFilter 接口,实现 catch 方法,就可以拦截异常了。
拦截什么异常用 @Catch 装饰器来声明,然后在 catch 方法返回对应的响应,给用户更友好的提示。
全局使用app.useGlobalFilters(new TestFilter())
,控制器使用@UseFilters(TestFilter)
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useGlobalFilters(new TestFilter())
await app.listen(process.env.PORT ?? 3000);
}
返回结果:
Nest 内置了很多 http 相关的异常,都是 HttpException 的子类:
内置异常的作用场景
异常类 | 使用场景 |
---|---|
BadRequestException | 请求数据格式不正确或不符合预期。 |
UnauthorizedException | 用户未通过身份验证,无权访问资源。 |
NotFoundException | 请求的资源不存在。 |
ForbiddenException | 用户已通过身份验证,但无权执行操作。 |
NotAcceptableException | 客户端请求的内容类型不被服务器接受。 |
RequestTimeoutException | 请求处理时间过长,服务器超时。 |
ConflictException | 请求与当前资源状态冲突,无法完成操作。 |
GoneException | 请求的资源已永久删除。 |
PayloadTooLargeException | 请求负载过大,服务器无法处理。 |
UnsupportedMediaTypeException | 请求的内容类型不受支持。 |
UnprocessableException | 请求有效,但无法处理(例如,验证失败)。 |
InternalServerErrorException | 服务器内部错误,无法完成请求。 |
NotImplementedException | 请求的功能尚未实现。 |
BadGatewayException | 作为网关或代理时,上游服务器返回无效响应。 |
ServiceUnavailableException | 服务器当前无法处理请求,通常是由于过载或维护。 |
GatewayTimeoutException | 作为网关或代理时,上游服务器响应超时。 |