tRPC多个proto文件、optional、import等用法【2 使用进阶】
多个proto文件场景
- 项目代码(欢迎star⭐️) :
Github:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-trpc/02-complicated
有时我们为了结构清晰,会使用多个proto文件。比如一个classroom里会有多个学生。
- classroom.proto
- student.proto
classroom.proto里肯定是需要使用student.proto,因此我们需要使用
import关键字
导入,然后trpc create分别生成classroom.trpc.go、student.trpc.go文件。
1 编写proto文件
项目结构:
student.proto
syntax = "proto3";package trpc.complicated;
option go_package="ziyi.com/go-demo/go-trpc/02-complicated/pb";message Student {string name = 1;int32 age = 2;
}
classroom.proto
虽然goland import报红,这是因为我们没有配置goland识别proto文件的路径。但并不影响执行trpc create命令生成trpc.go文件。
或者我们也可以在goland配置proto文件路径:
syntax = "proto3";package trpc.complicated;
option go_package="ziyi.com/go-demo/go-trpc/02-complicated/pb";import "student.proto"; // 导入外部proto文件(因为两个proto文件都在同一个目录,因此这里可使用相对路径)//定义教室服务
service ClassroomService {rpc GetInfo (Request) returns (Response) {}
}message Request {int32 roomId = 1;
}message Response {Classroom classroom = 1;
}//定义教室struct
message Classroom {int32 id = 1;string name = 2;string address = 3;repeated Student students = 4;
}
2 trpc create生成trpc.go文件
# 生成classroom.pb.go、classroom.trpc.go文件
trpc create -p classroom.proto \--rpconly \--nogomod \--mock=false \--protodir . \-o .# 生成student.pb.go文件
trpc create -p student.proto \--rpconly \--nogomod \--mock=false \--protodir . \-o .# 解释:
# -p student.proto 指定要生成代码的 .proto 文件
# --rpconly 只生成 stub 代码
# --nogomod 不生成 go.mod 文件
# --mock=false 禁用 mock 代码生成
# --protodir . 指定 .proto 文件的搜索路径
# -o . 输出目录为当前目录
生成后项目结构:
如果发现报错: generate files from template inside create rpc stub err: template execute err: template: trpc.go.tpl:91:31: executing “trpc.go.tpl” at <gofulltypex .RequestType $.FileDescriptor>: error calling gofulltypex: invalid type:xxx。
- 观察是否是.proto文件未加package xxxx;
3 编写server端
trpc_go.yaml
配置服务名称、监听地址、端口
server:service:# 服务名称- name: trpc.classroom# 监听地址ip: 127.0.0.1# 服务监听端口port: 8088
server.go
对外暴露服务
package mainimport ("context""fmt""trpc.group/trpc-go/tnet/log""trpc.group/trpc-go/trpc-go""ziyi.com/02-complicated/pb"
)func main() {trpc.ServerConfigPath = "/Users/ziyi/GolandProjects/ziyifast-code_instruction/go-demo/go-trpc/02-complicated/server/trpc_go.yaml"server := trpc.NewServer()pb.RegisterClassroomServiceService(server, &ClassRoomService{})if err := server.Serve(); err != nil {panic(err)}
}type ClassRoomService struct {
}func (c *ClassRoomService) GetInfo(ctx context.Context, request *pb.Request) (*pb.Response, error) {log.Info("【server】receive ", request.RoomId, " info...")if request.RoomId != 1 {return nil, fmt.Errorf("the classroom does not exist")}rsp := new(pb.Response)room := &pb.Classroom{Name: "grade7_21",Address: "北京市 朝阳区 大屯路 ",Students: []*pb.Student{&pb.Student{Name: "小明",Age: 18,},},}rsp.Classroom = roomreturn rsp, nil
}
4 编写client端
client.go
请求服务端
package mainimport ("context""trpc.group/trpc-go/trpc-go/client""ziyi.com/02-complicated/pb"
)func main() {cli := pb.NewClassroomServiceClientProxy(client.WithTarget("ip://localhost:8088"))rsp, err := cli.GetInfo(context.TODO(), &pb.Request{RoomId: 1})if err != nil {panic(err)}println("【client】 receive ", rsp.Classroom.Name)
}
5 演示
启动服务端、客户端,客户端向服务端发起请求查询id为1的classroom信息。
其他字段使用场景
具体代码地址:
https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-trpc/03-keyword
message:定义结构体struct
// message: 定义结构体,类比go中的type
message Request {// optional: 可选字段optional string reqCreateTime = 1;string name = 2;//tips:字段后的1、2、1001等id标识,不用连续,只要全局唯一即可。通常为了后续有字段扩展我们字段的id都会跳着定义。map<string, string> extInfo = 1001;}
service:定义服务
// service : 定义服务
service KeywordService {rpc GetKeyword(Request) returns (Response);//...
}
optional:可选字段
如果字段为optional,且未设置值,在序列化时候则不会包含该字段,且不会给字段默认值
// message: 定义结构体,类比go中的type
message Request {// optional: 可选字段optional string reqCreateTime = 1;map<string, string> reqInfo = 2;
}
repeated:可重复字段=>列表(切片)
message Classroom{string name = 1;//repeated 列表(切片)repeated int32 studentIds = 2;
}
enum:定义枚举类
enum ResponseCode {OK = 0;FAIL = 1;INVALID_PARAM = 2;
}
validate:规则校验
定义校验validate.proto:
使用:
// 案例一:
// 账号信息
message Account {AccountType type = 1; string id = 2 [(validate.rules).string.kstr = true]; // 账号id
}//案例二:
message Req {Account account = 1; map<string, string> ext = 2; repeated string variable_ids = 3[(validate.rules).repeated = {min_items: 0, max_items: 100, items: {string: {kstr: true, min_len: 1, max_len: 100}}}];
}
extend:扩展字段
例如下面代码实现了 对google.protobuf.MessageOptions 的扩展,用于在消息级别应用验证规则。具体来说:
- disabled:如果设置为 true,则会禁用此消息的所有验证规则,包括其支持验证的所有字段的验证规则。
- ignored:如果设置为 true,则会跳过为此消息生成验证方法。
validate.proto:
// Validation rules applied at the message level
extend google.protobuf.MessageOptions {// Disabled nullifies any validation rules for this message, including any// message fields associated with it that do support validation.optional bool disabled = 1071;// Ignore skips generation of validation methods for this message.optional bool ignored = 1072;
}
oneof type:单选
oneof 关键字表示在 FieldRules 消息中只能选择其中一个字段。这意味着每个 FieldRules 实例只能定义一种类型的验证规则。例如,如果选择了 FloatRules,则其他字段(如 DoubleRules 或 StringRules)将不会生效。
// FieldRules encapsulates the rules for each type of field. Depending on the
// field, the correct set should be used to ensure proper validations.
message FieldRules {optional MessageRules message = 17;oneof type {// Scalar Field TypesFloatRules float = 1;DoubleRules double = 2;Int32Rules int32 = 3;Int64Rules int64 = 4;}
}
Validate使用场景:校验字段
tRPC 验证器(Validator)是一种用于在 tRPC 通信过程中进行数据验证的工具,通过在 .proto 文件中定义验证规则(例如长度限制、格式检查等),确保客户端和服务器之间传递的数据符合预期的格式和约束条件。
- 我们这里采用开源的protoc-gen-validate自定义校验工具来讲解。
安装protoc-gen-validate工具
# fetches this repo into $GOPATH
go get github.com/envoyproxy/protoc-gen-validate
下载之后如果发现依然无法执行protoc-gen-validate命令,解决办法:下载源码,手动编译。
- 下载源码:go install github.com/envoyproxy/protoc-gen-validate@latest
- 进入源码所在目录(如果配置了GOPATH,会直接下载到GOPATH/pkg/mod/github.com/…目录下):
- 我的GOPATH:/Users/ziyi/go
- 我下载的源码路径:/Users/ziyi/go/pkg/mod/github.com/envoyproxy/protoc-gen-validate@v1.1.0
- cd 进入源码目录,手动编译: sudo go build -o protoc-gen-validate main.go
- 编译后的二进制添加可执行权限:sudo chmod +x protoc-gen-validate
- 将二进制拷贝到/usr/bin下(或其他目录,只要该目录配置了环境变量,能在任何地方执行即可)
protoc-gen-validate可支持校验类型
官网地址:https://gitcode.com/gh_mirrors/pr/protoc-gen-validate/overview?utm_source=csdn_github_accelerator&isLogin=1
实战一:使用Validate默认规则
- 官方文档: trpc validate官方文档
- 全部代码:
Github:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-trpc/04-validate
项目结构:
.
├── client
│ └── client.go
├── proto
│ ├── user_trpc.pb.go
│ ├── user.pb.go
│ ├── user.pb.validate.go
│ ├── user.proto
│ └── validate.proto
└── server└── server.go
1 编写user.proto
syntax = "proto3";import "validate.proto";option go_package = ".;proto";service UserService {rpc GetUser(User) returns (User);
}message User {// uid 是用户的唯一标识符,它必须大于999uint64 uid = 1 [(validate.rules).uint64.gt = 999];// email 是用户的电子邮件地址,它必须是一个有效的电子邮件地址string email = 2 [(validate.rules).string.email = true];// phone 是用户的电话号码,它必须符合中国大陆的手机号码格式string phone = 3 [(validate.rules).string = {pattern: "^1[3456789]\\d{9}$"}];
}
此时import语句会爆红,显示Cannot resolve import ‘validate.proto’,所以我们需要建立validate.proto文件。【注意:如果使用trpc 命令+ --validate参数,可忽略此步骤,trpc会拉取自己所依赖的validate.proto,原理:trpc-cmdline 工具内置了一份该文件。】
解决办法:
- 在本地GO Modules内找到 github.com/envoyproxy/protoc-gen-validate@v.xx.xx这个库中的validate/validate.proto文件,利用cv大法复制到proto/validate.proto文件中。
- 从远程GitHub找到此项目该文件的代码:validate/validate.proto,利用cv大法复制到proto/validate.proto文件中。
2 执行命令生成对应go文件(x.validate.go)
trpc create -p user.proto \--rpconly \--nogomod \--mock=false \--protodir . \-o . \-- validate
3 server/server.go
package mainimport ("context""log""trpc.group/trpc-go/trpc-go""ziyi.com/04-validate/pb"
)func main() {//设置服务配置文件路径trpc.ServerConfigPath = "/Users/ziyi/GolandProjects/ziyifast-code_instruction/go-demo/go-trpc/04-validate/server/trpc_go.yaml"server := trpc.NewServer()pb.RegisterUserServiceService(server, &UserService{})if err := server.Serve(); err != nil {panic(err)}
}type UserService struct {
}func (s *UserService) GetUser(ctx context.Context, req *pb.User) (*pb.User, error) {if err := req.ValidateAll(); err != nil {return nil, err}log.Printf("pass.....")return req, nil
}
trpc_go.yaml:
server:service:# 服务名称- name: trpc.validate# 监听地址ip: 127.0.0.1# 服务监听端口port: 8088
4 client/client.go
package mainimport ("context""trpc.group/trpc-go/trpc-go/client""ziyi.com/04-validate/pb"
)func main() {cli := pb.NewUserServiceClientProxy(client.WithTarget("ip://localhost:8088"))req := new(pb.User)req.Email = "123@qq.com"req.Phone = "aaaa" //校验不通过//req.Phone = "18173827109" //校验通过req.Uid = 10001_, err := cli.GetUser(context.Background(), req)if err != nil {panic(err)}
}
5 验证
- client中填写不满足格式的phone,校验不通过:
实战二:自定义Validate规则
官方文档:https://go-kratos.dev/docs/component/middleware/validate/
代码地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-trpc/05-validate-self
步骤:
- 拷贝一份validate.proto到本地
- 修改validate.proto,添加或者修改规则
- 通过命令生成xxx.pb.validate.go之后,根据自己需求修改ValidateAll函数
案例:我现在需要定义一个新pstr选项,如果某个字段为String且为pstr,那么它的值只能在[“curry”, “tom”, "xujie]中三选一。
具体操作:
①本地添加规则
②user.proto中设置pstr为true
③执行命令生成trpc代码以及对应校验文件
④修改user.pb.validate.go中ValidateAll方法
⑤编写client、server端代码,验证效果
- 校验通过
- 校验失败
参考文章:https://blog.csdn.net/MPY_3/article/details/140420543https://github.com/bufbuild/protoc-gen-validatehttps://pkg.go.dev/trpc.group/trpc/trpc-protocol/pb/go/trpc/validatehttps://go-kratos.dev/docs/component/middleware/validate/https://github.com/trpc-group/trpc-cmdline/blob/main/docs/examples/example-2/README.zh_CN.md#%E7%94%9F%E6%88%90-validatepbgo-%E6%96%87%E4%BB%B6