在业务中经常有这种需求,给某某服务加一个命令字,用来接收的RPC 请求来的protobuffer数据,按照某个pb数据定义反序列化之后,转成JSON 再传输到下游服务。
如果安装我们原来的方案,可能需要每次都修改pb文件,再编译服务再上线
旧的方式:
1、修改proto文件。
2、protoc 产出 *.pb.go文件,
3、编译服务。
但是实际上我们可以在golang 代码里面自动解析proto文件,整个流程在服务内部自动完成。
新的方式:
1、启动服务(从配置服务器加载 proto文件)
2、通过proto文件产生的一个 FileDescriptor ,进而根据对象名称找到 MessageDescriptor
3、直接用MessageDescriptor
可以看到新的方式 ,本质就是需要实现动态pb解析和协议转换的工作。
下面我们看示例代码
保存如下pb定义为test.proto
syntax = "proto2";
package test;
message AddFriendReq {
repeated string phone = 1;
optional string keyword =2;
}
示例代码
代码本身是依赖了一个三方包:github.com/jhump/protoreflect
package main
import (
"bytes"
"fmt"
testpb "github.com/lilien1010/my_gotest/proto" //这个包是从上面的pb文件生产的,用来做序列化测试
"github.com/golang/protobuf/proto"
"github.com/jhump/protoreflect/desc/protoparse"
"github.com/jhump/protoreflect/desc/protoprint"
"github.com/jhump/protoreflect/dynamic"
)
func main() {
Filename := "./proto/test.proto"
Parser := protoparse.Parser{}
//加载并解析 proto文件,得到一组 FileDescriptor
descs, err := Parser.ParseFiles(Filename)
if err != nil {
fmt.Printf("ParseFiles err=%v", err)
return
}
//这里的代码是为了测试打印
Printer := &protoprint.Printer{}
var buf bytes.Buffer
Printer.PrintProtoFile(descs[0], &buf)
fmt.Printf("descsStr=%s\n", buf.String())
//descs 是一个数组,这里因为只有一个文件,就取了第一个元素.
//通过proto的message名称得到MessageDescriptor 结构体定义描述符
msg := descs[0].FindMessage("test.AddFriendReq")
//再用消息描述符,动态的构造一个pb消息体
dmsg := dynamic.NewMessage(msg)
//pb二进制消息 做反序列化 到 test.AddFriendReq 这个消息体
err = dmsg.Unmarshal(GetMessageBin())
//把test.AddFriendReq 消息体序列化成 JSON 数据
jsStr, _ := dmsg.MarshalJSON()
fmt.Printf("jsStr=%s\n", jsStr)
}
//可能从远程服务得到一些二进制数据,这里为了方便测试,用本地序列化的pb
func GetMessageBin() []byte {
req := &testpb.AddFriendReq{
Phone: []string{"13145990022", "131313233"},
Keyword: proto.String("I am good"),
}
bin, err := proto.Marshal(req)
if err != nil {
fmt.Printf("bin=%v,err=%v", bin, err)
}
return bin
}