获取 Firestore 文档中嵌套字段的更新值

问题描述 投票:0回答:1

我正在尝试在 golang 中实现一个触发器(作为谷歌云函数),当更新 firestore 文档时会调用该触发器。我正在遵循此处提供的文档: https://cloud.google.com/functions/docs/calling/cloud-firestore

我的 firestore 文档如下:

{
  "aaBool": true,
  "aaInt": 1111,
  "aaStr": "Lorem",
  "map1": {
    "m1book": true,
    "m1int": 3333,
    "m1str": "m1Lorem"
  },
  "map2": {
    "map3": {
      "m3int": 888,
      "m3str": "m3Lorem"
    }
  }
}

我目前拥有的代码是:

// Package trigger_firestore gets fired whenever any insert/update event occurs in the default firestore database.
package trigger_firestore

import (
    "cloud.google.com/go/firestore"
    _ "encoding/json"
    firebase "firebase.google.com/go/v4"
    "fmt"
    "github.com/GoogleCloudPlatform/functions-framework-go/functions"
    "github.com/cloudevents/sdk-go/v2/event"
    "github.com/googleapis/google-cloudevents-go/cloud/firestoredata"
    "golang.org/x/net/context"
    "google.golang.org/protobuf/proto"
    "log"
    "os"
    "reflect"
    "strings"
)

// set the GOOGLE_CLOUD_PROJECT environment variable when deploying.
var (
    projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
)

// client is a Firestore client, reused between function invocations.
var client *firestore.Client

func init() {
    // Use the application default credentials.
    conf := &firebase.Config{ProjectID: projectID}

    // Use context.Background() because the app/client should persist across
    // invocations.
    ctx := context.Background()

    app, err := firebase.NewApp(ctx, conf)
    if err != nil {
        log.Fatalf("firebase.NewApp: %v", err)
    }

    client, err = app.Firestore(ctx)
    if err != nil {
        log.Fatalf("app.Firestore: %v", err)
    }

    // Register cloud event function
    functions.CloudEvent("ProcessTrigger", ProcessTrigger)
}

func ProcessTrigger(ctx context.Context, event event.Event) error {

    var data firestoredata.DocumentEventData
    if err := proto.Unmarshal(event.Data(), &data); err != nil {
        return fmt.Errorf("proto.Unmarshal: %w", err)
    }

    fullPath := strings.Split(data.GetValue().GetName(), "/documents/")[1]
    pathParts := strings.Split(fullPath, "/")
    doc := strings.Join(pathParts[1:], "/")
    log.Printf("Doc: %+v\n", doc)

    log.Printf("Function triggered by change to: %v\n", event.Source())
    log.Printf("Old value: %+v\n", data.GetOldValue())
    log.Printf("New value: %+v\n", data.GetValue())

    processUpdate(&data)

    return nil

}

func processUpdate(data *firestoredata.DocumentEventData) {
    updateMask := data.GetUpdateMask()
    log.Printf("Update Mask: %+v\n", updateMask)

    for _, fieldName := range updateMask.GetFieldPaths() {
        log.Printf("=================================")
        log.Printf("FieldName: %v", fieldName)
        log.Printf("FieldNameType: %v", reflect.TypeOf(fieldName))

        fieldValue, ok := data.GetValue().GetFields()[fieldName]
        if ok {
            log.Printf("FieldValue: %v", fieldValue)
            stringSlice := strings.Split(fieldValue.String(), ":")
            log.Println(stringSlice)
        }
    }
}

上面的代码适用于更新顶级非嵌套字段(前缀

aa
),我可以打印
fieldValue

但是,对于嵌套字段的更新(在

map1
map3
内),
ok
为 false。

如何获取嵌套字段的更新

fieldValue

生成的日志是:

