go微服務開山篇之protoBuf元件

2020-09-21 11:00:15

protoBuf安裝

下載安裝

  1. 下載地址:https://github.com/protocolbuffers/protobuf/releases

    根據系統下載對應的安裝檔案,我現在是win10就下載了protoc-3.13.0-win64.zip

  2. 解壓檔案,如:E:\protoc-3.13.0-win64

  3. 新增環境變數或者將bin目錄下的protoc.exe拷貝到gopath下的bin資料夾中,不用新增環境變數

    1. 在系統變數中新建:變數名:PROTOBUF_HOME 變數值:E:\protoc-3.13.0-win64
    2. 在系統變數中,編輯path,將前面建的PROTOBUF_HOME下的新增進去%PROTOBUF_HOME%\bin;
    3. cmd中執行protoc --version檢視版本,如果可以看到版本號,說明環境變數沒問題
  4. 安裝protoc-gen-go

    1. 下載:go get -u github.com/golang/protobuf/protoc-gen-go
    2. gopath的bin生成protoc-gen-go.exe

proto命令

# 檢視版本資訊
protoc --version

# 生成對應語言的檔案: --go_out=.   這是生成golang檔案 .代表當前目錄  hello.proto檔名
protoc --go_out=. hello.proto
# 或者
protoc hello.proto --go_out=.

proto語法

hello.proto

// 版本
syntax = "proto3";

/*
option設定包名,不設定預設會使用下一行的package後面的作為包名.
不設定會報一個警告:WARNING: Missing 'go_package' option in "hello.proto",
*/
option go_package = ".;hello";
package hello;

// message:一種訊息型別,後面會詳細說明型別
message Hello{
  // optional預設會加可以不寫,寫了生成
  optional string name = 1;
  int32 age = 2;
  string addr = 3;
}

說明:message 表示messages訊息型別,後面跟訊息名稱

message型別

定義方式

// message後面跟訊息名稱
message xxx{
}

說明:

  • .proto檔案中定義多種messages型別如:message、enum 和 service
  • 一個.proto檔案中可以定義多個messages型別,建議一個檔案中不要出現多種型別,會導致依賴性膨脹

欄位定義

message Hello{
  optional string name = 1;
  required int32 age = 2;
  repeated string addr = 3;
}

欄位詳細說明

/*
	optional預設可以不寫,如果寫了生成需要帶--experimental_allow_proto3_optional引數如:protoc --go_out=. hello.proto --experimental_allow_proto3_optional
	string 型別
	stringVal 變數名
	= 1 不是值是,=後面的數位表示欄位編號
*/
optional string stringVal = 1;
1.欄位規則:可以忽略,不用指定,預設為optional
	// optional 可以傳或者不傳值
    optional:欄位可出現 0 次或1次,表示可選,為空可以指定預設值 [default = 10],不然使用語言的預設值
        optional int32 result_per_page = 3 [default = 10];
            字串預設為空字串
            數位預設為0
            bool預設為false
            列舉預設為第一個列出的值,一定要注意列舉的順序,容易有坑
    // required 必須傳值、而且只能傳一次  注意proto3中已經無法使用required                                   
    required:欄位只能也必須出現 1// repeated 可以傳多個值或者不傳值                                        
    repeated:欄位可出現任意多次(包括 0),陣列或列表要使用這種
2.型別:
    int32int64、sint32、sint64、string32-bit ....
3.欄位名稱:
    注意命名規範 
4.=後面的數位表示欄位編號:
    0 ~ 536870911(除去 1900019999 之間的數位,預留的)


關於optional的說明:
對於接收方,如果能夠識別可選欄位就進行相應的處理,如果無法識別,則忽略該欄位,
訊息中的其它欄位正常處理。---因為optional欄位的特性,
很多介面在升級版本中都把後來新增的欄位都統一的設定為optional欄位,
這樣老的版本無需升級程式也可以正常的與新的軟體進行通訊,只不過新的欄位無法識別而已,
因為並不是每個節點都需要新的功能,因此可以做到按需升級和平滑過渡

