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 main
import (
"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 = room
return rsp, nil
}
4 编写client端
client.go
请求服务端
package main
import (
"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 Types
FloatRules 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/[email protected]
- 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 是用户的唯一标识符,它必须大于999
uint64 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/[email protected]这个库中的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 main
import (
"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 main
import (
"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 = "[email protected]"
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