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
相关产品推荐
相关产品推荐

