编写 proto 文件

PPG007 ... 2022-5-6 About 3 min

# 编写 proto 文件

# 示例文件

// 指定 Protobuf 的版本。
syntax = "proto3";
// 指明生成 go 代码的包名。
option go_package = "./;pb";
// 对于 Java,将一个 proto 文件编译为多个接口和类。
option java_multiple_files = true;
// 指定生成的 Java 代码的包名。
option java_package = "com.demo.pb";
// 引入其他 proto 文件。
import "laptop_message.proto";
import "filter_message.proto";

// 使用 message 关键字定义一个普通变量。
message CreateLaptopRequest {
  Laptop laptop = 1;
}

message CreateLaptopResponse {
  string id = 1;
}

message SearchLaptopRequest {
  Filter filter = 1;
}

message SearchLaptopResponse {
  Laptop laptop = 1;
}

message UploadImageRequest {
  // oneof 关键字表示这个 message 中这两种成员同时只能存在一个,代码中会存在覆盖问题。
  oneof data {
    ImageInfo info =1;
    bytes chunk_data = 2;
  }
}

message ImageInfo {
  string laptop_id = 1;
  string image_type = 2;
}

message UploadImageResponse {
  string id = 1;
  uint32 size = 2;
}

message RateLaptopRequest {
  string laptop_id = 1;
  double score = 2;
}

message RateLaptopResponse {
  string laptop_id = 1;
  uint32 rated_count =2;
  double average_score = 3;
}

message Laptop {
  string id = 1;
  string brand = 2;
  string name = 3;
  CPU cpu = 4;
  Memory ram = 5;
  // repeated 修饰数组。
  repeated GPU gpus = 6;
  repeated Storage storeges = 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;
}

message Memory {
  // 枚举类要以 0 开始。
  enum Unit {
    UNKNOWN = 0;
    BIT = 1;
    BYTE = 2;
    KILOBYTE = 3;
    MEGABYTE = 4;
    GIGABYTE = 5;
    TERABYTE = 6;
  }
  uint64 value = 1;
  Unit unit = 2;
}

