使用golang将数据快速以json格式插入到Firestore的故事

首先

我正在玩一款名为魔法少女的手机游戏,随着魔法少女的数量增加,搜索变得越来越困难。
由于游戏内的搜索功能有一些不完善的地方,为了兼顾学习,我开始使用Firebase和Nuxt.js实现了一个可以通过各种方式搜索魔法少女列表的功能。

在准备Firestore的主数据时,我考虑通过管理界面输入数据,但这显然太麻烦了,而且维护也很困难。
考虑到在制作过程中已经创建了用于模拟的JSON文件,所以我决定直接将JSON保存到Firestore中。

因为平时对Golang有兴趣,我试着使用Golang编写了一个可以读取JSON并将其插入或更新到Firestore的应用程序,一切都以JSON为主。
(怎么实现呢?当然是用Go语言写啦!!(仿Mr. Parker Jr的口吻))

在本文中,投入指的是将数据插入或更新到Firestore中。

预谋之物

假设能达到以下状态。

    • Firebaseでプロジェクトを作成し、Firestore Databaseまでは作成している

 

    golangが導入済みである(筆者環境は12.6で、動作を確認しています)

准备json文件

原始数据

魔法少女們擁有以下資訊(為了說明而部分省略)

    • Key(データを探索するときに使用、Firestoreではドキュメント名にも割当します。)

 

    • 名前

 

    • 属性(火、水、木、光、闇、無)

 

    • タイプ(アタック、ディフェンス、バランス、ヒールなど)

 

    HP、攻撃力、防御力といったステータス

json数据

根据魔法少女的数据创建 JSON。虽然示例只有一个人,但实际上我们要注册多个人,所以将其放入一个数组中。

[
  {
    "key" : "kaname madoka",
    "name" : "鹿目 まどか",
    "attribute" : "光",
    "type" : "ヒール",
    "status" : {
      "hp" : 24336,
      "attack": 6832,
      "defense" : 9276
    }
  }
] 

准备Firestore

スクリーンショット 2020-06-10 1.22.38.png

有关Firestore的结构

这之后将会向Firestore中插入数据,确定将文档放置在哪个位置以及如何命名文档是处理Firestore时的一个关键点。本次将按以下结构进行创建。

    • jsonの中身は’private/v1/magicalGirls’に配置する

 

    ドキュメント名はKey名と同一とする

为了使配置更容易在正式运行时设置安全规则,并且为了在升级版本时能够进行适配,我也还在摸索中,如果有任何建议,请留言。

将文档名称与键名相同是为了方便搜索和更新作为主数据。
如果是交易,可以自动分配文档名称,但主数据更容易发生更改,每次更改后搜索文档名称效率较低。
此外,由于golang端也根据文档名称进行插入和更新操作,因此文档名称与键名相同。

使用Golang将数据插入Firestore。

将JSON数据转换为Golang的类型

首先,我们需要在golang端准备好能够读取所需数据的设定。

在以下網站上,可以根據JSON文件即時轉換成任何語言。快速解析JSON。

这个画面分为左、中、右三个窗格。
在左侧窗格中,输入golang类型名称(由于是数组,可以输入多个名称)到Name。
在左侧窗格中央,输入创建的json。
在右侧窗格中,选择要转换的目标语言,选择Go。
选择后,golang源代码将在中间窗格中生成。

スクリーンショット 2020-06-10 1.12.59.png

获取Firestore的连接密钥

要想将数据投入Firestore,需要使用与服务帐户关联的包含秘密密钥的JSON数据。获取并不困难,但由于它是秘密密钥,绝不能公开。不能在git或其他地方管理它。

スクリーンショット 2020-06-10 1.32.52.png
スクリーンショット 2020-06-10 1.34.27.png
スクリーンショット 2020-06-10 1.35.37.png
スクリーンショット 2020-06-10 1.38.07.png

使用Golang连接到Firestore

我想您应该已经注意到了,在生成密钥时,示例源代码中包含有golang的代码。这个示例源代码是关于Firestore的身份验证部分。我们将根据这个示例源代码创建身份验证部分。

首先,我们需要安装Firebase库。

go get -u firebase.google.com/go

对Firebase的身份验证

首先进行Firebase的认证。
创建main.go文件,将示例代码放在main函数中。
若发生错误,将显示错误消息并结束程序。

package main

import (
    "context"
    "fmt"

    "google.golang.org/api/option"

    firebase "firebase.google.com/go"
)

func main() {
    // Use a service account
    ctx := context.Background()
    sa := option.WithCredentialsFile("path/to/serviceAccountKey.json")
    app, err := firebase.NewApp(ctx, nil, sa)
    if err != nil {
        fmt.Printf("error initializing app: %v", err)
        return 
    }
}

连接到Firestore

在main函数中添加一个连接到Firestore的客户端。

    client, err := app.Firestore(ctx)
    if err != nil {
        fmt.Printf("error create Firestore client: %v", err)
        return
    }
    defer client.Close()

准备要放入Firestore的数据

将生成的将 JSON 数据转换为 Golang 类型的源代码粘贴到 main.go 文件中。

package main

import (
    "context"
    "encoding/json"
    "fmt"

    "google.golang.org/api/option"

    firebase "firebase.google.com/go"
)

type MagicalGirls []MagicalGirl

func UnmarshalMagicalGirls(data []byte) (MagicalGirls, error) {
    var r MagicalGirls
    err := json.Unmarshal(data, &r)
    return r, err
}

func (r *MagicalGirls) Marshal() ([]byte, error) {
    return json.Marshal(r)
}

type MagicalGirl struct {
    Key       string `json:"key"`
    Name      string `json:"name"`
    Attribute string `json:"attribute"`
    Type      string `json:"type"`
    Status    Status `json:"status"`
}

