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

NestJS gRPC微服务返回不可读响应问题求助

NestJS gRPC微服务返回不可读响应问题

Postman中收到的登录接口响应里,user字段是带有特殊格式的字符串,无法正常解析,响应示例如下:

{    
    "message": "Logged In Successfully.",    
    "data": {        
        "user": "{\n  _id: new ObjectId(\"64770e2de47429bedcb24a85\"),\n  email: 'a@a.com',\n  password: '$2a$10$Grnc1u7/g4r1rnZofWrj.e6EmqMQQrDnqBrs/pNgOYF9cmQ4p9k8q',\n  name: 'John Doe',\n  phone: '1234567890',\n  type: 'ADMIN',\n  createdAt: 2023-05-31T09:06:53.671Z,\n  updatedAt: 2023-05-31T09:06:53.671Z,\n  __v: 0\n}",        
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0NzcwZTJkZTQ3NDI5YmVkY2IyNGE4NSIsInVzZXJUeXBlIjoiQURNSU4iLCJpYXQiOjE2ODU3NzM3NzUsImV4cCI6MTY4NTc3NDY3NX0.sl99Rg77fKRRZEnJ3eWQ2X6jR7svaJWdXsciUSh80zc"    
    }
}

相关代码

1. user.service.proto

syntax = "proto3";

package user;

service UserService {
  rpc Login (LoginRequest) returns (LoginResponse) {}
}

message LoginRequest {
  string email = 1;
  string password = 2;
}

message LoginResponse {
  string message = 1;
  map<string, string> data = 2;
}

2. 用户微服务控制器方法

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

  return sendSuccess(SUCCESS.S200.LOGIN, user);
}

3. 用户微服务login服务函数

async login(email: string, password: string): Promise<any> {
  const user = (await this.findByEmail(email)) as User;

  const comparepassword = await passwordService.comparePasswords(
    password,
    user.password,
  );
  if (user && comparepassword) {
    const payload = {
      id: user._id,
      userType: user.type,
    };
    const token = jwtService.generateAccessToken(payload);
    return { user, token };
  }

  return 'User not found';
}

4. API网关控制器方法

@Post('login')
@HttpCode(200)
async login(
  @Body() loginData: Login,
): Promise<any> {
  try {
    const user = await firstValueFrom(this.userService.Login(loginData));

    return user;
  } catch (error) {
    throw new Error('Login failed');
  }
}

解决方案

1. 修改Proto文件定义

问题核心在于LoginResponse中的data字段被定义为map<string, string>,但实际返回的data包含user对象和token字符串,gRPC无法将对象序列化为string类型,只能转成类似控制台输出的字符串格式。需要重新定义Proto结构,支持复杂数据类型:

syntax = "proto3";

package user;

service UserService {
  rpc Login (LoginRequest) returns (LoginResponse) {}
}

message LoginRequest {
  string email = 1;
  string password = 2;
}

// 定义User消息结构
message User {
  string _id = 1;
  string email = 2;
  string password = 3;
  string name = 4;
  string phone = 5;
  string type = 6;
  string createdAt = 7;
  string updatedAt = 8;
  int32 __v = 9;
}

// 定义LoginResponse的data结构
message LoginResponseData {
  User user = 1;
  string token = 2;
}

message LoginResponse {
  string message = 1;
  LoginResponseData data = 2;
}

2. 调整微服务返回逻辑

确保服务返回的结构和Proto定义匹配,将MongoDB的ObjectId和日期类型转成字符串:

async login(email: string, password: string): Promise<any> {
  const user = (await this.findByEmail(email)) as User;

  const comparepassword = await passwordService.comparePasswords(
    password,
    user.password,
  );
  if (user && comparepassword) {
    const payload = {
      id: user._id.toString(),
      userType: user.type,
    };
    const token = jwtService.generateAccessToken(payload);
    // 将MongoDB对象转为可序列化的普通对象
    const serializedUser = {
      ...user.toObject(), // Mongoose模型用toObject()转普通对象
      _id: user._id.toString(),
      createdAt: user.createdAt.toISOString(),
      updatedAt: user.updatedAt.toISOString(),
    };
    return { user: serializedUser, token };
  }

  throw new Error('User not found'); // 抛出错误便于gRPC处理异常
}

3. 更新控制器返回类型

调整微服务控制器的返回类型,匹配新的Proto定义:

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

  return {
    message: SUCCESS.S200.LOGIN,
    data: { user, token },
  };
}

4. 网关层适配

网关层调用gRPC服务时会自动得到结构化数据,直接返回即可,无需额外解析。

无效方法说明

之前尝试的JSON解析、util.inspect处理都是拿到错误格式后的补救手段,根源是gRPC序列化时因类型不匹配将对象转成了字符串,必须从Proto定义和数据序列化环节解决问题。

内容的提问来源于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.20 05:44:56