gRPC 中的空值问题
Protocol Buffers 3(proto3)没有原生的空值概念。每个字段都有默认值(字符串为空串,数字为 0,布尔为 false)。这导致在 gRPC 服务中无法直接返回 null 或 undefined。
错误示范
在 NestJS gRPC Controller 中直接返回 null 或 undefined:
@GrpcMethod('UserService', 'findOne')
async findOne(payload: { username: string }) {
const user = await this.prisma.user.findUnique({
where: { username: payload.username }
})
if (!user) {
return null // 报错:序列化失败
return undefined // 报错:序列化失败
}
return user
}
typescript
这种写法会导致 internal 错误,因为 gRPC 无法序列化空值。
正确做法:返回空对象 + 错误码
在 proto 的 Response 消息中添加 code 和 message 字段,用于标识业务状态:
message FindOneResponse {
int32 id = 1;
string username = 2;
string password = 3;
int32 code = 4; // 错误码,0 表示成功
string message = 5; // 错误描述
}
protobuf
Controller 中的处理:
@GrpcMethod('UserService', 'findOne')
async findOne(payload: { username: string }) {
const user = await this.prisma.user.findUnique({
where: { username: payload.username }
})
if (!user) {
return {
code: 10001,
message: '用户不存在'
}
}
return {
id: user.id,
username: user.username,
password: user.password,
code: 0,
message: 'success'
}
}
typescript
调用方根据 code 字段判断请求是否成功,再决定如何处理响应数据。
Proto 文件跨文件引用
在实际项目中,通常会将公共的 message 定义抽取到独立的 proto 文件中,然后在其他 proto 文件中引用。
创建公共 proto 文件
// proto/user-base.proto
syntax = "proto3";
package user;
message UserProto {
int32 id = 1;
string username = 2;
string password = 3;
string email = 4;
}
protobuf
在其他 proto 中引用
// proto/user.proto
syntax = "proto3";
package user;
import "user-base.proto";
message FindOneResponse {
user.UserProto data = 1;
int32 code = 2;
string message = 3;
}
protobuf
使用 包名.消息名 的格式引用其他文件中的 message,如 user.UserProto。
VS Code 插件 Bug
问题
vscode-proto3 插件在处理 import 语句时存在 Bug,可能导致引用路径报错。
解决方案
卸载 vscode-proto3,改用以下插件组合:
- Protobuf Support (
vscode-proto) - Protobuf Helper
安装后,import 语句可以正常解析,不再报错。
grpcurl 测试跨文件引用
当 proto 文件使用了跨文件引用时,使用 grpcurl 测试需要指定 import 路径:
grpcurl -plaintext \
-import-path ./proto \
-proto user.proto \
-d '{"username": "admin"}' \
localhost:5000 UserService/findOne
bash
-import-path 参数告诉 grpcurl 去哪里查找被引用的 proto 文件。如果不指定,会报找不到 user-base.proto 的错误。
↑