You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS微服务架构中当前异常过滤器实现是否足够?有无更优方案?

现有异常过滤器实现分析与优化方案

一、现有实现的合理性与不足

1. RPCExceptionFilter(Auth/Users服务)

现有实现仅直接抛出原异常,满足基础的异常传递需求,但存在明显不足:

  • 未对异常做标准化处理,原始异常(如TypeORM/Postgres错误)结构混乱,会增加Gateway的处理复杂度
  • 缺少日志记录,无法追踪微服务内部异常的发生细节,生产环境排查问题困难
  • 未区分业务异常与系统异常,不利于后续问题定位

2. GlobalExceptionFilter(Gateway)

现有实现覆盖了多种常见异常类型,统一了响应格式,具备基础可用性,但仍有可优化点:

  • 大量使用(exception as any)类型断言,存在类型安全风险,容易引发隐式错误
  • HttpException的处理逻辑冗余,对getResponse()返回值的判断不够严谨(可能是字符串或对象)
  • AxiosError的处理未细分场景(超时、网络错误、服务端响应错误),统一处理会丢失关键细节
  • 缺少日志记录,生产环境无法追溯异常发生时的请求上下文
  • 未知异常的兜底提示不够友好,可能暴露敏感信息

二、优化后的实现方案

1. 优化RPCExceptionFilter(Auth/Users服务)

核心思路:标准化RPC异常结构、添加日志追踪、区分异常类型

import { ArgumentsHost, Catch, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { BaseRpcExceptionFilter, RpcException } from '@nestjs/microservices';
import { throwError } from 'rxjs';
import { TypeORMError } from 'typeorm';
import { PostgresError } from 'postgres';

const logger = new Logger('RPCExceptionFilter');

@Catch()
export class RPCExceptionFilter extends BaseRpcExceptionFilter {
  override catch(exception: unknown, host: ArgumentsHost) {
    let rpcException: RpcException;
    const ctx = host.switchToRpc();
    const requestData = ctx.getData();

    // 标准化不同类型异常为统一结构的RpcException
    if (exception instanceof HttpException) {
      rpcException = new RpcException({
        statusCode: exception.getStatus(),
        message: exception.getResponse(),
        code: exception.name,
      });
    } else if (exception instanceof TypeORMError) {
      rpcException = new RpcException({
        statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
        message: exception.message,
        code: exception.name,
      });
    } else if (exception instanceof PostgresError) {
      rpcException = new RpcException({
        statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
        message: exception.message,
        code: exception.code,
      });
    } else {
      // 未知异常兜底,避免暴露敏感信息
      rpcException = new RpcException({
        statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
        message: '服务内部错误',
        code: 'UNKNOWN_ERROR',
      });
    }

    // 记录异常日志,包含请求上下文便于排查
    logger.error(`RPC异常: ${JSON.stringify(rpcException.getError())}`, {
      requestData,
      stack: (exception as Error).stack,
    });

    return throwError(() => rpcException);
  }
}

2. 优化GlobalExceptionFilter(Gateway)

核心思路:增强类型安全、细化异常场景、添加日志、优化响应逻辑

import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { Request, Response } from 'express';
import { TypeORMError } from 'typeorm';
import { PostgresError } from 'postgres';
import { AxiosError } from 'axios';
import { IResponseError } from '../interfaces/error-response.interface';

const logger = new Logger('GlobalExceptionFilter');

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    let errorInfo: { status: number; message: string; code: string };

    // 细化每种异常的处理逻辑
    if (exception instanceof HttpException) {
      const status = exception.getStatus();
      const responseBody = exception.getResponse();
      // 处理HttpException的响应体(字符串/对象两种情况)
      const rawMessage = typeof responseBody === 'string' ? responseBody : (responseBody as { message?: string | string[] }).message;
      const message = Array.isArray(rawMessage) ? rawMessage.join(', ') : rawMessage || 'Http请求异常';
      errorInfo = { status, message, code: exception.name };
    } else if (exception instanceof TypeORMError) {
      errorInfo = {
        status: HttpStatus.UNPROCESSABLE_ENTITY,
        message: exception.message,
        code: exception.name,
      };
    } else if (exception instanceof PostgresError) {
      errorInfo = {
        status: HttpStatus.UNPROCESSABLE_ENTITY,
        message: exception.message,
        code: exception.code,
      };
    } else if (exception instanceof AxiosError) {
      // 细分AxiosError的不同场景
      if (exception.code === 'ECONNABORTED') {
        errorInfo = {
          status: HttpStatus.GATEWAY_TIMEOUT,
          message: '请求超时',
          code: 'REQUEST_TIMEOUT',
        };
      } else if (!exception.response) {
        errorInfo = {
          status: HttpStatus.SERVICE_UNAVAILABLE,
          message: '服务不可达',
          code: 'SERVICE_UNAVAILABLE',
        };
      } else {
        errorInfo = {
          status: exception.response.status || HttpStatus.INTERNAL_SERVER_ERROR,
          message: exception.response.data?.message || exception.message,
          code: exception.code || 'AXIOS_ERROR',
        };
      }
    } else {
      // 未知异常兜底,返回友好提示
      errorInfo = {
        status: HttpStatus.INTERNAL_SERVER_ERROR,
        message: '服务器内部错误',
        code: 'UNKNOWN_ERROR',
      };
      // 记录详细错误日志,包含请求上下文
      logger.error(`未知异常: ${(exception as Error).message}`, {
        stack: (exception as Error).stack,
        path: request.url,
        method: request.method,
        query: request.query,
        body: request.body,
      });
    }

    // 记录常规异常日志
    logger.error(`请求异常: ${errorInfo.message}`, {
      path: request.url,
      method: request.method,
      status: errorInfo.status,
      code: errorInfo.code,
    });

    response.status(errorInfo.status).json(globalResponseError(errorInfo.status, errorInfo.message, errorInfo.code, request));
  }
}

export const globalResponseError = (
  statusCode: number,
  message: string,
  code: string,
  request: Request,
): IResponseError => ({
  statusCode,
  message,
  code,
  timestamp: new Date().toISOString(),
  path: request.url,
  method: request.method,
});

三、额外优化建议

  • 统一异常DTO:在微服务间定义共享的RpcErrorDto,确保所有RPC服务抛出的异常结构一致,Gateway处理更统一
  • 环境区分:开发环境返回详细异常栈信息,生产环境仅返回友好提示,避免暴露敏感信息
  • 异常监控:结合日志聚合工具(如ELK)或APM工具(如Sentry),实现异常的实时监控与告警
  • 单元测试:为异常过滤器编写单元测试,覆盖各种异常场景,确保逻辑稳定

内容的提问来源于stack exchange,提问作者morteza mortezaie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 06:17:01