【gRPC】 proto快速入门-具体操作(import、枚举类型、map类型、嵌套message、rpc空参数、时间戳参数)

2,494 阅读7分钟

官网文档:

protobuf.dev/programming…

对于0基础的读者请先看这篇文章,带你快速熟悉proto在gRPC下的应用

proto在gRPC下的应用

本文是对上篇文章的补充,详细介绍了import其他proto文件、使用枚举类型、使用map类型、嵌套message、rpc空参数和时间戳参数的基本使用,方便读者对proto文件进一步了解,希望这篇博客对你有所帮助。

1.import proto

在 Protobuf 中,您可以使用 import 指令来导入其他 .proto 文件中定义的消息类型和服务。在一个 .proto 文件中导入另一个 .proto 文件后,您可以使用导入的消息类型和服务。

下面是一个简单的示例。假设您有两个 .proto 文件:person.protoaddress.protoperson.proto 文件中定义了 Person 消息类型,address.proto 文件中定义了 Address 消息类型。您可以使用 import 指令在 person.proto 文件中导入 address.proto 文件,以使用 Address 消息类型。

address.proto:

syntax = "proto3";

package mypackage;

message Address {
	string street = 1;
	string city = 2;
	string province = 3;
}

person.proto:

syntax = "proto3";

package mypackage;

import "address.proto";

message Person {
	string name = 1;
	int32 age = 2;
	Address address = 3;
}

在上面的示例中,我们在 person.proto 文件中使用了 import "address.proto"; 来导入 Address 消息类型。然后,我们在 Person 消息类型中使用 Address 类型作为一个字段。

当您使用 protoc 工具从这些 .proto 文件生成代码时,生成的代码将包含 PersonAddress 消息类型以及它们的相应的 proto 包名。

在 Golang 中,您可以使用以下代码导入 PersonAddress 消息类型:

package main

import (
  "fmt"
  "mypackage/person" // 导入 person.proto 生成的 Golang 代码包
)

func main() {
  p := &person.Person{
    Name: "John",
    Age:  30,
    Address: &person.Address{
      Street: "ChangJiangNanStreet",
      City:   "ShangHai",
      Province:  "ShangHai",
    },
  }
  fmt.Println(p)
}

在上面的示例中,我们导入了 mypackage/person 包,这是 person.proto 生成的 Golang 代码包。然后,我们使用 PersonAddress 消息类型来创建一个 Person 对象,并打印它。

2.嵌套message

在 Protobuf 中,可以使用嵌套的消息类型来表示消息的复杂结构。通过将一个消息类型嵌套在另一个消息类型中,可以创建更丰富的数据模型,以满足复杂的应用程序需求。下面是一个简单的例子:

syntax = "proto3";

package mypackage;

message Address {
	string street = 1;
	string city = 2;
	string state = 3;
}

message Person {
	string name = 1;
	int32 age = 2;
	Address address = 3;
}

在上面的示例中,我们定义了两个消息类型:AddressPersonAddress 消息表示一个邮寄地址,它包含一个街道、一个城市、一个州和一个邮政编码。Person 消息表示一个人,它包含一个名字、一个年龄和一个 Address 类型的地址。

要在消息中使用嵌套的消息类型,可以像在上面的示例中那样将一个消息类型作为另一个消息类型的字段。在上面的示例中,我们将 Address 类型作为 Person 消息的一个字段,这样每个人都有一个地址。

还可以使用下面这种方式。

message OuterMessage {
	// ...
	message InnerMessage {
		// ...
	}
}

例如:

syntax = "proto3";

package mypackage;

message Person {
	string name = 1;
	int32 age = 2;
	Address address = 3;

message Address {
	string street = 1;
	string city = 2;
	string state = 3;
	int32 zip = 4;
	}
}

在上面的示例中,我们将 Address 消息类型嵌套在 Person 消息类型中,以表示一个人的地址。您可以像访问其他字段一样访问嵌套消息类型的字段。在 Golang 中,如果有一个名为 personPerson 对象,可以使用 person.Address.City 访问该人的地址的城市字段。

3.enum类型

在 Protobuf 中,您可以使用 enum 指令定义一个枚举类型。枚举类型允许您定义一组预定义的值,每个值都有一个关联的名称和数值。在 Golang 生成的代码中,枚举类型将被表示为常量。

下面是一个简单的示例,展示了如何在 Protobuf 中使用 enum 指令:

syntax = "proto3";

package mypackage;

enum Gender {
	MALE = 0;
	FEMALE = 1;
	OTHER = 2;
}

message Person {
	string name = 1;
	int32 age = 2;
	Gender gender = 3;
}

在上面的示例中,我们定义了一个 Gender 枚举类型,它包含三个预定义的值:MALEFEMALEOTHER。然后,在 Person 消息类型中,我们使用了 Gender 类型作为一个字段。

当您使用 protoc 工具从这个 .proto 文件生成代码时,生成的 Golang 代码将包含 Gender 枚举类型和相应的常量。

在 Golang 中,您可以使用以下代码来访问 Gender 常量:

package main

import (
	"fmt"
	"mypackage"
)

func main() {
	fmt.Println(mypackage.Gender_MALE)    // 输出 0
	fmt.Println(mypackage.Gender_FEMALE)  // 输出 1
	fmt.Println(mypackage.Gender_OTHER)   // 输出 2
}

