今天想跟大家分享两个问题,这两个问题关联性不大,仅仅是同一时期遇到的两个比较有针对性的问题,所以放在一起进行总结
问题一:在rpc中proto文件中如何定义一个Any类型的数据结构,以便容纳任意数据类型的数据进去,让其他客户端服务可以拿到并且成功解析
问题二:在复杂的yaml文件中如何将其转化为json格式的数据格式
先看第一个问题
- 在rpc中proto文件中如何定义一个Any类型的数据结构,以便容纳任意数据类型的数据进去,让其他客户端服务可以拿到并且成功解析
大家都知道,我们再实现业务逻辑时,比如你需要实现一个用户登录的接口,通过rpc服务进行数据核验之后,返回给客户端token字符串,那么具体返回什么数据形式,一般是定义好的,格式固定,在gateway层也有想对应的数据结构来承接通过rpc返回的数据。这种情况一般是比较好处理的。
但是在一种场景下,如果和第三方服务商进行交互,此时对于服务商返回的数据格式就比较不好掌控了,如果在后续的迭代升级中,服务商接口进行了升级改造,变更了数据格式,那么需要重新进行适配,调整数据格式,这种方式就比较不方便,此时,就可以在rpc层面定义一个结构体,用来承接第三方服务商返回的数据格式,然后直接透传给gateway层,让这种数据形式的更改和适配不在rpc层进行适配了,直接丢给客户端,让客户端根据自己的需求来获取相应的数据。如何做呢,接下来,我们看一个实例,来说明一下这个过程。为了方便起见,以下内容以go-zero框架进行代码演示,首先来看rpc层面的代码
我们首先创建一个demo rpc
syntax = "proto3";
package demo;
option go_package="./demo";
import "google/protobuf/any.proto";
message Request {
string ping = 1;
}
message Response {
google.protobuf.Any pong = 1;
}
service Demo {
rpc Ping(Request) returns(Response);
}
在proto文件中,我们定义的返回值结构体使用google.protobuf.Any的类型来承接任意类型的数据内容。
在gateway层,在api文件中,我们可以使用interface来承接rpc返回的数据内容
type Request {
Name string `path:"name,options=you|me"`
}
type Response {
Message interface{} `json:"message"`
}
service demo1-api {
@handler Demo1Handler
get /from/:name(Request) returns (Response)
}
看起来是不是觉得很简单,重点在于,这两种数据类型不能直接进行赋值,需要通过中间的数据转换,在rpc层面,需要进行以下处理
...
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/structpb"
...
structData, err := structpb.NewStruct(result)
if err != nil {
return nil, fmt.Errorf("failed to create struct: %v", err)
}
anyData, err := anypb.New(structData)
将任意类型的数据进行转换后,赋值给返回值结构体进行流转。
在gateway层面,需要将rpc返回的数据结构体进行解构处理
"google.golang.org/protobuf/types/known/structpb"
...
if result.Data != nil {
var structData structpb.Struct
if err := result.Data.UnmarshalTo(&structData); err != nil {
logx.Error("failed to unmarshal Any to Struct: ", err)
return nil, fmt.Errorf("failed to unmarshal Any to Struct: %v", err)
}
data = structData.AsMap()
}
这样就能达到直接透传不确定数据类型的数据给前端进行处理了,且数据在中间不做任何的数据结构的定义,对于后续的升级更加友好。
- 在复杂的yaml文件中如何将其转化为json格式的数据格式
这个问题的场景并不多见,我们可以先来看一个实例,进行说明
// Convert YAML to JSON while preserving comments
func yamlToJsonWithComments(yamlData []byte) ([]byte, error) {
// Step 1: Parse the YAML with comments
var node yaml.Node
err := yaml.Unmarshal(yamlData, &node)
if err != nil {
return nil, err
}
// Step 2: Convert YAML node structure to map
var jsonMap interface{}
err = node.Decode(&jsonMap)
if err != nil {
return nil, err
}
// Step 3: Marshal the JSON
jsonData, err := json.MarshalIndent(jsonMap, "", " ")
if err != nil {
return nil, err
}
return jsonData, nil
}
func main() {
yamlStr := `# Example YAML file for testing purposes, used in reading/writing files.
name: Jimmy
age: 29
relationships:
# 增加注释
parents:
# 增加注释2
- Sally # 增加注释1
- Robert # 增加注释3
# 增加注释4`
output, err := yamlToJsonWithComments([]byte(yamlStr))
if err != nil {
log.Fatalf("error parsing YAML: %v", err)
}
fmt.Println(string(output))
}
就是这种形式的字符串进行转换后,这样看是不是觉得好像也没什么,但是,实例中使用的是 "gopkg.in/yaml.v3"库,如果因为在老代码中,使用的是 "gopkg.in/yaml.v2",如果在进行转换的时候就会出现
2024/11/12 20:07:29 Warning: Unsupported key type map[interface {}]interface {}
这种类型的诡异错误,此时就需要进行特殊的转换,将yaml.Unmarshal后的数据在进行处理一下
func convert(i interface{}) interface{} {
switch x := i.(type) {
case map[interface{}]interface{}:
m2 := map[string]interface{}{}
for k, v := range x {
m2[k.(string)] = convert(v)
}
return m2
case []interface{}:
for i, v := range x {
x[i] = convert(v)
}
}
return i
}
然后再进行json.Marshal才能正常的进行编码。这可以说是yamlv2版本的一个问题,但是,v3版本可以说进行了很大的升级,对于注释也进行了全面的支持,比如上面实例中的注释,分为HeadComment,LineComment,FootComment三种注释,在进行结构解析时,可以根据需要进行获取。所以在有条件的情况下,尽量选择新版本的yaml库。