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

NestJS微服务抛403 HttpException网关返回500错误问题排查

问题现象

在NestJS微服务场景中,通过@nestjs/common导入的HttpException抛出403 Forbidden错误时,网关始终将该错误识别为500 Internal Server Error返回。
错误对象序列化结果:

{"response":"User already exists","status":403,"message":"User already exists","name":"HttpException"}

网关侧错误日志:

[Nest] 4747  - 11/06/2022, 18:01:58   ERROR [ExceptionsHandler] Internal server error
[Nest] 4747  - 11/06/2022, 18:01:58   ERROR [ExceptionsHandler] undefined

涉及的服务层代码:

import { Model } from 'mongoose';
import {
  Injectable,
  Inject,
  ForbiddenException,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';

import { User, UserDocument } from './schemas/user.schema';

@Injectable()
export class AccountManagementService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}
  async createUser(user: any): Promise<any> {
    const existingUser = await this.userModel.find({
      $or: [{ username: user.username }, { email: user.email }],
    });
    if (existingUser) {
      throw new HttpException('User already exists', HttpStatus.FORBIDDEN);
    }
    const createdUser = new this.userModel(user);
    return createdUser.save();
  }
}

涉及的微服务控制器代码:

import { Controller, Post } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { AccountManagementService } from './account-management.service';

@Controller()
export class AccountManagementController {
  constructor(
    private accountManagementService: AccountManagementService,
  ) {}

  @MessagePattern({ cmd: 'createUser' })
  async createUser(userData: any): Promise<any> {
    return this.accountManagementService
      .createUser(userData)
  }
}
故障原因
  1. 核心原因:NestJS的HTTP层和微服务传输层的异常处理逻辑完全独立。HttpException是HTTP场景专用的异常类,在@MessagePattern修饰的微服务方法中直接抛出时,跨进程传输会丢失异常原型链,网关侧拿到的只是一个普通序列化对象,无法识别为预期的业务异常,就会默认当作未知内部错误,返回500状态码。
  2. 附带逻辑bug:Mongoose的find()方法返回匹配结果的数组,即使没有匹配到数据也会返回空数组,空数组在JS布尔判断中为真值,现有代码的if (existingUser)判断会永远成立,无论用户是否存在都会触发抛错。
修复方案
  • 微服务端替换异常类:使用微服务专用的RpcException代替HttpException抛出业务错误,同时修复用户存在性判断逻辑:
// 补充导入RpcException
import { RpcException } from '@nestjs/microservices';

// 修正查询和抛错逻辑
async createUser(user: any): Promise<any> {
  // 改用findOne查询单条记录,性能更好,返回值为null时表示用户不存在
  const existingUser = await this.userModel.findOne({
    $or: [{ username: user.username }, { email: user.email }],
  });
  if (existingUser) {
    throw new RpcException({
      message: 'User already exists',
      status: HttpStatus.FORBIDDEN
    });
  }
  const createdUser = new this.userModel(user);
  return createdUser.save();
}
  • 网关层添加异常转换过滤器:捕获微服务返回的RpcException,转换为对应状态码的HTTP异常返回给客户端:
import { Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { BaseRpcExceptionFilter, RpcException } from '@nestjs/microservices';
import { Observable, throwError } from 'rxjs';

@Catch(RpcException)
export class RpcToHttpExceptionFilter extends BaseRpcExceptionFilter {
  catch(exception: RpcException, host: ArgumentsHost) {
    const errorContent = exception.getError();
    const statusCode = typeof errorContent === 'object' && 'status' in errorContent 
      ? errorContent.status 
      : HttpStatus.INTERNAL_SERVER_ERROR;
    return throwError(() => new HttpException(errorContent, statusCode));
  }
}

在网关调用微服务的接口上绑定该过滤器即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 16:42:16