service LaptopService {
  // 注意是 returns 不是 return。
  // 对于流,使用 stream 关键字修饰。
  rpc CreateLaptop(CreateLaptopRequest) returns (CreateLaptopResponse) {};
  rpc SearchLaptop (SearchLaptopRequest) returns (stream SearchLaptopResponse) {};
  rpc UploadImage (stream UploadImageRequest) returns (UploadImageResponse) {};
  rpc RateLaptop (stream RateLaptopRequest) returns (stream RateLaptopResponse) {};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

# Extensions

extensions 允许你在 Message 中定义一个区间的字段数值,相当于是在当前文件中尚未被定义的字段的占位符,这允许其他 proto 文件为这个 Message 添加一些字段,下面是一个示例:

example.proto:

syntax = "proto2";

option go_package = "mytest/proto/proto2";

message Foo {
    required int64 a = 1;
    optional int64 b = 2;
    extensions 100 to max;
}
1
2
3
4
5
6
7
8
9

extensions.proto:

syntax = "proto2";

option go_package = "mytest/proto/proto2";

import "proto2/example.proto";

extend Foo {
    optional string c = 100;
}
1
2
3
4
5
6
7
8
9

最后,通过代码修改值如下:

func main() {
	f := proto2.Foo{
		A: proto.Int64(123),
		B: proto.Int64(456),
	}
	proto.SetExtension(&f, proto2.E_C.TypeDescriptor().Type(), "test")
	log.Println(f.String())
}
1
2
3
4
5
6
7
8

Note

proto3 不支持使用 extensions 关键字。

# custom option

如果想自定义 option,需要引入 google/protobuf/descriptor.proto,这其实就是一个 proto2 编写的 proto 文件:

syntax = "proto3";

option go_package = "mytest/proto/proto3";

import "google/protobuf/descriptor.proto";

extend google.protobuf.FileOptions {
  string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
  int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
  float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
  int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
  bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
  uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
  MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
  MyMessage my_method_option = 50007;
}

option (my_file_option) = "Hello world!";

message MyMessage {
  option (my_message_option) = 1234;

  int32 foo = 1 [(my_field_option) = 4.5];
  string bar = 2;
  oneof qux {
    option (my_oneof_option) = 42;

    string quux = 3;
  }
}

enum MyEnum {
  option (my_enum_option) = true;

  FOO = 0 [(my_enum_value_option) = 321];
  BAR = 1;
}

message RequestType {}
message ResponseType {}

service MyService {
  option (my_service_option) = FOO;

  rpc MyMethod(RequestType) returns(ResponseType) {
    // Note:  my_method_option has type MyMessage.  We can set each field
    //   within it using a separate "option" line.
    option (my_method_option).foo = 567;
    option (my_method_option).bar = "Some string";
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

读取每一个 option 的示例代码如下:

package main

import (
	"encoding/json"
	"log"
	"mytest/proto/proto3"

	"github.com/spf13/cast"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protodesc"
)

func main() {
	m := proto3.MyMessage{}

	f := protodesc.ToFileDescriptorProto(m.ProtoReflect().Descriptor().ParentFile())
	log.Println(cast.ToString(proto.GetExtension(f.Options, proto3.E_MyFileOption)))

	dp := protodesc.ToDescriptorProto(m.ProtoReflect().Descriptor())
	log.Println(cast.ToInt32(proto.GetExtension(dp.Options, proto3.E_MyMessageOption)))

	fdp := protodesc.ToFieldDescriptorProto(m.ProtoReflect().Descriptor().Fields().ByNumber(1))
	log.Println(cast.ToFloat64(proto.GetExtension(fdp.Options, proto3.E_MyFieldOption)))

	odp := protodesc.ToOneofDescriptorProto(m.ProtoReflect().Descriptor().Oneofs().ByName("qux"))
	log.Println(cast.ToInt64(proto.GetExtension(odp.Options, proto3.E_MyOneofOption)))

	var e proto3.MyEnum = 123

	edp := protodesc.ToEnumDescriptorProto(e.Descriptor())
	log.Println(cast.ToBool(proto.GetExtension(edp.Options, proto3.E_MyEnumOption)))

	evdp := protodesc.ToEnumValueDescriptorProto(e.Descriptor().Values().ByNumber(0))
	log.Println(cast.ToUint32(proto.GetExtension(evdp.Options, proto3.E_MyEnumValueOption)))


	sdp := protodesc.ToServiceDescriptorProto(m.ProtoReflect().Descriptor().ParentFile().Services().ByName("MyService"))
	i := proto.GetExtension(sdp.Options, proto3.E_MyServiceOption)
	log.Println(i.(proto3.MyEnum))

	mdp := protodesc.ToMethodDescriptorProto(m.ProtoReflect().Descriptor().ParentFile().Services().ByName("MyService").Methods().ByName("MyMethod"))
	i2 := proto.GetExtension(mdp.Options, proto3.E_MyMethodOption)
	log.Println(i2.(*proto3.MyMessage).String())
}

func String(v interface{}) string {
	b, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	return string(b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# Any

Any 允许定义一个可以存储任意类型的字段,底层是 bytes 数组,需要 import google/protobuf/any.proto

syntax = "proto3";

option go_package = "mytest/proto/proto3";

import "google/protobuf/any.proto";

message A {
  string a = 1;
}

message B {
  string b = 1;
}

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Go 示例代码:

func main() {
	s := proto3.ErrorStatus{
		Message: "test",
	}
	a := proto3.A{
		A: "a",
	}
	b := proto3.B{
		B: "b",
	}
	anyA, err := anypb.New(&a)
	if err != nil {
		panic(err)
	}
	anyB, err := anypb.New(&b)
	if err != nil {
		panic(err)
	}
	s.Details = append(s.Details, anyA, anyB)
	for _, d := range s.Details {
		switch d.MessageName() {
		case "A":
			pm, err := d.UnmarshalNew()
			if err != nil {
				panic(err)
			}
			log.Println(pm.(*proto3.A).String())
		case "B":
			pm, err := d.UnmarshalNew()
			if err != nil {
				panic(err)
			}
			log.Println(pm.(*proto3.B).String())
		default:
			panic("unknown message name")
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Last update: July 10, 2022 09:29
Contributors: PPG007