NestJS构建gRPC服务如何实现请求参数校验并返回BAD_REQUEST异常
NestJS gRPC服务请求参数校验实现方案
直接用Nest微服务模块原生适配的校验能力即可,不会丢失gRPC上下文的call对象,参数非法时可正常返回对应错误码。
具体实现步骤
定义带校验规则的请求DTO
对应proto定义的请求结构编写DTO类,用class-validator的装饰器标记校验规则,示例:import { IsNotEmpty, IsEnum } from 'class-validator'; // 对应proto里定义的请求枚举 enum BizType { ADD = 0, EDIT = 1, } export class MethodRequestDto { @IsNotEmpty({ message: '资源ID不能为空' }) resourceId: string; @IsEnum(BizType, { message: '操作类型取值非法' }) bizType: BizType; }正确绑定gRPC方法参数
注意要从@nestjs/microservices导入gRPC专属的@Payload装饰器绑定请求体,不要用HTTP场景的@Body装饰器,metadata、call等上下文参数可以正常注入,不会出现undefined问题:import { Controller } from '@nestjs/common'; import { GrpcMethod, Payload } from '@nestjs/microservices'; import { Metadata, ServerWritableStream, status } from '@grpc/grpc-js'; import { Observable } from 'rxjs'; import { MethodRequestDto } from './dto/method-request.dto'; import { MethodResponse } from './interfaces/service.interface'; @Controller() export class BizController { @GrpcMethod('BizService', 'OperateResource') operateResource( @Payload() request: MethodRequestDto, metadata: Metadata, call: ServerWritableStream<MethodRequestDto, Observable<MethodResponse>> ) { // 正常业务逻辑,走到这里的request已经完成校验 return { success: true }; } }注册适配gRPC场景的校验管道
全局注册Nest内置的ValidationPipe,自定义异常工厂把校验错误转成gRPC标准的参数错误状态,gRPC协议中INVALID_ARGUMENT状态等价于HTTP场景的BAD_REQUEST,不要用默认的HTTP异常返回:import { NestFactory } from '@nestjs/core'; import { MicroserviceOptions, Transport, RpcException } from '@nestjs/microservices'; import { ValidationPipe } from '@nestjs/common'; import { status } from '@grpc/grpc-js'; import { join } from 'path'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, { transport: Transport.GRPC, options: { // 原有gRPC配置:proto路径、服务地址、包名等 package: 'biz', protoPath: join(__dirname, './proto/biz.proto'), url: '0.0.0.0:50051', }, }); app.useGlobalPipes( new ValidationPipe({ transform: true, // 自动把请求plain object转换为DTO类实例 whitelist: true, // 自动剔除DTO中未声明的冗余字段 forbidNonWhitelisted: true, // 出现冗余字段时直接抛错,按需开启 exceptionFactory: (validationErrors) => { const errorMsg = validationErrors .map(err => `${err.property}: ${Object.values(err.constraints || {}).join('、')}`) .join('; '); return new RpcException({ code: status.INVALID_ARGUMENT, message: `请求参数校验失败: ${errorMsg}`, }); }, }) ); await app.listen(); } bootstrap();
之前方案失效原因说明
- 用
@Body/错误来源的@Payload装饰器失效:是因为误用了HTTP模块专属的参数装饰器,Nest会按照HTTP请求的规则解析上下文,自然拿不到gRPC专属的call对象,只要换成@nestjs/microservices导出的@Payload就不会有这个问题 - class-validator的无装饰器Schema方案本身长期无人维护,存在已知bug,生产环境不要用,官方推荐的装饰器+管道方案是生态最成熟、稳定性最高的实现。
可选优化
如果用ts-proto等工具自动生成proto对应的TS类型,可以直接开启工具的class-validator装饰器自动生成配置,不需要手写DTO类,维护成本更低。
内容的提问来源于stack exchange,提问作者Przemek Wit
相关产品推荐
相关产品推荐

