NestJS异常过滤器 (Exception Filters)
学习目标
- 理解异常过滤器在NestJS中的作用和地位
- 掌握全局异常处理的实现方法
- 学会创建和使用自定义异常
- 理解HTTP异常的使用场景
- 能够实现统一的错误响应格式
核心知识点
1. 异常过滤器概念
异常过滤器是NestJS中用于处理异常的组件,它们可以捕获应用程序中抛出的异常,并将其转换为适当的HTTP响应。异常过滤器的主要作用:
- 捕获应用程序中的异常
- 自定义错误响应格式
- 记录异常信息
- 处理不同类型的异常
2. 内置异常
NestJS提供了一系列内置的HTTP异常类,位于@nestjs/common包中:
HttpException:基础HTTP异常类BadRequestException:400错误UnauthorizedException:401错误ForbiddenException:403错误NotFoundException:404错误InternalServerErrorException:500错误- 等等
使用内置异常:
import { Controller, Get, NotFoundException } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get(':id')
findOne(@Param('id') id: string) {
const cat = this.catsService.findOne(id);
if (!cat) {
throw new NotFoundException(`Cat with id ${id} not found`);
}
return cat;
}
}3. 自定义异常
创建自定义异常类,继承自HttpException:
// forbidden.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class ForbiddenException extends HttpException {
constructor(message: string = 'Forbidden') {
super(message, HttpStatus.FORBIDDEN);
}
}4. 异常过滤器创建
创建异常过滤器需要实现ExceptionFilter接口:
// http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.getResponse(),
});
}
}5. 应用异常过滤器
控制器级别
使用@UseFilters()装饰器在控制器级别应用异常过滤器:
import { Controller, Get, UseFilters } from '@nestjs/common';
import { HttpExceptionFilter } from './http-exception.filter';
@Controller('cats')
@UseFilters(new HttpExceptionFilter())
export class CatsController {
// 控制器方法
}方法级别
使用@UseFilters()装饰器在方法级别应用异常过滤器:
import { Controller, Get, UseFilters } from '@nestjs/common';
import { HttpExceptionFilter } from './http-exception.filter';
@Controller('cats')
export class CatsController {
@Get()
@UseFilters(new HttpExceptionFilter())
findAll() {
// 方法实现
}
}全局级别
在应用程序级别注册全局异常过滤器:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();6. 捕获所有异常
创建一个捕获所有异常的过滤器:
// all-exceptions.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
// 确定状态码
const status = exception.getStatus ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
// 确定错误消息
const message = exception.message || 'Internal server error';
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}7. 依赖注入
异常过滤器可以通过依赖注入系统注入其他服务:
// logging-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, Inject } from '@nestjs/common';
import { LoggerService } from './logger.service';
@Catch()
export class LoggingExceptionFilter implements ExceptionFilter {
constructor(private loggerService: LoggerService) {}
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest();
// 记录异常
this.loggerService.error(
`Exception occurred: ${exception.message}`,
exception.stack,
`Request: ${request.method} ${request.url}`
);
// 继续处理异常
// ...
}
}8. 异常过滤器链
可以同时应用多个异常过滤器,它们会按照注册顺序执行:
@Controller('cats')
@UseFilters(new LoggingExceptionFilter(), new HttpExceptionFilter())
export class CatsController {
// 控制器方法
}9. 自定义异常响应
可以根据不同的异常类型返回不同的响应格式:
// custom-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, BadRequestException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class CustomExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
// 根据异常类型返回不同的响应格式
if (exception instanceof BadRequestException) {
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
errors: exception.getResponse(),
});
} else {
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.getResponse(),
});
}
}
}实践案例分析
案例:统一错误处理系统
需求分析
我们需要创建一个统一的错误处理系统,包括:
- 捕获所有类型的异常
- 记录异常信息
- 返回统一的错误响应格式
- 区分不同环境的错误处理(开发环境显示详细错误,生产环境显示友好错误)
实现步骤
- 创建自定义异常类
- 创建全局异常过滤器
- 集成日志服务
- 测试错误处理系统
代码实现
1. 创建自定义异常类
// business.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
export class BusinessException extends HttpException {
constructor(message: string, statusCode: HttpStatus = HttpStatus.BAD_REQUEST) {
super(message, statusCode);
}
}2. 创建日志服务
// logger.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class LoggerService {
error(message: string, stack?: string, context?: string) {
console.error(`[ERROR] ${context ? `[${context}] ` : ''}${message}`);
if (stack) {
console.error(stack);
}
}
warn(message: string, context?: string) {
console.warn(`[WARN] ${context ? `[${context}] ` : ''}${message}`);
}
info(message: string, context?: string) {
console.info(`[INFO] ${context ? `[${context}] ` : ''}${message}`);
}
debug(message: string, context?: string) {
console.debug(`[DEBUG] ${context ? `[${context}] ` : ''}${message}`);
}
}3. 创建全局异常过滤器
// global-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Inject } from '@nestjs/common';
import { Request, Response } from 'express';
import { LoggerService } from './logger.service';
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
constructor(private loggerService: LoggerService) {}
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
// 确定状态码
const status = exception.getStatus ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
// 确定错误消息
let message = exception.message || 'Internal server error';
// 开发环境显示详细错误,生产环境显示友好错误
const isProduction = process.env.NODE_ENV === 'production';
// 记录异常
this.loggerService.error(
`Exception occurred: ${message}`,
exception.stack,
`Request: ${request.method} ${request.url}`
);
// 构建错误响应
const errorResponse = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
message: isProduction && status === HttpStatus.INTERNAL_SERVER_ERROR
? 'An internal server error occurred'
: message,
...(!isProduction && exception.stack && { stack: exception.stack }),
};
// 返回错误响应
response
.status(status)
.json(errorResponse);
}
}4. 注册全局异常过滤器
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { GlobalExceptionFilter } from './global-exception.filter';
import { LoggerService } from './logger.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 创建日志服务实例
const loggerService = new LoggerService();
// 注册全局异常过滤器
app.useGlobalFilters(new GlobalExceptionFilter(loggerService));
await app.listen(3000);
}
bootstrap();5. 测试错误处理系统
创建测试控制器:
// test.controller.ts
import { Controller, Get, Post, Body, Param, BadRequestException } from '@nestjs/common';
import { BusinessException } from './business.exception';
@Controller('test')
export class TestController {
@Get('bad-request')
async testBadRequest() {
throw new BadRequestException('Invalid request parameters');
}
@Get('not-found/:id')
async testNotFound(@Param('id') id: string) {
throw new BusinessException(`Resource with id ${id} not found`, 404);
}
@Get('server-error')
async testServerError() {
throw new Error('Something went wrong');
}
@Post('business-error')
async testBusinessError(@Body() body: any) {
if (!body.name) {
throw new BusinessException('Name is required');
}
return { message: 'Success' };
}
}测试结果
开发环境
Bad Request 错误:
{ "statusCode": 400, "timestamp": "2023-10-01T10:00:00.000Z", "path": "/test/bad-request", "method": "GET", "message": "Invalid request parameters" }Not Found 错误:
{ "statusCode": 404, "timestamp": "2023-10-01T10:00:00.000Z", "path": "/test/not-found/123", "method": "GET", "message": "Resource with id 123 not found" }Server Error 错误:
{ "statusCode": 500, "timestamp": "2023-10-01T10:00:00.000Z", "path": "/test/server-error", "method": "GET", "message": "Something went wrong", "stack": "Error: Something went wrong\n at TestController.testServerError..." }
生产环境
- Server Error 错误(生产环境):
{ "statusCode": 500, "timestamp": "2023-10-01T10:00:00.000Z", "path": "/test/server-error", "method": "GET", "message": "An internal server error occurred" }
代码解析
自定义异常:
- 创建了
BusinessException类,用于处理业务逻辑错误 - 允许自定义错误消息和状态码
- 创建了
日志服务:
- 创建了
LoggerService类,用于记录异常信息 - 支持不同级别的日志(error, warn, info, debug)
- 创建了
全局异常过滤器:
- 捕获所有类型的异常
- 根据环境变量区分错误处理方式
- 记录详细的异常信息
- 返回统一的错误响应格式
测试验证:
- 创建了测试控制器,模拟不同类型的错误
- 在不同环境下测试错误处理效果
互动思考问题
思考:异常过滤器、中间件和守卫(Guards)的区别是什么?它们各自的使用场景是什么?
讨论:在生产环境中,为什么不应该向客户端返回详细的错误信息?
实践:尝试创建一个基于角色的异常过滤器,根据用户角色返回不同详细程度的错误信息。
挑战:如何实现异常的国际化处理,根据用户的语言设置返回不同语言的错误消息?
扩展:了解NestJS的管道(Pipes),思考它与异常过滤器的区别和配合使用的场景。
小结
本集我们学习了NestJS异常过滤器的核心概念和使用方法,包括:
- 异常过滤器的基本概念和作用
- 内置异常的使用方法
- 自定义异常的创建和使用
- 异常过滤器的创建和注册
- 全局异常处理的实现
- 不同环境下的错误处理策略
- 异常日志记录
通过实践案例,我们创建了一个完整的统一错误处理系统,展示了如何使用异常过滤器捕获和处理应用中的各种异常,提供一致的错误响应格式。异常过滤器是NestJS处理错误的重要组件,它使得错误处理更加集中和可管理。
在下一集中,我们将学习NestJS的管道(Pipes),了解如何使用管道进行数据验证和转换,确保输入数据的合法性。