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

如何在NestJS微服务中实现精准的异常处理?

问题分析与解决方案

一、实现针对性异常区分

1. 修复服务层核心逻辑并抛出明确异常

首先修正findByEmail的异步调用bug,并创建自定义异常类用于区分不同错误场景:

// 自定义异常类
export class UserNotFoundException extends Error {
  constructor() {
    super('邮箱未注册');
    this.name = 'UserNotFoundException';
  }
}

export class InvalidCredentialsException extends Error {
  constructor() {
    super('无效凭据');
    this.name = 'InvalidCredentialsException';
  }
}

// 服务层findByEmail方法
async findByEmail(email: string): Promise<User> {
  // 必须添加await,否则user是Promise对象,!user永远为false
  const user = await this.userModel.findOne({ email }).exec();
  if (!user) {
    throw new UserNotFoundException();
  }
  return user;
}

// 服务层login方法
async login(email: string, password: string): Promise<{ user: User; token: string; refreshToken: string }> {
  const user = await this.findByEmail(email);
  
  const comparePassword = await passwordService.comparePasswords(password, user.password);
  if (!comparePassword) {
    throw new InvalidCredentialsException();
  }
  
  const { token, refreshToken } = await this.sessionService.createSession(user._id, {
    userId: user._id,
    type: user.type,
  });
  return { user, token, refreshToken };
}

2. GRPC控制器封装对应GRPC状态码

在GRPC控制器中根据异常类型,封装带状态码的RpcException:

import { Status } from '@grpc/grpc-js';

@GrpcMethod('UserService', 'Login')
async login(@Payload() payload: Login): Promise<any> {
  try {
    const { email, password } = payload;
    const { user, token, refreshToken } = await this.usersService.login(email, password);

    return {
      user: {
        email: user.email,
        userId: user.id,
        name: user.name,
        phone: user.phone,
      },
      token,
      refreshToken,
    };
  } catch (error) {
    let statusCode = Status.INTERNAL;
    if (error instanceof UserNotFoundException) {
      statusCode = Status.NOT_FOUND;
    } else if (error instanceof InvalidCredentialsException) {
      statusCode = Status.UNAUTHENTICATED;
    }
    throw new RpcException({ code: statusCode, message: error.message });
  }
}

3. API网关转换异常为HTTP格式并抛出

网关控制器中解析GRPC异常,转换为Nest的HttpException抛出(让全局过滤器捕获):

import { Status } from '@grpc/grpc-js';
import { HttpException, HttpStatus } from '@nestjs/common';

@Post('login')
async login(@Body() loginData: Login): Promise<any> {
  try {
    const user = await firstValueFrom(this.userService.Login(loginData));
    return sendSuccess(user, '登录成功');
  } catch (error) {
    let statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
    let message = '服务器内部错误';
    
    if (error.code === Status.NOT_FOUND) {
      statusCode = HttpStatus.NOT_FOUND;
      message = error.details;
    } else if (error.code === Status.UNAUTHENTICATED) {
      statusCode = HttpStatus.UNAUTHORIZED;
      message = error.details;
    }
    
    // 抛出异常,交给全局过滤器处理
    throw new HttpException(message, statusCode);
  }
}

二、修复全局异常过滤器不生效问题

1. 调整过滤器适配异常解析逻辑

优化AllExceptionsFilter,确保正确解析HttpException的响应内容:

import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { Response } from 'express';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    
    let status: number;
    let message: string;
    
    if (exception instanceof HttpException) {
      status = exception.getStatus();
      // 兼容HttpException的字符串或对象格式响应
      const exceptionResponse = exception.getResponse();
      message = typeof exceptionResponse === 'string' ? exceptionResponse : (exceptionResponse as any).message;
    } else {
      status = HttpStatus.INTERNAL_SERVER_ERROR;
      message = exception.message || '服务器内部错误';
    }
    
    response.status(status).json(sendError(message, status));
  }
}

2. 确保过滤器正确注册且无局部覆盖

  • 网关main.ts中注册全局过滤器的代码保持不变,但需确保控制器/方法未使用@UseFilters()装饰器(局部过滤器会覆盖全局过滤器):
app.useGlobalFilters(new AllExceptionsFilter());
  • 核心前提:网关控制器必须抛出异常,而非直接return sendError,否则过滤器无法捕获异常。

三、其他关键修复总结

  1. 修复findByEmail方法缺少await的bug,否则永远无法触发“用户未找到”逻辑;
  2. 补充登录流程中密码不匹配的异常抛出逻辑,避免无响应或未知错误。

内容的提问来源于stack exchange,提问作者John Oliver

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 15:55:39