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

proto

错误分析

能看到错误其实是protoc-gen-go报出的,解决方案有两种:

  1. 使用 go_package 参数
  2. 命令行使用–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.gopaths
  • 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/
作者
puresai
许可协议