在上面的示例中,我们使用 mypackage 包中的 Gender 常量来访问枚举类型的值。如果您的枚举类型名称为 MyEnum,则生成的常量名称将为 MyEnum_XXX,其中 XXX 为每个枚举值的名称。

4.map类型

在 Protobuf 3.0 及以上版本中,您可以使用 map 类型来定义一个键值对结构。map 类型允许您定义一个映射,其中键和值都是 Protobuf 数据类型。

下面是一个简单的示例,展示了如何在 Protobuf 中使用 map 类型:

syntax = "proto3";

package mypackage;

message Person {
	string name = 1;
	int32 age = 2;
	map<string, string> phone_numbers = 3;
}

在上面的示例中,我们定义了一个 Person 消息类型,其中包含一个名为 phone_numbersmap 类型字段。在这个 map 中,键的类型为 string,值的类型也为 string

当您使用 protoc 工具从这个 .proto 文件生成代码时,生成的 Golang 代码将包含 Person 消息类型和相应的 map 类型字段。

在 Golang 中,您可以使用以下代码来访问 map 类型的值:

package main

import (
"fmt"
"mypackage"
)

func main() {
p := &mypackage.Person{
	Name: "John",
	Age:  30,
	PhoneNumbers: map[string]string{
		"home": "123-456-7890",
		"work": "456-789-0123",
	},
}
	fmt.Println(p)
}

在上面的示例中,我们使用 map 类型为 PhoneNumbers 字段指定了两个电话号码,一个为家庭电话,一个为工作电话。我们创建一个 Person 对象并打印它。

需要注意的是,当您使用 map 类型时,您需要初始化 map,并为它添加键值对。在上面的示例中,我们使用 map[string]string 类型初始化了 PhoneNumbers 字段。如果map 类型的键或值是一个自定义的 Protobuf 数据类型,则需要先初始化该类型,然后再将其作为值添加到 map 中。

5.rpc方法入参为空怎么设置?

在 Protobuf 中,如果您需要在 rpc 定义中使用一个空的请求消息,可以使用 google.protobuf.Empty 类型。google.protobuf.Empty 是一个消息类型,表示一个空的消息,它没有任何字段。

要在 rpc 中使用 google.protobuf.Empty 类型,可以在 .proto 文件中导入 google/protobuf/empty.proto 文件,并将其用作请求或响应类型。下面是一个简单的例子:

syntax = "proto3";

package mypackage;

import "google/protobuf/empty.proto";

service MyService {
  rpc MyMethod(google.protobuf.Empty) returns (google.protobuf.Empty);
}

在上面的示例中,我们定义了一个 MyService 服务,它包含一个 MyMethod rpcMyMethod 没有任何请求参数,返回一个空的响应消息。我们使用 google.protobuf.Empty 类型作为请求和响应类型。

在 Golang 中,您可以使用以下代码调用 MyMethod rpc,并将请求参数设置为空:

内置的proto所对应的go代码都可以在当前"github.com/golang/protobuf/ptypes"路径中找到。

package main

import (
    "context"
    "fmt"

    "github.com/golang/protobuf/ptypes/empty"
    "google.golang.org/grpc"
)

func main() {
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
      panic(err)
  }
    defer conn.Close()

    client := mypackage.NewMyServiceClient(conn)

    _, err = client.MyMethod(context.Background(), &empty.Empty{})
    if err != nil {
      panic(err)
  }

    fmt.Println("MyMethod called successfully")
}

在上面的示例中,我们使用 github.com/golang/protobuf/ptypes/empty 包中的 Empty 类型来创建一个空的请求消息。然后,我们使用 MyServiceClient 接口中的 MyMethod 方法调用 MyMethod rpc,并将请求参数设置为空。

5.内置的时间戳类型

在 Protobuf 中,可以使用标准的 google.protobuf.Timestamp 类型表示时间戳。Timestamp 是一个消息类型,用于存储从纪元开始的时间戳。它包含两个字段:secondsnanos,分别表示自纪元开始的秒数和纳秒数。

要在 Protobuf 中添加时间戳,可以按照以下步骤操作:

.proto 文件中定义 Timestamp 类型,可以像定义其他消息类型一样,例如:

syntax = "proto3";
package mypackage;

import "google/protobuf/timestamp.proto";

message MyMessage {
	google.protobuf.Timestamp created_at = 1;
}

在你的代码中创建一个 Timestamp 对象,并将其设置为消息中的时间戳字段。可以使用标准库中的 time 包获取当前时间,例如:

package main

import (
    "fmt"
    "time"

    "github.com/golang/protobuf/ptypes/timestamp"
)

func main() {
    createdAt := &timestamp.Timestamp{
      Seconds: time.Now().Unix(),
      Nanos:   int32(time.Now().Nanosecond()),
  }

    fmt.Println(createdAt)
}

在上面的示例中,createdAt 是一个 Timestamp 对象,表示当前时间。我们使用 time.Now().Unix() 获取自纪元开始的秒数,使用 time.Now().Nanosecond() 获取自纪元开始的纳秒数,并将它们分别设置为 Timestamp 对象的 SecondsNanos 字段。

需要注意的是,由于 Timestamp 类型是在 google.protobuf 包中定义的,因此在使用它之前需要导入该包。在 Golang 中,可以使用 github.com/golang/protobuf/ptypes/timestamp 包中的 Timestamp 类型和函数来处理 Protobuf 时间戳。