最近修改历史项目的时候,有使用到 proto文件生成go代码,有踩一下坑,分享一下。
开始前 如果不熟悉Protobuf语法可以先看这篇-Protobuf语法 ,如果没有安装生产工具请先执行:
go get -u github.com/golang/protobuf/proto go get -u github.com/golang/protobuf/protoc-gen-go go get -u github.com/micro/micro/v2/cmd/protoc-gen-micro
尝试 编写 proto 文件 我们先新建common.proto:
syntax = "proto3"; //语法声明 enum TypeHello { Unuse = 0; Morning = 1; Afernoon = 2; Evening = 3; }
protoc 生成 .pb.go protoc --proto_path=./ --micro_out=. --go_out=. *.proto
错误分析 能看到错误其实是protoc-gen-go报出的,解决方案有两种:
使用 go_package 参数
命令行使用–go_opt=M
我比较建议使用 go_package,当多个proto文件有依赖时,使用 go_package 比较清晰,使用 –go_opt=M 可能要麻烦得多,甚至出错。诸如以下:
// common.proto syntax = "proto3"; //语法声明 package common; //包名 // go_package 使用 go mod 需要的路径即可,也可以是私有 gitlab package option go_package = "github.com/puresai/go-learing/micro/hello/common"; enum TypeHello { Unuse = 0; Morning = 1; Afernoon = 2; Evening = 3; }
生成时务必加上 –go_out=paths=source_relative,具体说明可见文末说明。
protoc --proto_path=. --go_out=paths=source_relative:. -I=../common *.proto
这里我们稍微弄复杂一点,hello.proto 依赖 common.proto:
syntax = "proto3"; //语法声明 import "common.proto"; // 依赖 package hello; option go_package="github.com/puresai/go-learing/micro/hello/hello"; // 定义服务 service Demo { rpc SayHello (HelloRequest) returns (HelloReply) {} } // 请求数据格式 message HelloRequest { string name = 1; } // 响应数据格式 message HelloReply { common.TypeHello hello = 2; string message = 1; }
注意这里多了个 micro_out,这时需要 protoc-gen-micro的,会多生成一个.pb.micro.go文件。
protoc --proto_path=. --go_out=paths=source_relative:. --micro_out=paths=source_relative:. -I=../common *.proto
文件生成了,使用go-micro(V2)写一个简单的demo。
package mainimport ( "context" "fmt" "log" "os" "os/signal" "syscall" "time" "github.com/puresai/go-learing/micro/hello/common" "github.com/puresai/go-learing/micro/hello/hello" "github.com/micro/go-micro/v2" _ "github.com/micro/go-plugins/registry/kubernetes/v2" "github.com/sirupsen/logrus" ) const ( ServiceName = "hello-server" ) type HelloServer struct {}func (s *HelloServer) SayHello(ctx context.Context, req *hello.HelloRequest, res *hello.HelloReply) error { res.Message = "hello " + req.Name res.Hello = common.TypeHello_Afernoon return nil } func main () { service := micro.NewService( micro.Name(ServiceName), micro.AfterStart(func () error { fmt.Println("starting..." ) return nil }), micro.Address(":8089" ), ) service.Init() hello.RegisterDemoHandler(service.Server(), &HelloServer{}) go func () { if err := service.Run(); err != nil { log.Fatal(err) } }() stop := make (chan os.Signal) signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT, os.Interrupt) go func () { tick := time.NewTicker(3 * time.Second) for { select { case <-stop: tick.Stop() default : <-tick.C client() } } }() select { case <-stop: logrus.Infof("got exit signal, shutdown" ) } } func client () { service := micro.NewService(micro.Name(ServiceName + "client" )) c := hello.NewDemoService(ServiceName, service.Client()) rsp, err := c.SayHello(context.TODO(), &hello.HelloRequest{Name: "puresai" }) if err != nil { fmt.Println(err) } fmt.Println(rsp.Message) }
可以看到我们通过protoc生成的代码是没有问题的。
more paths 生成的文件在输出目录中的.pb.go
位置取决于–go_out 标识符。有以下模式:
paths=import
: 输出文件将放置在以 Go 包的导入路径命名的目录中。例如,protos/buzz.proto
具有 Go 导入路径的输入文件会example.com/project/protos/fizz
导致输出文件位于example.com/project/protos/fizz/buzz.pb.go
. paths
module=$PREFIX
: 输出文件将放置在以 Go 包的导入路径命名的目录中,但从输出文件名中删除指定的目录前缀。例如,protos/buzz.proto
具有 Go 导入路径example.com/project/protos/fizz
并 example.com/project
指定为module
前缀的输入文件会生成位于protos/fizz/buzz.pb.go
. 在模块路径之外生成任何 Go 包都会导致错误。此模式对于将生成的文件直接输出到 Go 模块很有用。
paths=source_relative
: 输出文件与输入文件放在相同的相对目录中。例如,输入文件protos/buzz.proto
导致输出文件位于protos/buzz.pb.go
. 默认是第一种 `paths=import
其实写文章的时候我也尝试了下使用M,执行命令如下:
// 这里写法有点特殊哦,注意,因为我是在文件同一目录运行,所以 common.proto=../common,这样生成的package才会是common,若是 common.proto=./生成就是下划线了 protoc --proto_path=./ --micro_out=. --go_out=. --go_opt=Mcommon.proto=../common *.proto
虽然这样也能生成,但生成代码并不是我想要的(import部分只是个相对路径,或许换成类似example.com/project/protos/fizz
也能生成)。如果感兴趣,我更建议可以阅读参考文章,自自己,我用错了也未尝不可能呢?
参考: