全局异常过滤器使用说明
概述
GlobalExceptionsFilter 是 NestJS 应用的全局异常处理过滤器,负责捕获和处理应用中所有的未处理异常,提供统一的错误响应格式。
核心功能
1. 统一异常捕获
- 捕获所有类型的异常(HttpException 和其他未知异常)
- 自动识别 HTTP 状态码
- 提供标准化的错误响应格式
2. 数据库异常处理
- 特殊处理 MySQL 重复条目错误(ER_DUP_ENTRY)
- 自动解析并格式化错误消息
- 提供用户友好的错误提示
3. 标准化响应格式
json
{
"code": 500,
"timestamp": "2024-01-01T00:00:00.000Z",
"path": "/api/users",
"msg": "错误消息"
}基础使用
注册全局过滤器
在 main.ts 中注册全局异常过滤器:
typescript
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { GlobalExceptionsFilter } from './common/filters/GlobalExceptionsFilter'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
// 注册全局异常过滤器
app.useGlobalFilters(new GlobalExceptionsFilter())
await app.listen(3000)
}
bootstrap()模块级别注册
在特定模块中注册:
typescript
import { Module } from '@nestjs/common'
import { APP_FILTER } from '@nestjs/core'
import { GlobalExceptionsFilter } from './common/filters/GlobalExceptionsFilter'
@Module({
providers: [
{
provide: APP_FILTER,
useClass: GlobalExceptionsFilter,
},
],
})
export class AppModule {}API 参考
Filter 方法
catch(exception, host)
处理捕获的异常
参数:
exception: 捕获的异常对象host: ArgumentsHost 对象,用于获取请求上下文
返回值: void
响应格式
typescript
interface ErrorResponse {
code: number // HTTP 状态码
timestamp: string // ISO 时间戳
path: string // 请求路径
msg: string // 错误消息
}完整示例
基础异常处理示例
typescript
// controller.ts
import { Controller, Get, Post } from '@nestjs/common'
import { HttpException, HttpStatus } from '@nestjs/common'
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id') id: string) {
// 抛出标准 HTTP 异常
throw new HttpException('用户不存在', HttpStatus.NOT_FOUND)
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
// 抛出自定义异常
if (!createUserDto.email) {
throw new HttpException('邮箱不能为空', HttpStatus.BAD_REQUEST)
}
// 业务逻辑...
}
}数据库异常处理示例
typescript
// service.ts
import { Injectable } from '@nestjs/common'
import { Repository } from 'typeorm'
import { User } from './entities/user.entity'
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto) {
try {
const user = this.usersRepository.create(createUserDto)
return await this.usersRepository.save(user)
} catch (error) {
// 数据库异常会被 GlobalExceptionsFilter 自动处理
throw error
}
}
}高级用法
自定义异常类型
typescript
// custom-exceptions.ts
import { HttpException, HttpStatus } from '@nestjs/common'
export class BusinessLogicException extends HttpException {
constructor(message: string, businessCode: number) {
super(
{
message,
businessCode,
timestamp: new Date().toISOString(),
},
HttpStatus.BAD_REQUEST,
)
}
}
// 使用自定义异常
@Post()
create(@Body() createUserDto: CreateUserDto) {
if (this.isEmailExists(createUserDto.email)) {
throw new BusinessLogicException('邮箱已存在', 1001)
}
}异常日志记录
typescript
// 增强版 GlobalExceptionsFilter
import { Logger } from '@nestjs/common'
@Catch()
export class GlobalExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger(GlobalExceptionsFilter.name)
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse<Response>()
const request = ctx.getRequest<Request>()
// 记录异常日志
this.logger.error(
`Exception occurred: ${exception.message}`,
exception.stack,
`Path: ${request.url}, Method: ${request.method}`
)
// 原有处理逻辑...
}
}环境相关的错误处理
typescript
@Catch()
export class GlobalExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const isDevelopment = process.env.NODE_ENV === 'development'
// 开发环境显示详细错误信息
if (isDevelopment) {
console.error('Detailed error:', exception)
}
// 生产环境隐藏敏感信息
const errorMessage = isDevelopment
? exception.message
: '服务器内部错误'
// 响应处理...
}
}最佳实践
1. 异常分类处理
typescript
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse<Response>()
const request = ctx.getRequest<Request>()
let status: number
let message: string
if (exception instanceof HttpException) {
// HTTP 异常处理
status = exception.getStatus()
message = exception.message
} else if (exception.code === 'ER_DUP_ENTRY') {
// 数据库重复条目异常
status = HttpStatus.CONFLICT
const match = exception.message.match(/Duplicate entry '(.+)' for/)
message = `${match[1]} 已存在`
} else {
// 未知异常处理
status = HttpStatus.INTERNAL_SERVER_ERROR
message = '服务器内部错误'
// 记录详细错误日志
console.error('Unhandled exception:', exception)
}
response.status(200).json({
code: status,
timestamp: new Date().toISOString(),
path: request.url,
msg: message,
})
}2. 错误码体系
typescript
// error-codes.ts
export enum ErrorCode {
// 用户相关错误 1000-1999
USER_NOT_FOUND = 1001,
USER_ALREADY_EXISTS = 1002,
INVALID_CREDENTIALS = 1003,
// 业务逻辑错误 2000-2999
INSUFFICIENT_PERMISSIONS = 2001,
INVALID_OPERATION = 2002,
// 系统错误 5000-5999
DATABASE_ERROR = 5001,
EXTERNAL_SERVICE_ERROR = 5002,
}
// 在异常处理中使用
response.status(200).json({
code: status,
errorCode: getErrorCode(exception), // 自定义函数获取错误码
timestamp: new Date().toISOString(),
path: request.url,
msg: message,
})3. 异常监控集成
typescript
// 集成 Sentry 等监控工具
import * as Sentry from '@sentry/node'
@Catch()
export class GlobalExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
// 发送到监控系统
Sentry.captureException(exception)
// 原有处理逻辑...
}
}注意事项
- 响应状态码:虽然内部使用 200 状态码,但
code字段包含实际的 HTTP 状态码 - 数据库异常:目前只处理 MySQL 的 ER_DUP_ENTRY 错误,可根据需要扩展
- 日志记录:生产环境中应配置适当的日志级别和存储方式
- 安全性:避免在错误消息中泄露敏感信息
- 性能考虑:异常处理应尽量轻量,避免影响系统性能
常见问题
Q: 如何排除特定路由不使用全局过滤器?
A: 可以在过滤器中添加路由排除逻辑:
typescript
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const request = ctx.getRequest<Request>()
// 排除健康检查等特定路由
if (request.url.includes('/health')) {
throw exception // 重新抛出,让默认处理器处理
}
// 正常处理逻辑...
}Q: 如何处理不同类型的数据库异常?
A: 扩展异常处理逻辑:
typescript
if (exception.code === 'ER_DUP_ENTRY') {
// 处理重复条目
} else if (exception.code === 'ER_NO_REFERENCED_ROW_2') {
// 处理外键约束错误
message = '关联数据不存在'
} else if (exception.code === 'ER_DATA_TOO_LONG') {
// 处理数据超长错误
message = '数据长度超出限制'
}Q: 如何在不同环境下返回不同的错误信息?
A: 根据环境变量调整错误信息:
typescript
const isProduction = process.env.NODE_ENV === 'production'
const errorMessage = isProduction
? '服务器内部错误'
: exception.messageQ: 如何记录异常堆栈信息?
A: 在开发环境中记录详细信息:
typescript
if (process.env.NODE_ENV === 'development') {
console.error('Exception stack:', exception.stack)
}Q: 如何与现有的错误处理机制集成?
A: 可以保持现有处理逻辑的同时添加全局过滤器:
typescript
// 保留控制器中的 try-catch
@Post()
async create(@Body() dto: CreateDto) {
try {
return await this.service.create(dto)
} catch (error) {
// 特定业务逻辑处理
if (error.code === 'CUSTOM_ERROR') {
throw new HttpException('自定义错误', HttpStatus.BAD_REQUEST)
}
throw error // 其他异常交给全局过滤器处理
}
}配置示例
完整的 main.ts 配置
typescript
import { NestFactory } from '@nestjs/core'
import { ValidationPipe } from '@nestjs/common'
import { AppModule } from './app.module'
import { GlobalExceptionsFilter } from './common/filters/GlobalExceptionsFilter'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
// 全局管道配置
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}))
// 全局异常过滤器
app.useGlobalFilters(new GlobalExceptionsFilter())
// CORS 配置
app.enableCors()
await app.listen(process.env.PORT || 3000)
}
bootstrap()