protobuf相关使用 | 青训营笔记

276 阅读1分钟

protobuf相关使用 | 青训营笔记

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

计划完成抖音项目,提供的接口为proto2格式,记录一下proto相关内容并转化成golang代码。

1 安装proto

# 下载安装包
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-linux-x86_64.zip
# 解压到 /usr/local 目录下
$ sudo 7z x protoc-3.20.1-linux-x86_64.zip -o/usr/local

如果能正常显示版本,则表示安装成功。

$ protoc --version
libprotoc 3.20.1

2 安装protoc-gen-go

使用protoc-gen-go插件将protoc文件转化为go代码

$ go get -u github.com/golang/protobuf/protoc-gen-go

3 例子

3.1 user.proto

以用户接口为例

syntax = "proto2"; 		//proto版本,此处使用proto2
package douyin.core;	//包名称,用来防止不同的消息类型有命名冲突。

option go_package = "../pkg;message"; //生成go文件路径以及包名

message User {
    required int64 id = 1; // 类型 | 后面的「1」为数字标识符,在消息定义中需要唯一
    required string name = 2; 
    optional int64 follow_count = 3; 
    optional int64 follower_count = 4; 
    required bool is_follow = 5; 
}

字段规则:

required:字段只能也必须出现 1 次

optional:字段可出现 0 次或1次

repeated:字段可出现任意多次(包括 0)

3.2 生成go文件

使用以下命令将文件夹内所有文件生成go文件

$ protoc --go_out=. *.proto

根据上面定义的生成go文件路径,生成文件如下

├── pkg
│   └── user.pb.go
├── proto
    └── user.proto

生成对应的结构体

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

	Id            *int64  `protobuf:"varint,1,req,name=id" json:"id,omitempty"` 
	Name          *string `protobuf:"bytes,2,req,name=name" json:"name,omitempty"` 
	FollowCount   *int64  `protobuf:"varint,3,opt,name=follow_count,json=followCount" json:"follow_count,omitempty"`
	FollowerCount *int64  `protobuf:"varint,4,opt,name=follower_count,json=followerCount" json:"follower_count,omitempty"`
	IsFollow      *bool   `protobuf:"varint,5,req,name=is_follow,json=isFollow" json:"is_follow,omitempty"`
}

3.3 序列化与反序列化

直接使用proto的Marshal、Unmarshal方法即可

import (
	"google.golang.org/protobuf/proto"
	"testing"
)

func TestProto(t *testing.T) {
	user := &message.User{
		Id:       new(int64),
		Name:     new(string),
		IsFollow: new(bool),
	}
	*user.Id = 123
	*user.Name = "someName"
	*user.IsFollow = false
	data, err := proto.Marshal(user)
	if err != nil {
		t.Errorf("Marshal error\n")
	}
	newUser := &message.User{}
	err = proto.Unmarshal(data, newUser)
	if err != nil {
		t.Errorf("Unmarshal error\n")
	}
	if user.GetId() != newUser.GetId() {
		t.Errorf("user:%+v,newUser:%+v\n", user, newUser)
	}
}

运行测试

=== RUN   TestProto
--- PASS: TestProto (0.00s)
PASS
ok  	TikTokLite/proto	0.002s

4 字段类型

proto类型go类型备注proto类型go类型备注
double*float64float*float32
int32*int32int64*int64
uint32*uint32uint64*uint64
sint32*int32适合负数sint64*int64适合负数
fixed32*uint32固长编码,适合大于2^28的值fixed64*uint64固长编码,适合大于2^56的值
sfixed32*int32固长编码sfixed64*int64固长编码
bool*boolstring*stringUTF8 编码,长度不超过 2^32

proto2版本生成的类型均为指针类型,proto3版本生成的类型均为值类型

5 一些问题

5.1 生成go文件报错 protoc-gen-go: program not found or is not executable

go get安装时会安装在$GOPATH/go/bin目录下

执行 cp protoc-gen-go /usr/local/bin/ 即可

5.2 proto import

例如video需要引入user的信息

message Video {
     required User author = 1; //视频作者信息
}

需要在前面import user文件

import "user.proto";

message Video {
     required douyin.core.User author = 1; //douyin.core为user的package名称
}

若video与user在同一个package内则不用添加包名称

import "user.proto";
package douyin.core;
message Video {
     required User author = 1; //douyin.core为user的package名称
}

vscode proto插件import会报错,好像并不用管他,也没找到解决办法。。