proto文件生成go代码
最近修改历史项目的时候,有使用到 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 main
import (
"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(
// Set service name
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())
// 发起RPC调用
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
也能生成)。如果感兴趣,我更建议可以阅读参考文章,自自己,我用错了也未尝不可能呢?
参考:
proto文件生成go代码
https://blog.puresai.com/2022/05/25/400/