在业务中经常有这种需求,给某某服务加一个命令字,用来接收的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
}