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