1、背景

正常情况下通过 protoc 生成的 pb.go 文件中的标签是官方定义好的,如果需要pb.go 作为参数请求,或者需要将该结构体转为json ,则会有部分“空”值字段会被忽略。从而存在一些问题。

如下图所示

message ListChannelReq {
int64  community_id = 1;
}

通过protoc 生成 pb.go 文件

protoc -I . --go_out=plugins=grpc,paths=source_relative:. ./channel.proto
type ListChannelReq struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	CommunityId int64 `protobuf:"varint,1,opt,name=community_id,json=communityId,proto3" json:"community_id,omitempty"` 
}

问题:
1、这里 生成的 字段中 json 标签字段 如果如果值为0 则在转json的时候字段丢失,会转成 {}
2、如果我们想要在该 字段属性上添加其他标签该怎么办?如 form 标签

2、自定义protobuf 标签

2.1 通过修改 protoc-gen-go 源码

git clone  https://github.com/golang/protobuf.git

1、找到 generateMessage 方法

2、在方法内找到 _tag := fmt.Sprintf(“protobuf:%s json:%q”, g.goTag(message, field, wiretype), jsonName+“,omitempty”),并替换成 tag := fmt.Sprintf(“protobuf:%s json:%q xml:%q form:%q”, g.goTag(message, field, wiretype), jsonName, jsonName, jsonName), _对就多了个form标签

3、添加一个方法, 获取到proto文件的注释信息, 在generator.go文件的任意位置即可。

func (g *Generator) GetComments(path string) string {
	if !g.writeOutput {
		return ""
	}
	if loc, ok := g.file.comments[path]; ok {
		text := strings.TrimSuffix(loc.GetLeadingComments(), "\n")
		for _, line := range strings.Split(text, "\n") {
			comments := strings.TrimPrefix(line, " ")
			return comments
		}
	}
	return ""
}

4、在文件内找到**_ g.PrintComments(fmt.Sprintf(“%s,%d,%d”, message.path, messageFieldPath, i)),_**注释掉,并添加如下内容

//g.PrintComments(fmt.Sprintf("%s,%d,%d", message.path, messageFieldPath, i))
path := fmt.Sprintf("%s,%d,%d", message.path, messageFieldPath, i)
g.PrintComments(path)
comments := g.GetComments(path)
if comments != "" && strings.Contains(comments, "binding") {
	comments = strings.Replace(comments, "\r", "", 1)
	cArr := strings.Split(comments, "|")
	if len(cArr) > 1 {
		tag = tag + fmt.Sprintf(" binding:%q", strings.Replace(comments, cArr[0]+"|binding:", "", 1))
	}else{
		tag = tag + fmt.Sprintf(" binding:%q", strings.Replace(comments, "binding:", "", 1))
	}
}

5、文件修改完毕进行编译

SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build main.go

6、编译好的文件protoc 安装位置 并重命名为protoc-gen-go,最好是放到/usr/local/bin目录下。

1、 whereis protoc-gen-go

2.2 使用第三方库 protoc-go-inject-tag

1、安装下载库

go install github.com/favadi/protoc-go-inject-tag@latest
$ protoc-go-inject-tag -h
Usage of protoc-go-inject-tag:
  -XXX_skip string
        tags that should be skipped (applies 'tag:"-"') for unknown fields (deprecated since protoc-gen-go v1.4.0)
  -input string
        pattern to match input file(s)
  -verbose
        verbose logging
  -remove_tag_comment
        removes tag comments from the generated file(s)

2、使用 ( 在原有的基础上添加 @gotags )

//  gotags: tagname:"tagvalue"  ...
message ListChannelReq {
	// @gotags: json:"community_id" form:"community_id"
	int64  community_id = 1;
}

3、像往常一样使用 protoc 命令生成文件

protoc -I . --go_out=plugins=grpc,paths=source_relative:. ./*.proto
type ListChannelReq struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	// @gotags: json:"community_id" form:"community_id"
	CommunityId int64 `protobuf:"varint,1,opt,name=community_id,json=communityId,proto3" json:"community_id,omitempty"`
}

4、然后protoc-go-inject-tag针对生成的文件运行

protoc-go-inject-tag -input="*.pb.go" -remove_tag_comment

使用该-remove_tag_comment标志,您可以删除通常注释到生成的代码的 gotag 注释。这允许使用代码注释生成 openapi 文件的 swag/openapi 生成器等库提供更无缝的支持。

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

	CommunityId int64 `protobuf:"varint,1,opt,name=community_id,json=communityId,proto3" json:"community_id" form:"community_id"`
}

3 参考建议

建议使用第二种方案,其侵入性较小,方便快捷