NestJS gRPC网关POST/PATCH请求无限发送问题求助
问题描述
开发应用时突然发现订单无法加载、提交,Postman中POST和PATCH请求一直处于「Sending Request」无限循环,无法完成,但GET和DELETE请求正常,此前功能运行正常。
服务层代码
/* eslint-disable prettier/prettier */ import { Comprador, Compradores, CreateOneCompraDTO, PaginacaoDto, UpdateCompraDTO } from '@app/lib'; import { Injectable, NotFoundException, OnModuleInit } from '@nestjs/common'; //import {Compra, Compradores,CreateCompraDto,PaginacaoDto,UpdateCompraDto} from '@app/common' import { randomUUID } from 'crypto'; import { Observable, Subject } from 'rxjs'; @Injectable() export class DadosService implements OnModuleInit { private readonly compradores: Comprador[] = []; onModuleInit() { } create(CreateCompraDto: CreateOneCompraDTO) { const comprador: Comprador = { ...CreateCompraDto, dadosCompra: {}, id: randomUUID(), } this.compradores.push(comprador) return comprador } findAll(): Compradores { return {compradores: this.compradores} } findOne(id: string): any{ return this.compradores.find((comprador) => comprador.id === id) } updateOne(id: string, dados: UpdateCompraDTO) { const compradorId = this.compradores.findIndex((comp) => comp.id === id) if(compradorId !== -1){ this.compradores[compradorId] = { ...this.compradores[compradorId], ...dados } return this.compradores[compradorId] } throw new NotFoundException(`Comprador do id: ${id}`) } remove(id: string){ const deleteId = this.compradores.findIndex((del) => del.id === id) if(deleteId !== -1){ return this.compradores.splice(deleteId, 1)[0] } throw new NotFoundException(`Comprador do id: ${id}`) } queryCompradores(paginacaoDTO: Observable<PaginacaoDto>): Observable<Compradores> { const subject = new Subject<Compradores>() const onNext = (paginacaoDTO: PaginacaoDto) => { const start = paginacaoDTO.page * paginacaoDTO.skip subject.next({ compradores: this.compradores.splice(start, start + paginacaoDTO.skip), }) }; const onComplete = () => subject.complete() paginacaoDTO.subscribe({ next: onNext, complete: onComplete }) return subject.asObservable(); } }
控制器代码
/* eslint-disable @typescript-eslint/no-unused-vars *//* eslint-disable prettier/prettier */ //import { Controller } from '@nestjs/common'; import { Controller } from '@nestjs/common' import { DadosService } from './dados.service'; import { Observable } from 'rxjs'; import { GrpcMethod } from '@nestjs/microservices'; import { Comprador, Compradores, CompradosServiceController, CreateOneCompraDTO, FindOneCompraDTO, PaginacaoDto, UpdateCompraDTO } from '@app/lib'; @Controller() export class DadosController implements CompradosServiceController { constructor(private readonly dadosService: DadosService) {} @GrpcMethod('CompradosService', 'queryComprador') queryComprador(request: Observable<PaginacaoDto>): Observable<Compradores> { return this.dadosService.queryCompradores(request) } @GrpcMethod('CompradosService', 'createOneCompra') createOneCompra(CreateCompraDto: CreateOneCompraDTO): Comprador{ return this.dadosService.create(CreateCompraDto) } @GrpcMethod('CompradosService', 'findAllCompra') findAllCompra() { return this.dadosService.findAll() } @GrpcMethod('CompradosService', 'findOneCompra') findOneCompra(request: FindOneCompraDTO) { return this.dadosService.findOne(request.id) } @GrpcMethod('CompradosService', 'updateOneCompra') updateOneCompra(request: UpdateCompraDTO) { return this.dadosService.updateOne(request.id, request) } @GrpcMethod('CompradosService', 'deleteOneCompra') deleteOneCompra(FindOneCompraDTO: FindOneCompraDTO) { return this.dadosService.remove(FindOneCompraDTO.id) } }
API网关控制器代码
/* eslint-disable prettier/prettier */ import { CreateOneCompraDTO, UpdateCompraDTO } from '@app/lib'; import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'; import { CompradoresService } from 'apps/compra/src/compradores/compradores.service'; import { HashearSenha } from '../recursos/pipeHashearSenha'; @Controller('/compradores') export class CompradoresController { constructor(private readonly compradoresService: CompradoresService) {} @Post() // eslint-disable-next-line @typescript-eslint/no-unused-vars criarCompradores(@Body() {senha, ...dados}: CreateOneCompraDTO, @Body('senha', HashearSenha) senhaHash: string){ return this.compradoresService.criarComprador({senha: senhaHash, ...dados}) } @Get() listarOsCOmpradores (){ return this.compradoresService.listarCompradores() } @Get('/:id') listarOComprador(@Param('id') id: string){ return this.compradoresService.listarComprador(id) } @Patch('/:id') // eslint-disable-next-line @typescript-eslint/no-unused-vars atualizarOComprador(@Param('id') id: string, @Body() {senha, ...dados}: UpdateCompraDTO, @Body('senha', HashearSenha) senhaHash: string){ return this.compradoresService.atualizarComprador(id, {senha: senhaHash, ...dados}) } @Delete('/:id') deletarOComprador(@Param('id') id: string){ return this.compradoresService.deletarComprador(id) } @Post('compradores') compradoresStream(){ return this.compradoresService.Compradores() } }
API网关服务层代码
/* eslint-disable prettier/prettier */ //import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; import { ReplaySubject } from 'rxjs'; import { AUTH_SERVICE } from './constantes'; import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { COMPRADOS_SERVICE_NAME, CompradosServiceClient, CreateOneCompraDTO, PaginacaoDto, UpdateCompraDTO } from '@app/lib'; @Injectable() export class CompradoresService implements OnModuleInit { private compradorService: CompradosServiceClient constructor( @Inject(AUTH_SERVICE) private cliente: ClientGrpc ){} onModuleInit() { this.compradorService = this.cliente.getService<CompradosServiceClient>(COMPRADOS_SERVICE_NAME) } criarComprador(dados: CreateOneCompraDTO){ return this.compradorService.createOneCompra(dados) } listarCompradores(){ return this.compradorService.findAllCompra({}) } listarComprador(id: string){ return this.compradorService.findOneCompra({id}) } atualizarComprador(id: string, dados: UpdateCompraDTO){ return this.compradorService.updateOneCompra({id, ...dados}) } deletarComprador(id: string){ return this.compradorService.deleteOneCompra({id}) } Compradores(){ const Compradores$ = new ReplaySubject<PaginacaoDto>() Compradores$.next({ page: 0, skip: 200}) Compradores$.next({ page: 1, skip: 200}) Compradores$.next({ page: 2, skip: 200}) Compradores$.next({ page: 3, skip: 200}) Compradores$.complete(); let parcialNumber = 1; this.compradorService.queryComprador(Compradores$).subscribe((empresas) => { console.log('Chunk', parcialNumber, empresas) parcialNumber += 1 }) } }
问题定位与解决方案
核心问题
queryCompradores方法破坏数据源:服务层中使用this.compradores.splice(start, start + paginacaoDTO.skip),splice是修改原数组的方法,会直接删除数组元素。当该方法被触发后,原compradores数组被清空,导致后续POST/PATCH请求无法正常处理数据。- 网关
Compradores方法无返回值:该方法内部订阅了GRPC流但未返回任何响应,导致对应的POST请求挂起,引发无限循环。
修复步骤
替换
splice为slice,保护原数组
修改服务层queryCompradores方法中的数据截取逻辑:const onNext = (paginacaoDTO: PaginacaoDto) => { const start = paginacaoDTO.page * paginacaoDTO.skip; // slice返回新数组,不修改原数据源 subject.next({ compradores: this.compradores.slice(start, start + paginacaoDTO.skip), }); };修复网关
Compradores方法的返回逻辑
修改方法使其返回可观察对象,让请求能正常结束:Compradores(): Observable<Compradores> { const Compradores$ = new ReplaySubject<PaginacaoDto>(); Compradores$.next({ page: 0, skip: 200 }); Compradores$.next({ page: 1, skip: 200 }); Compradores$.next({ page: 2, skip: 200 }); Compradores$.next({ page: 3, skip: 200 }); Compradores$.complete(); return this.compradorService.queryComprador(Compradores$); }添加错误处理
在GRPC调用中添加错误捕获,避免未处理的异常导致请求阻塞:import { catchError } from 'rxjs'; import { BadRequestException } from '@nestjs/common'; criarComprador(dados: CreateOneCompraDTO){ return this.compradorService.createOneCompra(dados).pipe( catchError(err => { console.error('创建失败:', err); throw new BadRequestException(err.message); }) ); }
内容的提问来源于stack exchange,提问作者user22358808
相关产品推荐
相关产品推荐