type Status struct {
    HP      int64 `json:"hp"`
    Attack  int64 `json:"attack"`
    Defense int64 `json:"defense"`
}

func main() {
.....

将数据导入到Firestore中。

使用Firestore客户端加载和创建的json文件,实现将数据插入的操作。数据将被插入到Firestore准备好的’private/v1/magicalGirls’路径中,以文档名称作为键名。

import (
...
    "io/ioutil"
...
)

...

    // load json file
    bytes, err := ioutil.ReadFile("path/to/magicalGirls.json")
    if err != nil {
        fmt.Printf("error load magicalGirls.json : %v", err)
    }
    magicalGirls, err := UnmarshalMagicalGirls(bytes)
    if err != nil {
        fmt.Printf("error unmarshal magicalGirls.json : %v", err)
    }

    for i := range magicalGirls {
        _, err = client.Collection("private/v1/magicalGirls").Doc(magicalGirls[i].Key).Set(ctx, magicalGirls[i])
        if err != nil {
            fmt.Printf("error adding magicalGirl: %v", err)
            return
        }
    }

确认投入的数据

在输入数据之后,获取集合中的所有文档并确认是否已注册。
虽然可以在Firebase界面上进行确认,但既然已经开始了,还是从golang端进行获取吧。
通过使用迭代器,可以获取和确认Firestore的数据。

import(
...
    "google.golang.org/api/iterator"
...
)

...
    iter := client.Collection("private/v1/magicalGirls").Documents(ctx)
    for {
        doc, err := iter.Next()
        if err == iterator.Done {
            break
        }
        if err != nil {
            fmt.Printf("error get magicalGirl documents : %v", err)
        }
        fmt.Println(doc.Data())
    }

创建的 main.go

以下是创建的 main.go。

为了连接 Firebase,我们将秘钥从外部接收。

package main

import (
    "context"
    "encoding/json"
    "flag"
    "fmt"
    "io/ioutil"
    "os"

    "google.golang.org/api/iterator"
    "google.golang.org/api/option"

    firebase "firebase.google.com/go"
)

type MagicalGirls []MagicalGirl

func UnmarshalMagicalGirls(data []byte) (MagicalGirls, error) {
    var r MagicalGirls
    err := json.Unmarshal(data, &r)
    return r, err
}

func (r *MagicalGirls) Marshal() ([]byte, error) {
    return json.Marshal(r)
}

type MagicalGirl struct {
    Key       string `json:"key"`
    Name      string `json:"name"`
    Attribute string `json:"attribute"`
    Type      string `json:"type"`
    Status    Status `json:"status"`
}

type Status struct {
    HP      int64 `json:"hp"`
    Attack  int64 `json:"attack"`
    Defense int64 `json:"defense"`
}

func main() {
    flag.Parse()
    if flag.NArg() != 1 {
        fmt.Println("firebase key file not set for Args")
        return
    }
    args := flag.Args()
    file := args[0]
    _, err := os.Stat(file)
    if err != nil {
        fmt.Println("error : firebase key file not found")
        return
    }

    // Use a service account
    ctx := context.Background()
    sa := option.WithCredentialsFile(file)
    app, err := firebase.NewApp(ctx, nil, sa)
    if err != nil {
        fmt.Printf("error initializing app: %v", err)
        return
    }

    client, err := app.Firestore(ctx)
    if err != nil {
        fmt.Printf("error create Firestore client: %v", err)
        return
    }
    defer client.Close()

    // load json file
    bytes, err := ioutil.ReadFile("path/to/magicalGirls.json")
    if err != nil {
        fmt.Printf("error load magicalGirls.json : %v", err)
    }
    magicalGirls, err := UnmarshalMagicalGirls(bytes)
    if err != nil {
        fmt.Printf("error unmarshal magicalGirls.json : %v", err)
    }

    for i := range magicalGirls {
        _, err = client.Collection("private/v1/magicalGirls").Doc(magicalGirls[i].Key).Set(ctx, magicalGirls[i])
        if err != nil {
            fmt.Printf("error adding magicalGirl: %v", err)
            return
        }
    }

    iter := client.Collection("private/v1/magicalGirls").Documents(ctx)
    for {
        doc, err := iter.Next()
        if err == iterator.Done {
            break
        }
        if err != nil {
            fmt.Printf("error get magicalGirl documents : %v", err)
        }
        fmt.Println(doc.Data())
    }
}

我們試著實際輸入數據。

在执行之前,我会做一个go get操作。(将GO111MODULE设置为on。)

go get
go ran main.go firebasekeyfile.json
スクリーンショット 2020-06-11 1.22.22.png

通过Firebase界面查看Firestore时,显示如下:
它会自动为私有(private)和v1等集合(collection),以及文档(document)生成。

スクリーンショット 2020-06-11 1.30.34.png

最後

我认为很少有机会首次输入数据到JSON中。
但是,我认为能够在没有界面的情况下进行主数据的输入和维护,并且能够进行批量操作,比我想象中的更加方便。
我认为在处理非事务性数据的网站(如列表和计算工具等)中,可能会有一些用途。

额外赠品

由于可以通过JSON插入数据,所以我试了很多东西,感觉很有趣。我将其留作我的备忘录。

    • ドキュメント名にはスペースを含めることが可能

 

    • ドキュメント名に日本語を使ってもOK

 

    ドキュメント名のみで、ドキュメントの中身がないものは生成できない

请提供更多的上下文。

在制作这个之前,我参考了很多资料。非常感谢。
使用Golang将数据写入Firebase的实时数据库 – Qiita
终于登场了Firebase Admin SDK Go! – Qiita

广告
将在 10 秒后关闭
bannerAds