註釋

// 單行註釋
/* */ 多行註釋

enum列舉型別

定義方式

enum EnumAllowingAlias {
  option allow_alias = true; 
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}

你可以通過為不同的列舉常數指定相同的值來定義別名。
為此,你需要將 allow_alias 選項設定為true,否則 protocol 編譯器將在找到別名時生成錯誤訊息。

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}

enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
}

欄位定義

UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;

都是常數,沒有型別申明

注意:message中可以巢狀enum型別,裡層的欄位編號和外層的欄位編號不衝突

service型別

定義方式

service TestService {
    //rpc 伺服器端對外的函數名(傳入引數)returns(返回引數)
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

欄位定義

rpc SayHello (HelloRequest) returns (HelloReply) {}
//rpc 伺服器端對外的函數名   (傳入引數) returns (返回引數){}

使用其他 Message 型別

message後面的名稱可以在其他的型別中使用,這個時候型別就是指定的型別了

message SearchResponse {
  repeated Result result = 1;   // 這裡的型別是下面定的Result型別,包含 Result message的所有
}

message Result {
  required string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}

欄位型別

.proto說明Go語言C++語言Python語言Java語言
double浮點型別float64doublefloatdouble
float浮點型別float32floatfloatfloat
int32int32int32intint
int64int64int64int/longlong
uint32uint32uint32int/longint
uint64uint64uint64int/longlong
sint32int32int32intint
sint64int64int64int/longlong
fixed32uint32uint32int/longint
fixed64uint64uint64int/longlong
sfixed32int32int32intint
sfixed34int64int64int/longlong
boolboolboolboolboolean
stringstringstringstr/unicodeString
bytes[]bytestringstrByteString

protoBuf元件小實戰

proto檔案

E:\workspace\src\sample\protoBuf_sample\hello.proto

syntax = "proto3";
option go_package = ".;hello";
package hello;

message Hello{
  optional string name = 1;
  int32 age = 2;
  repeated string addr = 3;
}

proto轉go檔案

E:\workspace\src\sample\protoBuf_sample 到這個目錄執行命令

// 因為proto檔案中有設定optional,轉換就需要加--experimental_allow_proto3_optional引數
protoc --go_out=. hello.proto --experimental_allow_proto3_optional

go使用轉換過來的檔案

這是proto檔案生成的
E:\workspace\src\sample\protoBuf_sample\hello.pb.go

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.23.0
// 	protoc        v3.13.0
// source: hello.proto

package hello

import (
	proto "github.com/golang/protobuf/proto"
	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
	reflect "reflect"
	sync "sync"
)

const (
	// Verify that this generated code is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
	// Verify that runtime/protoimpl is sufficiently up-to-date.
	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)

// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4

type Hello struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name *string  `protobuf:"bytes,1,opt,name=name,proto3,oneof" json:"name,omitempty"`
	Age  int32    `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
	Addr []string `protobuf:"bytes,3,rep,name=addr,proto3" json:"addr,omitempty"`
}

func (x *Hello) Reset() {
	*x = Hello{}
	if protoimpl.UnsafeEnabled {
		mi := &file_hello_proto_msgTypes[0]
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		ms.StoreMessageInfo(mi)
	}
}

func (x *Hello) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*Hello) ProtoMessage() {}

func (x *Hello) ProtoReflect() protoreflect.Message {
	mi := &file_hello_proto_msgTypes[0]
	if protoimpl.UnsafeEnabled && x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use Hello.ProtoReflect.Descriptor instead.
func (*Hello) Descriptor() ([]byte, []int) {
	return file_hello_proto_rawDescGZIP(), []int{0}
}

func (x *Hello) GetName() string {
	if x != nil && x.Name != nil {
		return *x.Name
	}
	return ""
}

func (x *Hello) GetAge() int32 {
	if x != nil {
		return x.Age
	}
	return 0
}

func (x *Hello) GetAddr() []string {
	if x != nil {
		return x.Addr
	}
	return nil
}

var File_hello_proto protoreflect.FileDescriptor

var file_hello_proto_rawDesc = []byte{
	0x0a, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x68,
	0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x4f, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x17, 0x0a,
	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e,
	0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20,
	0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72,
	0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x42, 0x07, 0x0a, 0x05,
	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x3b, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_hello_proto_rawDescOnce sync.Once
	file_hello_proto_rawDescData = file_hello_proto_rawDesc
)

func file_hello_proto_rawDescGZIP() []byte {
	file_hello_proto_rawDescOnce.Do(func() {
		file_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_hello_proto_rawDescData)
	})
	return file_hello_proto_rawDescData
}

var file_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_hello_proto_goTypes = []interface{}{
	(*Hello)(nil), // 0: hello.Hello
}
var file_hello_proto_depIdxs = []int32{
	0, // [0:0] is the sub-list for method output_type
	0, // [0:0] is the sub-list for method input_type
	0, // [0:0] is the sub-list for extension type_name
	0, // [0:0] is the sub-list for extension extendee
	0, // [0:0] is the sub-list for field type_name
}

func init() { file_hello_proto_init() }
func file_hello_proto_init() {
	if File_hello_proto != nil {
		return
	}
	if !protoimpl.UnsafeEnabled {
		file_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
			switch v := v.(*Hello); i {
			case 0:
				return &v.state
			case 1:
				return &v.sizeCache
			case 2:
				return &v.unknownFields
			default:
				return nil
			}
		}
	}
	file_hello_proto_msgTypes[0].OneofWrappers = []interface{}{}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_hello_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   1,
			NumExtensions: 0,
			NumServices:   0,
		},
		GoTypes:           file_hello_proto_goTypes,
		DependencyIndexes: file_hello_proto_depIdxs,
		MessageInfos:      file_hello_proto_msgTypes,
	}.Build()
	File_hello_proto = out.File
	file_hello_proto_rawDesc = nil
	file_hello_proto_goTypes = nil
	file_hello_proto_depIdxs = nil
}

E:\workspace\src\sample\main.go

package main

import (
	"fmt"
	"github.com/golang/protobuf/proto"
	hello "sample/protoBuf_sample"
)

func main() {
	name := "張三"
	data := &hello.Hello{
		// Name *string 這是因為proto中設定optional
		Name: &name,
		Age:  18,
		Addr: []string{"北京", "上海", "廣州"},
	}
	fmt.Println(data)

	// 編碼
	byte_data, _ := proto.Marshal(data)
	//fmt.Println(byte_data)
	fmt.Printf("編碼:%v\n", byte_data)

	var newData hello.Hello
	// 解碼
	proto.Unmarshal(byte_data, &newData)
	fmt.Printf("解碼:%v", newData)

	fmt.Println(newData.Name) //返回一個地址
	// 獲取值
	fmt.Println(*newData.Name)
	fmt.Println(newData.GetName())

	// 將struct轉換為字串
	fmt.Println(newData.String())

	// Reset()會將struct的所有field的值清零
	//fmt.Println(newData.Reset)
}

/*
name:"張三"  age:18  addr:"北京"  addr:"上海"  addr:"廣州"
編碼:[10 6 229 188 160 228 184 137 16 18 26 6 229 140 151 228 186 172 26 6 228 184 138 230 181 183 26 6 229 185 191 229 183 158]
解碼:{{{} [] [] 0xc00009c780} 0 [] 0xc0000886c0 18 [北京 上海 廣州]}0xc0000886c0
張三
張三
name:"張三"  age:18  addr:"北京"  addr:"上海"  addr:"廣州"
0x5cd390
*/