Go大师课程系列将学习
基础
准备
让我们开始创建一个新项目。首先,我将在main.go文件中创建一个简单的 hello-world 程序并运行它,只是为了确保 Go 正常运行。
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}
然后创建一个名为 的新文件夹protoc,并processor_message.proto在其下添加一个文件。
pcbook
├── proto
│ └── processor_message.proto
└── main.go
如何定义 protobuf 消息
现在回到我们的 proto 文件。此文件将包含笔记本电脑 CPU 的消息定义。
我们从 开始syntax = "proto3"。
目前,Google 官方文档中提供了 2 个版本的 protocol buffer :proto2和proto3。为简单起见,我们在本课程中仅使用proto3(较新的版本)。
语法非常简单,只需使用message关键字后跟消息名称即可。然后在消息块中,我们定义其所有字段,如下图所示:
注意消息名称应为UpperCamelCase,字段名称应为lower_snake_case。
我们可以使用许多内置的标量值数据类型,例如:string、bool、byte、float、double和许多其他整数类型。我们还可以使用自己的数据类型,例如枚举或其他消息。
每个消息字段都应分配一个唯一的标签。标签比字段名称更重要,因为 protobuf 将使用它来序列化消息。
标签只是一个任意整数,最小值为 1,最大值为 2 29 - 1,但 19000 至 19999 之间的数字除外,因为它们是为内部协议缓冲区实现保留的。
请注意,标签 1 到 15 仅占用 1 个字节进行编码,而标签 16 到 2047 占用 2 个字节。因此,您应该明智地使用它们,例如:将标签 1 到 15 保存在出现频率非常高的字段中。
请记住,标签不需要按顺序(或连续)排列,但对于消息的同一级别字段,它们必须是唯一的。
定义 CPU 消息
现在让我们回到我们的 proto 文件并定义 CPU 消息。
syntax = "proto3";
message CPU {
string brand = 1;
string name = 2;
uint32 number_cores = 3;
uint32 number_threads = 4;
double min_ghz = 5;
double max_ghz = 6;
}
CPU 将具有类型的品牌string,例如“Intel”,并且名称也是类型的string,例如“Core i7-9850”。
我们需要跟踪 CPU 有多少个核心或线程。它们不能为负数,所以我们uint32在这里使用。
接下来,它有最小和最大频率,例如 2.4 Ghz 或类似的值。因此我们可以double在这里使用类型。
生成 Go 代码
现在我们已经完成了第一个 protobuf 消息。我们如何从中生成 Go 代码?
首先,我们需要安装 protocol buffer 编译器(或者protoc)。在 macOS 上,我们可以借助Homebrew轻松完成此操作。
您可以使用以下简单命令安装 Homebrew:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安装 Homebrew 后,可以运行此命令来安装protoc:
brew install protobuf
我们可以通过运行命令来检查它是否正常工作protoc。
接下来我们会到grpc.io去复制并运行2个命令来安装2个库:golang grpc库和protoc-gen-go库。
go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go
现在一切就绪!我将创建一个名为的新文件夹pb来存储生成的 Go 代码。
pcbook
├── proto
│ └── processor_message.proto
├── pb
└── main.go
然后运行此命令来生成代码:
protoc --proto_path=proto proto/*.proto --go_out=plugins=grpc:pb
我们的 proto 文件位于proto文件夹内,所以我们告诉protoc在该文件夹中查找它。
通过go_out参数,我们告诉protoc使用grpc插件来生成Go代码,并将它们存储在pb我们之前创建的文件夹中。
现在如果我们在 vscode 中打开该文件夹,我们将看到一个新文件processor_message.pb.go。
pcbook
├── proto
│ └── processor_message.proto
├── pb
│ └── processor_message.pb.go
└── main.go
查看内部,有一个 CPU 结构和所有字段,其数据类型正确,正如我们在协议缓冲区文件中定义的一样。
const _ = proto.ProtoPackageIsVersion3
type CPU struct {
Brand string `protobuf:"bytes,1,opt,name=brand,proto3" json:"brand,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
NumberCores uint32 `protobuf:"varint,3,opt,name=number_cores,json=numberCores,proto3" json:"number_cores,omitempty"`
NumberThreads uint32 `protobuf:"varint,4,opt,name=number_threads,json=numberThreads,proto3" json:"number_threads,omitempty"`
MinGhz float64 `protobuf:"fixed64,5,opt,name=min_ghz,json=minGhz,proto3" json:"min_ghz,omitempty"`
MaxGhz float64 `protobuf:"fixed64,6,opt,name=max_ghz,json=maxGhz,proto3" json:"max_ghz,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CPU) Reset() { *m = CPU{} }
func (m *CPU) String() string { return proto.CompactTextString(m) }
func (*CPU) ProtoMessage() {}
func (*CPU) Descriptor() ([]byte, []int) {
return fileDescriptor_466578cecc6db379, []int{0}
}
func (m *CPU) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CPU.Unmarshal(m, b)
}
func (m *CPU) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CPU.Marshal(b, m, deterministic)
}
func (m *CPU) XXX_Merge(src proto.Message) {
xxx_messageInfo_CPU.Merge(m, src)
}
func (m *CPU) XXX_Size() int {
return xxx_messageInfo_CPU.Size(m)
}
func (m *CPU) XXX_DiscardUnknown() {
xxx_messageInfo_CPU.DiscardUnknown(m)
}
var xxx_messageInfo_CPU proto.InternalMessageInfo
func (m *CPU) GetBrand() string {
if m != nil {
return m.Brand
}
return ""
}
func (m *CPU) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *CPU) GetNumberCores() uint32 {
if m != nil {
return m.NumberCores
}
return 0
}
func (m *CPU) GetNumberThreads() uint32 {
if m != nil {
return m.NumberThreads
}
return 0
}
func (m *CPU) GetMinGhz() float64 {
if m != nil {
return m.MinGhz
}
return 0
}
func (m *CPU) GetMaxGhz() float64 {
if m != nil {
return m.MaxGhz
}
return 0
}
gRPC 内部使用了一些特殊字段来序列化消息,但我们不需要关心它们。还生成了一些有用的 getter 函数。
编写 Makefile
我们用来生成代码的命令相当长,因此当我们更新 proto 文件并想要重新生成代码时,输入起来不太方便。因此,让我们创建一个 Makefile,其中包含一个简短的命令来执行此操作。
pcbook
├── proto
│ └── processor_message.proto
├── pb
│ └── processor_message.pb.go
├── main.go
└── Makefile
在这个 Makefile 中,我们添加了一个gen任务来运行代码生成命令,一个clean任务来随时删除所有生成的 go 文件,以及一个run任务来运行该main.go文件。
gen:
protoc --proto_path=proto proto/*.proto --go_out=plugins=grpc:pb
clean:
rm pb/*.go
run:
go run main.go
深入
- 在protobuf消息字段中定义和使用自定义类型,例如枚举或其他消息。
- 讨论何时使用嵌套类型以及何时不使用。
- 将protobuf消息组织成多个文件,放入一个包中,然后将其导入到其他地方。
- 探索一些 Google 已经定义的知名类型。
- 了解重复字段、其中之一字段。
- 使用选项告诉 protoc 使用我们想要的包名生成 Go 代码。
一个文件中有多条消息
让我们从processor_message.proto文件开始。我们可以在 1 个文件中定义多个消息,因此我将在此处添加一个 GPU 消息。这是有道理的,因为 GPU 也是一个处理器。
syntax = "proto3";
message CPU {
string brand = 1;
string name = 2;
uint32 number_cores = 3;
uint32 number_threads = 4;
double min_ghz = 5;
double max_ghz = 6;
}
message GPU {
string brand = 1;
string name = 2;
double min_ghz = 3;
double max_ghz = 4;
// memory ?
}
它具有与 CPU 类似的一些字段,例如品牌、名称、最小和最大频率。唯一不同的是它有自己的内存。
自定义类型:消息和枚举
内存是一个非常流行的术语,可以在其他地方使用,例如 RAM 或存储(持久驱动器)。它有许多不同的测量单位,例如千字节、兆字节、千兆字节或太字节。所以我将在一个单独的memory_message.proto文件中将其定义为自定义类型,以便我们以后可以重复使用它。
pcbook
├── proto
│ ├── processor_message.proto
│ └── memory_message.proto
├── pb
│ └── processor_message.pb.go
├── main.go
└── Makefile
首先,我们需要定义测量单位。为此,我们将使用枚举。由于此单位应仅存在于内存上下文中,因此我们应该将其定义为内存消息中的嵌套类型。
syntax = "proto3";
message Memory {
enum Unit {
UNKNOWN = 0;
BIT = 1;
BYTE = 2;
KILOBYTE = 3;
MEGABYTE = 4;
GIGABYTE = 5;
TERABYTE = 6;
}
uint64 value = 1;
Unit unit = 2;
}
惯例是,始终使用特殊值作为枚举的默认值,并为其分配标签 0。然后我们添加其他单位,从 BIT 到 TERABYTE。
内存消息将有2个字段:一个用于值,另一个用于单位。
导入 proto 文件
现在让我们回到processor_message.proto文件。我们必须导入文件memory_message.proto才能使用 Memory 类型。在 GPU 消息中,我们添加了一个 Memory 类型的新内存字段。
syntax = "proto3";
import "memory_message.proto";
message CPU {
string brand = 1;
string name = 2;
uint32 number_cores = 3;
uint32 number_threads = 4;
double min_ghz = 5;
double max_ghz = 6;
}
message GPU {
string brand = 1;
string name = 2;
double min_ghz = 3;
double max_ghz = 4;
Memory memory = 5;
}
现在如果我们尝试生成 Go 代码,会出现错误“包名称不一致”
因为我们没有在proto文件中指定包名,所以protoc默认会使用文件名作为Go包。
protoc 在这里抛出错误的原因是,生成的 2 个 Go 文件将属于 2 个不同的包,但在 Go 中,我们不能将不同包的 2 个文件放在同一个文件夹中,在本例中就是 文件pb夹。
设置包名称
为了解决这个问题,我们必须通过使用此命令在我们的 proto 文件中指定 protoc 将生成的代码放在同一个包中package techschool.pcbook。
文件memory_message.proto:
syntax = "proto3";
package techschool.pcbook;
message Memory {
enum Unit {
UNKNOWN = 0;
BIT = 1;
BYTE = 2;
KILOBYTE = 3;
MEGABYTE = 4;
GIGABYTE = 5;
TERABYTE = 6;
}
uint64 value = 1;
Unit unit = 2;
}
文件processor_message.proto:
syntax = "proto3";
package techschool.pcbook;
import "memory_message.proto";
message CPU {
string brand = 1;
string name = 2;
uint32 number_cores = 3;
uint32 number_threads = 4;
double min_ghz = 5;
double max_ghz = 6;
}
message GPU {
string brand = 1;
string name = 2;
double min_ghz = 3;
double max_ghz = 4;
Memory memory = 5;
}
现在如果我们make gen再次运行,它将起作用,并且生成的两个 Go 文件将属于同一个包techschool_pcbook。Protoc 在这里使用下划线,因为在 Go 中我们不能在包名中使用点。
文件memory_message.pb.go:
package techschool_pcbook
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Memory_Unit int32
const (
Memory_UNKNOWN Memory_Unit = 0
Memory_BIT Memory_Unit = 1
Memory_BYTE Memory_Unit = 2
Memory_KILOBYTE Memory_Unit = 3
Memory_MEGABYTE Memory_Unit = 4
Memory_GIGABYTE Memory_Unit = 5
Memory_TERABYTE Memory_Unit = 6
)
...
文件processor_message.pb.go:
package techschool_pcbook
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type CPU struct {
Brand string `protobuf:"bytes,1,opt,name=brand,proto3" json:"brand,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
NumberCores uint32 `protobuf:"varint,3,opt,name=number_cores,json=numberCores,proto3" json:"number_cores,omitempty"`
NumberThreads uint32 `protobuf:"varint,4,opt,name=number_threads,json=numberThreads,proto3" json:"number_threads,omitempty"`
MinGhz float64 `protobuf:"fixed64,5,opt,name=min_ghz,json=minGhz,proto3" json:"min_ghz,omitempty"`
MaxGhz float64 `protobuf:"fixed64,6,opt,name=max_ghz,json=maxGhz,proto3" json:"max_ghz,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
...
定义存储消息
让我们继续我们的项目。我将为storage_message.proto文件中的存储创建一条新消息。
pcbook
├── proto
│ ├── processor_message.proto
│ ├── memory_message.proto
│ └── storage_message.proto
├── pb
│ ├── processor_message.pb.go
│ └── memory_message.pb.go
├── main.go
└── Makefile
存储可以是硬盘驱动器或固态驱动器。所以我们应该Driver用这两个值定义一个枚举。
syntax = "proto3";
package techschool.pcbook;
import "memory_message.proto";
message Storage {
enum Driver {
UNKNOWN = 0;
HDD = 1;
SSD = 2;
}
Driver driver = 1;
Memory memory = 2;
}
然后在存储消息中添加2个字段:驱动程序类型和内存大小。
使用选项为 Go 生成自定义包名称
protoc 为我们生成的Go 包名techschool_pcbook有点太长,并且与pb包含 Go 文件的文件夹名称不匹配。
所以我想告诉它用作pb包名,但仅适用于 Go,因为 Java 或其他语言将使用不同的包命名约定。
我们可以通过option go_package = "pb"在 proto 文件中进行设置来做到这一点。
文件storage_message.proto:
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
import "memory_message.proto";
message Storage {
enum Driver {
UNKNOWN = 0;
HDD = 1;
SSD = 2;
}
Driver driver = 1;
Memory memory = 2;
}
文件memory_message.proto:
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
message Memory {
enum Unit {
UNKNOWN = 0;
BIT = 1;
BYTE = 2;
KILOBYTE = 3;
MEGABYTE = 4;
GIGABYTE = 5;
TERABYTE = 6;
}
uint64 value = 1;
Unit unit = 2;
}
文件processor_message.proto:
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
import "memory_message.proto";
message CPU {
string brand = 1;
string name = 2;
uint32 number_cores = 3;
uint32 number_threads = 4;
double min_ghz = 5;
double max_ghz = 6;
}
message GPU {
string brand = 1;
string name = 2;
double min_ghz = 3;
double max_ghz = 4;
Memory memory = 5;
}
现在如果我们运行make gen生成代码,所有生成的 Go 文件都将使用相同的pb包。
pcbook
├── proto
│ ├── processor_message.proto
│ ├── memory_message.proto
│ └── storage_message.proto
├── pb
│ ├── processor_message.pb.go
│ ├── memory_message.pb.go
│ └── storage_message.pb.go
├── main.go
└── Makefile
文件storage_message.pb.go:
package pb
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Storage_Driver int32
const (
Storage_UNKNOWN Storage_Driver = 0
Storage_HDD Storage_Driver = 1
Storage_SSD Storage_Driver = 2
)
var Storage_Driver_name = map[int32]string{
0: "UNKNOWN",
1: "HDD",
2: "SSD",
}
var Storage_Driver_value = map[string]int32{
"UNKNOWN": 0,
"HDD": 1,
"SSD": 2,
}
func (x Storage_Driver) String() string {
return proto.EnumName(Storage_Driver_name, int32(x))
}
func (Storage_Driver) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_170f09d838bd8a04, []int{0, 0}
}
type Storage struct {
Driver Storage_Driver `protobuf:"varint,1,opt,name=driver,proto3,enum=techschool.pcbook.Storage_Driver" json:"driver,omitempty"`
Memory *Memory `protobuf:"bytes,2,opt,name=memory,proto3" json:"memory,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
...
文件memory_message.pb.go:
package pb
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Memory_Unit int32
const (
Memory_UNKNOWN Memory_Unit = 0
Memory_BIT Memory_Unit = 1
Memory_BYTE Memory_Unit = 2
Memory_KILOBYTE Memory_Unit = 3
Memory_MEGABYTE Memory_Unit = 4
Memory_GIGABYTE Memory_Unit = 5
Memory_TERABYTE Memory_Unit = 6
)
var Memory_Unit_name = map[int32]string{
0: "UNKNOWN",
1: "BIT",
2: "BYTE",
3: "KILOBYTE",
4: "MEGABYTE",
5: "GIGABYTE",
6: "TERABYTE",
}
var Memory_Unit_value = map[string]int32{
"UNKNOWN": 0,
"BIT": 1,
"BYTE": 2,
"KILOBYTE": 3,
"MEGABYTE": 4,
"GIGABYTE": 5,
"TERABYTE": 6,
}
func (x Memory_Unit) String() string {
return proto.EnumName(Memory_Unit_name, int32(x))
}
func (Memory_Unit) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_c0c7f919ccc765da, []int{0, 0}
}
type Memory struct {
Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
Unit Memory_Unit `protobuf:"varint,2,opt,name=unit,proto3,enum=techschool.pcbook.Memory_Unit" json:"unit,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
...
文件processor_message.pb.go:
package pb
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type CPU struct {
Brand string `protobuf:"bytes,1,opt,name=brand,proto3" json:"brand,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
NumberCores uint32 `protobuf:"varint,3,opt,name=number_cores,json=numberCores,proto3" json:"number_cores,omitempty"`
NumberThreads uint32 `protobuf:"varint,4,opt,name=number_threads,json=numberThreads,proto3" json:"number_threads,omitempty"`
MinGhz float64 `protobuf:"fixed64,5,opt,name=min_ghz,json=minGhz,proto3" json:"min_ghz,omitempty"`
MaxGhz float64 `protobuf:"fixed64,6,opt,name=max_ghz,json=maxGhz,proto3" json:"max_ghz,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
...
定义键盘消息
接下来,我们将定义键盘消息。它可以有 QWERTY、QWERTZ 或 AZERTY 布局。供您参考,QWERTZ 在德国广泛使用。而在法国,AZERTY 更受欢迎。
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
message Keyboard {
enum Layout {
UNKNOWN = 0;
QWERTY = 1;
QWERTZ = 2;
AZERTY = 3;
}
Layout layout = 1;
bool backlit = 2;
}
键盘可以有背光,也可以没有背光,因此我们使用布尔字段。很简单,对吧?
定义屏幕消息
现在我们来写一个更复杂的消息:屏幕。它有一个嵌套的消息类型:Resolution。我们在这里使用嵌套类型的原因是:分辨率是一个与屏幕紧密相关的实体,单独存在时没有任何意义。
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
message Screen {
message Resolution {
uint32 width = 1;
uint32 height = 2;
}
enum Panel {
UNKNOWN = 0;
IPS = 1;
OLED = 2;
}
float size_inch = 1;
Resolution resolution = 2;
Panel panel = 3;
bool multitouch = 4;
}
类似地,我们有一个屏幕面板枚举,可以是 IPS 或 OLED。然后是屏幕尺寸(以英寸为单位)。最后是一个布尔字段,用于判断它是否是多点触控屏幕。
定义笔记本电脑消息
好了,我想我们基本上已经定义了笔记本电脑的所有必要组件。现在让我们定义笔记本电脑的消息。
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
import "processor_message.proto";
import "memory_message.proto";
message Laptop {
string id = 1;
string brand = 2;
string name = 3;
CPU cpu = 4;
Memory ram = 5;
}
它有一个字符串类型的唯一标识符。此 ID 将由服务器自动生成。它有一个品牌和一个名称。然后是 CPU 和 RAM。我们需要导入其他 proto 文件才能使用这些类型。
重复字段
一台笔记本电脑可以有多个 GPU,所以我们使用repeated关键字来告诉 protoc 这是一个 GPU 列表。
同样,笔记本电脑有多个存储器也是正常的,所以这个字段也应该重复。
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
import "processor_message.proto";
import "memory_message.proto";
import "storage_message.proto";
import "screen_message.proto";
import "keyboard_message.proto";
message Laptop {
string id = 1;
string brand = 2;
string name = 3;
CPU cpu = 4;
Memory ram = 5;
repeated GPU gpus = 6;
repeated Storage storages = 7;
Screen screen = 8;
Keyboard keyboard = 9;
}
接下来是 2 个常规字段:屏幕和键盘。这很简单。
Oneof 字段
那么笔记本电脑的重量呢?假设我们允许以公斤或磅为单位指定它。为了做到这一点,我们可以使用一个新关键字:oneof。
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
import "processor_message.proto";
import "memory_message.proto";
import "storage_message.proto";
import "screen_message.proto";
import "keyboard_message.proto";
message Laptop {
string id = 1;
string brand = 2;
string name = 3;
CPU cpu = 4;
Memory ram = 5;
repeated GPU gpus = 6;
repeated Storage storages = 7;
Screen screen = 8;
Keyboard keyboard = 9;
oneof weight {
double weight_kg = 10;
double weight_lb = 11;
}
}
在此块中,我们定义了 2 个字段,一个用于千克,另一个用于磅。请记住,当您使用oneof字段组时,只有最后分配的字段才会保留其值。
知名类型
然后我们再添加 2 个字段:美元价格和笔记本电脑的发布年份。最后,我们需要一个时间戳字段来存储系统中记录的最后更新时间。
Timestamp 是 Google 已经定义的著名类型之一,因此我们只需要导入包并使用它。
syntax = "proto3";
package techschool.pcbook;
option go_package = "pb";
import "processor_message.proto";
import "memory_message.proto";
import "storage_message.proto";
import "screen_message.proto";
import "keyboard_message.proto";
import "google/protobuf/timestamp.proto";
message Laptop {
string id = 1;
string brand = 2;
string name = 3;
CPU cpu = 4;
Memory ram = 5;
repeated GPU gpus = 6;
repeated Storage storages = 7;
Screen screen = 8;
Keyboard keyboard = 9;
oneof weight {
double weight_kg = 10;
double weight_lb = 11;
}
double price_usd = 12;
uint32 release_year = 13;
google.protobuf.Timestamp updated_at = 14;
}
还有许多其他知名类型。请查看此链接以了解更多信息。
现在我们可以运行make gen来为所有消息生成 Go 代码。
pcbook
├── proto
│ ├── processor_message.proto
│ ├── memory_message.proto
│ ├── storage_message.proto
│ ├── keyboard_message.proto
│ ├── screen_message.proto
│ └── laptop_message.proto
├── pb
│ ├── processor_message.pb.go
│ ├── memory_message.pb.go
│ └── storage_message.pb.go
│ ├── keyboard_message.pb.go
│ ├── screen_message.pb.go
│ └── laptop_message.pb.go
├── main.go
└── Makefile