NestJS本身内置了异常处理,但是它只会处理HttpException,如果一个异常,不是HttpException,那么内置的异常处理机制会统一抛出
{
"statusCode": 500,
"message": "Internal server error"
}
这种情况下,就需要我们手动编写一个Filter类。NestCli已经提供了创建方式,执行nest g filter db-exception --no-spec
src目录下就创建了一个db-exception文件夹。这里推荐创建一个filter文件夹,然后将db-exception放在db-exception下。
复制官网的模板代码
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter {
catch(exception, host) {
const ctx = host.switchToHttp();
const request = ctx.getRequest();
}
假设我们的异常是在数据库中插入了一个主键重复的元素。那么对应的异常就是UniqueConstraintViolationException我们就可以简单的将代码改造为:
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import { UniqueConstraintViolationException } from '@mikro-orm/core';
@Catch()
export class DbExceptionFilter<T> implements ExceptionFilter {
catch(exception: T, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
if (exception instanceof UniqueConstraintViolationException) {
response.status(HttpStatus.CONFLICT).json({
message: exception.message,
});
}
}
}
和filter一样,有两种使用方式,第一种是在对应的controller中的方法使用装饰器 @UseFilters(new DbExceptionFilter())
第二种就是全局使用,在main.ts中添加:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
但是这样做很显然不明智,因为前端的message会收到没有经过处理的错误信息,既杂乱,也很可能暴露数据库中的数据。
因此,可以对message做简单的包装:
response.status(HttpStatus.CONFLICT).json({
message: "Resource already exists",
});
如果异常信息只有1,2个这样做可行,但是如果对每一个异常都用if判断一下,就显得不太聪明。因此我们可以简单创建一个映射表。
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import {
DriverException,
} from '@mikro-orm/core';
//key就是异常的名字,value是http状态码和返回的消息
const DB_HTTP_MAP: Record<string, { status: number; message: string }> = {
UniqueConstraintViolationException: {
status: HttpStatus.CONFLICT,
message: 'Unique constraint violation',
},
};
@Catch()
export class DbExceptionFilter<T> implements ExceptionFilter {
catch(exception: DriverException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const { status, message } = DB_HTTP_MAP[exception.name];
response.status(status).json({ message });
}
}
这里我们把异常的类型设置为了DriverException,但是这样做会把所有的异常都当作是Mikro-ORM中的异常进行识别和处理。如果抛出的异常不是Mikro-ORM中的异常,例如是最简单的Http中的异常,其实不需要我们这里进行处理,而是应该直接交给nestJS内置的工具自行处理。
因此,尽可能的不要将filter作用于全局,或者filter上写明具体匹配的异常的类,这样就能保证异常处理器只匹配这一类异常。
这样,我们可以把Mikro-ORM中所有的异常类都放在@Catch中
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import {
UniqueConstraintViolationException,
CheckConstraintViolationException,
DriverException,
} from '@mikro-orm/core';
//key就是异常的名字,value是http状态码和返回的消息
const DB_HTTP_MAP: Record<string, { status: number; message: string }> = {
UniqueConstraintViolationException: {
status: HttpStatus.CONFLICT,
message: 'Unique constraint violation',
},
};
@Catch(UniqueConstraintViolationException, CheckConstraintViolationException)
export class DbExceptionFilter<T> implements ExceptionFilter {
catch(exception: DriverException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const { status, message } = DB_HTTP_MAP[exception.name];
response.status(status).json({ message });
}
}