Skip to content

异常捕获&统一异常格式

全局捕获:捕获自定义业务异常、捕获 http 异常、捕获其他异常

统一格式:捕获后统一异常格式 :::

1、自定义业务异常

ts
// src/common/exceptions/business.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';

// 业务异常接口(规范返回字段)
export interface BusinessExceptionOptions {
  code: number; // 业务码(非HTTP状态码)
  message: string; // 错误消息
  httpStatus?: HttpStatus; // HTTP状态码,默认500
  data?: any; // 附加数据
}

/**
 * 业务异常基类
 * 所有业务相关异常都继承此类
 */
export class BusinessException extends HttpException {
  readonly code: number;
  readonly data?: any;

  constructor(options: BusinessExceptionOptions) {
    // 调用父类构造器,传递响应体和HTTP状态码
    super(
      {
        code: options.code,
        message: options.message,
        data: options.data,
      },
      options.httpStatus ?? HttpStatus.INTERNAL_SERVER_ERROR,
    );

    this.code = options.code;
    this.data = options.data;
  }

  // 快捷方法:创建参数错误异常(HTTP 400)
  static badRequest(code: number, message: string, data?: any) {
    return new BusinessException({
      code,
      message,
      httpStatus: HttpStatus.BAD_REQUEST,
      data,
    });
  }

  // 快捷方法:创建未授权异常(HTTP 401)
  static unauthorized(code: number, message: string, data?: any) {
    return new BusinessException({
      code,
      message,
      httpStatus: HttpStatus.UNAUTHORIZED,
      data,
    });
  }
}

2、定义异常过滤器

ts
// src/common/filters/global-exception.filter.ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { BusinessException } from '../exceptions/business.exception';

/**
 * 全局异常过滤器
 * 捕获所有异常,统一响应格式
 */
@Catch() // 不指定异常类型 = 捕获所有异常
export class GlobalExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(GlobalExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    // 获取HTTP上下文(req/res)
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    // 1. 初始化默认响应参数
    let status = HttpStatus.INTERNAL_SERVER_ERROR; // 默认500
    let code = 50000; // 默认业务码
    let message = '服务器内部错误';
    let error = undefined;
    let data: null;
    let stack: string | undefined = undefined;

    // 2. 区分异常类型处理
    if (exception instanceof BusinessException) {
      // 处理自定义业务异常
      status = exception.getStatus();
      code = exception.code;
      message = exception.message;
      data = exception.data;
      stack = (exception.cause as Error)?.stack; // 根因栈信息
      this.logger.warn(
        `[业务异常] ${request.method} ${request.url} | 业务码: ${code} | 消息: ${message}`,
      );
    } else if (exception instanceof HttpException) {
      // 处理内置HTTP异常(非业务异常)
      status = exception.getStatus();
      const responseBody = exception.getResponse();
      // 兼容Nest内置响应格式(字符串或对象)
      if (typeof responseBody === 'string') {
        message = responseBody;
      } else {
        message = (responseBody as any).message || message;
        code = (responseBody as any).code || status * 100; // 适配默认格式
        error = (responseBody as any).error;
      }
      this.logger.warn(
        `[HTTP异常] ${request.method} ${request.url} | HTTP状态码: ${status} | 消息: ${message}`,
      );
    } else {
      // 处理未知异常(如原生Error、字符串等)
      message =
        exception instanceof Error ? exception.message : String(exception);
      stack = exception instanceof Error ? exception.stack : undefined;
      this.logger.error(
        `[未知异常] ${request.method} ${request.url} | 消息: ${message}`,
        stack,
      );
    }

    // 3. 构建标准化响应体
    const responseBody = {
      code,
      message,
      data: data || null,
      path: request.url,
      method: request.method,
      timestamp: new Date().toISOString(),
      // 开发环境返回栈信息,生产环境隐藏
      ...(process.env.NODE_ENV === 'development' && { stack, error }),
    };

    // 4. 发送响应
    response.status(status).json(responseBody);
  }
}

3、定义全局异常过滤器

ts
// src/main.ts

import { GlobalExceptionFilter } from './common/filters/global-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 注册全局异常过滤器
  app.useGlobalFilters(new GlobalExceptionFilter());

  app.setGlobalPrefix('api/v1');
  await app.listen(3000);
}
bootstrap();

4、测试

  1. http 异常测试
shell
# 请求一个不存在的路由
http://localhost:3000/api/v1/user123456

# 结果
{
    "code": 40400,
    "message": "Cannot POST /api/v1/user123465",
    "data": null,
    "path": "/api/v1/user123465",
    "method": "POST",
    "timestamp": "2025-12-09T08:07:41.751Z",
    "error": "Not Found"
}
  1. 自定义业务异常测试
shell
# 在 src/user/user.controller.ts 中,使用自定义异常
import { BusinessException } from '../common/exceptions/business.exception';
@Post()
async create(@Body() CreateUserDto: CreateUserDto) {
    throw BusinessException.badRequest(40001, '使用自定义异常');
}

# 进入此路由并触发自定义异常
http://localhost:3000/api/v1/user # 方法: POST 、参数: { "username": "alias", "password": "1212343" }

# 结果
{
    "code": 40001,
    "message": "使用自定义异常",
    "data": null,
    "path": "/api/v1/user",
    "method": "POST",
    "timestamp": "2025-12-09T08:11:42.664Z"
}
  1. 其他系统异常测试
shell
# 在 src/user/user.controller.ts 中,使用自定义异常
@Post()
async create(@Body() CreateUserDto: CreateUserDto) {
  throw new Error('报错了哦!')
}

# 进入此路由并触发异常
http://localhost:3000/api/v1/user # 方法: POST 、参数: { "username": "alias", "password": "1212343" }

# 结果
{
    "code": 50000,
    "message": "报错了哦!",
    "data": null,
    "path": "/api/v1/user",
    "method": "POST",
    "timestamp": "2025-12-09T08:13:39.811Z",
    "stack": "Error: 报错了哦!\n    at UserController.create (/Users/macbookpro/Desktop/nest 学习模板/3.1、nest-Mysql-TypeORM-Repository/src/user/user.controller.ts:23:11)\n    at /Users/macbookpro/Desktop/nest 学习模板/3.1、nest-Mysql-TypeORM-Repository/node_modules/.pnpm/@nestjs+core@10.4.17_@nestjs+common@10.4.17_class-transformer@0.5.1_class-validator@0.14.3_re_42tscuezzqw2ny42cjwxmnswou/node_modules/@nestjs/core/router/router-execution-context.js:38:29\n    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"
}