2024/04/14 14:55:44 Doc: testdoc
2024/04/14 14:55:44 Function triggered by change to: //firestore.googleapis.com/projects/myproj/databases/(default)
2024/04/14 14:55:44 Old value: name:"projects/myproj/databases/(default)/documents/testcollection/testdoc" fields:{key:"aaBool" value:{boolean_value:true}} fields:{key:"aaInt" value:{integer_value:11111}} fields:{key:"aaStr" value:{string_value:"Lorem"}} fields:{key:"map1" value:{map_value:{fields:{key:"m1book" value:{boolean_value:true}} fields:{key:"m1int" value:{integer_value:3333}} fields:{key:"m1str" value:{string_value:"m1Lorem"}}}}} fields:{key:"map2" value:{map_value:{fields:{key:"map3" value:{map_value:{fields:{key:"m3int" value:{integer_value:888}} fields:{key:"m3str" value:{string_value:"m3Lorem"}}}}}}}} create_time:{seconds:1713104864 nanos:548214000} update_time:{seconds:1713106076 nanos:80031000}
2024/04/14 14:55:44 New value: name:"projects/myproj/databases/(default)/documents/testcollection/testdoc" fields:{key:"aaBool" value:{boolean_value:false}} fields:{key:"aaInt" value:{integer_value:222}} fields:{key:"aaStr" value:{string_value:"LoremIpsum"}} fields:{key:"map1" value:{map_value:{fields:{key:"m1book" value:{boolean_value:false}} fields:{key:"m1int" value:{integer_value:4444}} fields:{key:"m1str" value:{string_value:"m1LoremIpsum"}}}}} fields:{key:"map2" value:{map_value:{fields:{key:"map3" value:{map_value:{fields:{key:"m3int" value:{integer_value:999}} fields:{key:"m3str" value:{string_value:"m3LoremIpsum"}}}}}}}} create_time:{seconds:1713104864 nanos:548214000} update_time:{seconds:1713106543 nanos:26415000}
2024/04/14 14:55:44 Update Mask: field_paths:"map2.map3.m3int" field_paths:"map2.map3.m3str" field_paths:"map1.m1str" field_paths:"map1.m1int" field_paths:"map1.m1book" field_paths:"aaStr" field_paths:"aaBool" field_paths:"aaInt"
2024/04/14 14:55:44 =================================
2024/04/14 14:55:44 FieldName: map2.map3.m3int
2024/04/14 14:55:44 FieldNameType: string
2024/04/14 14:55:44 =================================
2024/04/14 14:55:44 FieldName: map2.map3.m3str
2024/04/14 14:55:44 FieldNameType: string
2024/04/14 14:55:44 =================================
2024/04/14 14:55:44 FieldName: map1.m1str
2024/04/14 14:55:44 FieldNameType: string
2024/04/14 14:55:44 =================================
2024/04/14 14:55:44 FieldName: map1.m1int
2024/04/14 14:55:44 FieldNameType: string
2024/04/14 14:55:44 =================================
2024/04/14 14:55:44 FieldName: map1.m1book
2024/04/14 14:55:44 FieldNameType: string
2024/04/14 14:55:44 =================================
2024/04/14 14:55:44 FieldName: aaStr
2024/04/14 14:55:44 FieldNameType: string
2024/04/14 14:55:44 FieldValue: string_value:"LoremIpsum"
2024/04/14 14:55:44 [string_value "LoremIpsum"]
2024/04/14 14:55:44 =================================
2024/04/14 14:55:44 FieldName: aaBool
2024/04/14 14:55:44 FieldNameType: string
2024/04/14 14:55:44 FieldValue: boolean_value:false
2024/04/14 14:55:44 [boolean_value false]
2024/04/14 14:55:44 =================================
2024/04/14 14:55:44 FieldName: aaInt
2024/04/14 14:55:44 FieldNameType: string
2024/04/14 14:55:44 FieldValue: integer_value:222
2024/04/14 14:55:44 [integer_value 222]

如果需要,这就是我部署触发器的方式:

gcloud functions deploy test-trigger-1 \
    --gen2 \
    --runtime=go122 \
    --region=us-west1 \
    --trigger-location=us-west1 \
    --source=. \
    --entry-point=ProcessTrigger \
    --min-instances=1 \
    --memory=256Mi \
    --set-env-vars=[GOOGLE_CLOUD_PROJECT=myproj] \
    --trigger-event-filters=type=google.cloud.firestore.document.v1.written \
    --trigger-event-filters=database='(default)' \
    --trigger-event-filters-path-pattern=document='testcollection/{docID}'
go google-cloud-firestore google-cloud-functions
1个回答
0
投票

这里的讨论来看,谷歌似乎没有提供任何直接的方法来访问嵌套字段,也无意这样做。出于纯粹的挫败感,该讨论线程中的一些个人贡献者已经实施并发布了解决方案。我测试了其中一个并且它有效。我将这个解决方案粘贴在下面。将使用它直到我找到更好的东西。


func parseFirestoreDocument(value *firestoredata.Document) map[string]interface{} {
    if value == nil {
        return nil
    }

    fields := value.GetFields()

    if fields == nil {
        return nil
    }

    result := make(map[string]interface{})

    for k, v := range fields {
        result[k] = parseValue(v)
    }

    return result
}

func parseValue(value *firestoredata.Value) interface{} {
    if value == nil {
        return nil
    }

    switch value.ValueType.(type) {
    case *firestoredata.Value_NullValue:
        return nil
    case *firestoredata.Value_BooleanValue:
        return value.GetBooleanValue()
    case *firestoredata.Value_IntegerValue:
        return value.GetIntegerValue()
    case *firestoredata.Value_DoubleValue:
        return value.GetDoubleValue()
    case *firestoredata.Value_StringValue:
        return value.GetStringValue()
    case *firestoredata.Value_TimestampValue:
        return value.GetTimestampValue()
    case *firestoredata.Value_GeoPointValue:
        return value.GetGeoPointValue()
    case *firestoredata.Value_BytesValue:
        return value.GetBytesValue()
    case *firestoredata.Value_ReferenceValue:
        return value.GetReferenceValue()
    case *firestoredata.Value_ArrayValue:
        return parseArray(value.GetArrayValue())
    case *firestoredata.Value_MapValue:
        return parseMap(value.GetMapValue())
    default:
        return nil
    }
}

func parseMap(value *firestoredata.MapValue) interface{} {
    if value == nil {
        return nil
    }

    fields := value.GetFields()

    if fields == nil {
        return nil
    }

    result := make(map[string]interface{})

    for k, v := range fields {
        result[k] = parseValue(v)
    }

    return result
}

func parseArray(value *firestoredata.ArrayValue) interface{} {
    if value == nil {
        return nil
    }

    values := value.GetValues()

    if values == nil {
        return nil
    }

    result := make([]interface{}, len(values))

    for i, v := range values {
        result[i] = parseValue(v)
    }

    return result
}

我使用这个,它也适用于深度嵌套的字段。由于 Firebase 类型有限,我们可以根据需要轻松切换和递归解析它们。

取自:https://github.com/googleapis/google-cloud-go/issues/1438

© www.soinside.com 2019 - 2024. All rights reserved.