如何使用MongoDBGo驱动程序使用Go和MongoDB

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 自由软件基金会 来接受捐赠。

介绍

在多年依赖社区开发的解决方案后,MongoDB 宣布 正在为 Go 开发官方驱动程序。 2019 年 3 月,随着 v1.0.0 的发布,这个新的驱动程序达到了生产就绪状态 ,并从那时起不断更新。

与其他官方 MongoDB 驱动程序一样,Go 驱动程序 是 Go 编程语言的惯用语,并提供了一种简单的方法来使用 MongoDB 作为 Go 程序的数据库解决方案。 它与 MongoDB API 完全集成,并公开了 API 的所有查询、索引和聚合功能,以及其他高级功能。 与第三方库不同的是,它将由 MongoDB 工程师全力支持,因此您可以放心它的持续开发和维护。

在本教程中,您将开始使用官方的 MongoDB Go 驱动程序。 您将安装驱动程序、连接到 MongoDB 数据库并执行多个 CRUD 操作。 在此过程中,您将创建一个任务管理器程序,用于通过命令行管理任务。

先决条件

对于本教程,您将需要以下内容:

  • 在您的机器上安装 Go,并按照 如何安装 Go 和设置本地编程环境 配置 Go 工作区。 在本教程中,项目将命名为 tasker。 您需要在启用 Go Modules 的机器上安装 Go v1.11 或更高版本。
  • 按照 如何安装 MongoDB 为您的操作系统安装 MongoDB。 MongoDB 2.6 或更高版本是 MongoDB Go 驱动程序支持的最低版本。

如果您使用的是 Go v1.11 或 1.12,请确保通过将 GO111MODULE 环境变量设置为 on 来启用 Go Modules,如下所示:

export GO111MODULE="on"

有关实现环境变量的更多信息,请阅读有关 如何读取和设置环境变量和 Shell 变量 的教程。

本指南中显示的命令和代码已使用 Go v1.14.1 和 MongoDB v3.6.3 进行了测试。

第 1 步 — 安装 MongoDB Go 驱动程序

在这一步中,您将为 MongoDB 安装 Go Driver 包并将其导入您的项目。 您还将连接到 MongoDB 数据库并检查连接状态。

继续在您的文件系统中为本教程创建一个新目录:

mkdir tasker

设置项目目录后,使用以下命令更改为该目录:

cd tasker

接下来,使用 go.mod 文件初始化 Go 项目。 此文件定义项目要求并将依赖项锁定到其正确版本:

go mod init

如果您的项目目录在 $GOPATH 之外,则需要为您的模块指定导入路径,如下所示:

go mod init github.com/<your_username>/tasker

此时,您的 go.mod 文件将如下所示:

去.mod

module github.com/<your_username>/tasker

go 1.14

使用以下命令将 MongoDB Go 驱动程序添加为项目的依赖项:

go get go.mongodb.org/mongo-driver

您将看到如下输出:

Outputgo: downloading go.mongodb.org/mongo-driver v1.3.2
go: go.mongodb.org/mongo-driver upgrade => v1.3.2

此时,您的 go.mod 文件将如下所示:

去.mod

module github.com/<your_username>/tasker

go 1.14

require go.mongodb.org/mongo-driver v1.3.1 // indirect

接下来,在项目根目录中创建一个 main.go 文件并在文本编辑器中打开它:

nano main.go

要开始使用驱动程序,请将以下包导入您的 main.go 文件:

main.go

package main

import (
    "context"
    "log"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

在这里,您添加了 MongoDB Go 驱动程序提供的 mongooptions 包。

接下来,在导入之后,创建一个新的 MongoDB 客户端并连接到正在运行的 MongoDB 服务器:

main.go

. . .
var collection *mongo.Collection
var ctx = context.TODO()

func init() {
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
    client, err := mongo.Connect(ctx, clientOptions)
    if err != nil {
        log.Fatal(err)
    }
}

mongo.Connect() 接受 Contextoptions.ClientOptions 对象,用于设置连接字符串和其他驱动程序设置。 您可以访问 选项包文档 以查看可用的配置选项。

Context 类似于超时或截止日期,指示操作何时应停止运行并返回。 当特定操作运行缓慢时,它有助于防止生产系统的性能下降。 在此代码中,您传递 context.TODO() 表示您不确定现在要使用什么上下文,但您计划在未来添加一个。

接下来,让我们确保使用 Ping 方法找到并成功连接到您的 MongoDB 服务器。 在 init 函数中添加以下代码:

main.go

. . .
    log.Fatal(err)
  }

  err = client.Ping(ctx, nil)
  if err != nil {
    log.Fatal(err)
  }
}

如果在连接到数据库时出现任何错误,那么在您尝试修复问题时程序应该会崩溃,因为如果没有活动的数据库连接,保持程序运行是没有意义的。

添加以下代码以创建数据库:

main.go

. . .
  err = client.Ping(ctx, nil)
  if err != nil {
    log.Fatal(err)
  }

  collection = client.Database("tasker").Collection("tasks")
}

您创建一个 tasker 数据库和一个 task 集合来存储您将要创建的任务。 您还将 collection 设置为包级变量,以便您可以在整个包中重用数据库连接。

保存并退出文件。

此时完整的main.go如下:

main.go

package main

import (
    "context"
    "log"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

var collection *mongo.Collection
var ctx = context.TODO()

func init() {
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
    client, err := mongo.Connect(ctx, clientOptions)
    if err != nil {
        log.Fatal(err)
    }

    err = client.Ping(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    collection = client.Database("tasker").Collection("tasks")
}

您已将程序设置为使用 Go 驱动程序连接到 MongoDB 服务器。 在下一步中,您将继续创建任务管理器程序。

第 2 步 — 创建 CLI 程序

在此步骤中,您将安装众所周知的 cli 包以帮助开发任务管理器程序。 它提供了一个界面,您可以利用该界面快速创建现代命令行工具。 例如,这个包提供了为你的程序定义子命令的能力,以获得更像 git 的命令行体验。

运行以下命令将包添加为依赖项:

go get github.com/urfave/cli/v2

接下来,再次打开您的 main.go 文件:

nano main.go

将以下突出显示的代码添加到您的 main.go 文件中:

main.go

package main

import (
    "context"
    "log"
    "os"

    "github.com/urfave/cli/v2"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)
. . .

如前所述,您导入 cli 包。 您还导入了 os 包,您将使用它来将命令行参数传递给您的程序:

init 函数之后添加以下代码以创建 CLI 程序并编译代码:

main.go

. . .
func main() {
    app := &cli.App{
        Name:     "tasker",
        Usage:    "A simple CLI program to manage your tasks",
        Commands: []*cli.Command{},
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

该片段创建了一个名为 tasker 的 CLI 程序,并添加了一个简短的使用说明,当您运行该程序时将打印出来。 Commands 切片是您为程序添加命令的地方。 Run 命令将参数切片解析为适当的命令。

保存并退出您的文件。

这是构建和运行程序所需的命令:

go run main.go

您将看到以下输出:

OutputNAME:
   tasker - A simple CLI program to manage your tasks

USAGE:
   main [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help (default: false)

该程序运行并显示帮助文本,这有助于了解该程序可以做什么以及如何使用它。

在接下来的步骤中,您将通过添加子命令来帮助管理 MongoDB 中的任务,从而提高程序的实用性。

第 3 步 — 创建任务

在这一步中,您将使用 cli 包向 CLI 程序添加一个子命令。 在本节结束时,您将能够通过在 CLI 程序中使用新的 add 命令将新任务添加到 MongoDB 数据库。

首先打开您的 main.go 文件:

nano main.go

接下来,导入 go.mongodb.org/mongo-driver/bson/primitivetimeerrors 包:

main.go

package main

import (
    "context"
    "errors"
    "log"
    "os"
    "time"

    "github.com/urfave/cli/v2"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)
. . .

然后创建一个新结构来表示数据库中的单个任务,并将其插入到 main 函数之前:

main.go

. . .
type Task struct {
    ID        primitive.ObjectID `bson:"_id"`
    CreatedAt time.Time          `bson:"created_at"`
    UpdatedAt time.Time          `bson:"updated_at"`
    Text      string             `bson:"text"`
    Completed bool               `bson:"completed"`
}
. . .

您使用 primitive 包来设置每个任务的 ID 的类型,因为 MongoDB 默认使用 ObjectIDs 作为 _id 字段。 MongoDB 的另一个默认行为是,小写的字段名称在被序列化时用作每个导出字段的键,但这可以使用 bson 结构标签进行更改。

接下来,创建一个接收 Task 实例并将其保存在数据库中的函数。 在 main 函数之后添加此代码段:

main.go

. . .
func createTask(task *Task) error {
    _, err := collection.InsertOne(ctx, task)
  return err
}
. . .

collection.InsertOne() 方法将提供的任务插入到数据库集合中,并返回插入的文档的 ID。 由于您不需要此 ID,因此您可以通过分配给下划线运算符来丢弃它。

下一步是向您的任务管理器程序添加一个新命令以创建新任务。 我们称它为 add

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
        },
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

添加到 CLI 程序中的每个新命令都放置在 Commands 切片中。 每一个都由名称、使用说明和操作组成。 这是将在命令执行时运行的代码。

在此代码中,您收集 add 的第一个参数并使用它来设置新 Task 实例的 Text 属性,同时为其他属性分配适当的默认值。 新任务随后被传递给 createTask,它将任务插入数据库并返回 nil 如果一切顺利导致命令退出。

保存并退出您的文件。

通过使用 add 命令添加一些任务来测试它。 如果成功,您将不会看到任何错误打印到您的屏幕上:

go run main.go add "Learn Go"
go run main.go add "Read a book"

现在您可以成功添加任务,让我们实现一种方法来显示您已添加到数据库中的所有任务。

第 4 步 — 列出所有任务

可以使用 collection.Find() 方法在集合中列出文档,该方法需要一个过滤器以及一个指向可以解码结果的值的指针。 它的返回值是 Cursor,它提供了一个文档流,可以一次迭代和解码一个。 游标在用完后关闭。

打开您的 main.go 文件:

nano main.go

确保导入 bson 包:

main.go

package main

import (
    "context"
    "errors"
    "log"
    "os"
    "time"

    "github.com/urfave/cli/v2"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)
. . .

然后在 createTask 之后立即创建以下函数:

main.go

. . .
func getAll() ([]*Task, error) {
  // passing bson.D{{}} matches all documents in the collection
    filter := bson.D{{}}
    return filterTasks(filter)
}

func filterTasks(filter interface{}) ([]*Task, error) {
    // A slice of tasks for storing the decoded documents
    var tasks []*Task

    cur, err := collection.Find(ctx, filter)
    if err != nil {
        return tasks, err
    }

    for cur.Next(ctx) {
        var t Task
        err := cur.Decode(&t)
        if err != nil {
            return tasks, err
        }

        tasks = append(tasks, &t)
    }

    if err := cur.Err(); err != nil {
        return tasks, err
    }

  // once exhausted, close the cursor
    cur.Close(ctx)

    if len(tasks) == 0 {
        return tasks, mongo.ErrNoDocuments
    }

    return tasks, nil
}

BSON(二进制编码 JSON) 是文档在 MongoDB 数据库中的表示方式,bson 包帮助我们在 Go 中使用 BSON 对象。 getAll() 函数中使用的 bson.D 类型表示 BSON 文档,它用于属性顺序重要的地方。 通过将 bson.D{{}} 作为过滤器传递给 filterTasks(),您表示要匹配集合中的所有文档。

filterTasks() 函数中,您遍历 collection.Find() 方法返回的 Cursor 并将每个文档解码为 Task 的实例。 然后将每个 Task 附加到函数开始时创建的任务切片。 一旦游标用尽,它就会关闭并返回 tasks 切片。

在创建列出所有任务的命令之前,让我们创建一个辅助函数,该函数获取 tasks 的切片并打印到标准输出。 您将使用 color 包对输出进行着色。

在你可以使用这个包之前,安装它:

go get gopkg.in/gookit/color.v1

您将看到以下输出:

Outputgo: downloading gopkg.in/gookit/color.v1 v1.1.6
go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

并将其与 fmt 包一起导入到您的 main.go 文件中:

main.go

package main

import (
    "context"
    "errors"
  "fmt"
    "log"
    "os"
    "time"

    "github.com/urfave/cli/v2"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "gopkg.in/gookit/color.v1"
)
. . .

接下来,在 main 函数之后创建一个新的 printTasks 函数:

main.go

. . .
func printTasks(tasks []*Task) {
    for i, v := range tasks {
        if v.Completed {
            color.Green.Printf("%d: %s\n", i+1, v.Text)
        } else {
            color.Yellow.Printf("%d: %s\n", i+1, v.Text)
        }
    }
}
. . .

这个 printTasks 函数获取 tasks 的切片,对每个切片进行迭代,并将其打印到标准输出,使用绿色表示已完成的任务,黄色表示未完成的任务。

继续添加以下突出显示的行,为 Commands 切片创建一个新的 all 命令。 此命令会将所有添加的任务打印到标准输出:

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
            {
                Name:    "all",
                Aliases: []string{"l"},
                Usage:   "list all tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getAll()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
        },
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

. . .

all 命令检索数据库中存在的所有任务并将它们打印到标准输出。 如果不存在任何任务,则会打印添加新任务的提示。

保存并退出您的文件。

使用 all 命令构建并运行您的程序:

go run main.go all

它将列出您迄今为止添加的所有任务:

Output1: Learn Go
2: Read a book

现在您可以查看数据库中的所有任务,让我们在下一步中添加将任务标记为已完成的功能。

第 5 步 — 完成任务

在此步骤中,您将创建一个名为 done 的新子命令,它允许您将数据库中的现有任务标记为已完成。 要将任务标记为已完成,您可以使用 collection.FindOneAndUpdate() 方法。 它允许您在集合中定位文档并更新其部分或全部属性。 这种方法需要一个过滤器来定位文档和一个更新文档来描述操作。 这两个都是使用 bson.D 类型构建的。

首先打开您的 main.go 文件:

nano main.go

filterTasks 函数之后插入以下代码段:

main.go

. . .
func completeTask(text string) error {
    filter := bson.D{primitive.E{Key: "text", Value: text}}

    update := bson.D{primitive.E{Key: "$set", Value: bson.D{
        primitive.E{Key: "completed", Value: true},
    }}}

    t := &Task{}
    return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
}
. . .

该函数匹配文本属性等于 text 参数的第一个文档。 update 文档指定将 completed 属性设置为 true。 如果 FindOneAndUpdate() 操作出错,将由 completeTask() 返回。 否则返回 nil

接下来,让我们在 CLI 程序中添加一个新的 done 命令,将任务标记为已完成:

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
            {
                Name:    "all",
                Aliases: []string{"l"},
                Usage:   "list all tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getAll()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
            {
                Name:    "done",
                Aliases: []string{"d"},
                Usage:   "complete a task on the list",
                Action: func(c *cli.Context) error {
                    text := c.Args().First()
                    return completeTask(text)
                },
            },
        },
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

. . .

您使用传递给 done 命令的参数来查找其 text 属性匹配的第一个文档。 如果找到,文档上的 completed 属性设置为 true

保存并退出您的文件。

然后使用 done 命令运行您的程序:

go run main.go done "Learn Go"

如果再次使用 all 命令,您会注意到标记为已完成的任务现在打印为绿色。

go run main.go all

有时,您只想查看尚未完成的任务。 接下来我们将添加该功能。

第 6 步 — 仅显示待处理任务

在此步骤中,您将合并代码以使用 MongoDB 驱动程序从数据库中检索挂起的任务。 待处理任务是那些 completed 属性设置为 false 的任务。

让我们添加一个新函数来检索尚未完成的任务。 打开您的 main.go 文件:

nano main.go

然后在 completeTask 函数之后添加此代码段:

main.go

. . .
func getPending() ([]*Task, error) {
    filter := bson.D{
        primitive.E{Key: "completed", Value: false},
    }

    return filterTasks(filter)
}
. . .

您使用 MongoDB 驱动程序中的 bsonprimitive 包创建过滤器,它将匹配 completed 属性设置为 false 的文档。 然后将待处理任务的切片返回给调用者。

与其创建一个新的命令来列出待处理的任务,不如让它成为在没有任何命令的情况下运行程序时的默认操作。 您可以通过向程序中添加 Action 属性来执行此操作,如下所示:

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Action: func(c *cli.Context) error {
            tasks, err := getPending()
            if err != nil {
                if err == mongo.ErrNoDocuments {
                    fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                    return nil
                }

                return err
            }

            printTasks(tasks)
            return nil
        },
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
. . .

当程序在没有任何子命令的情况下执行时,Action 属性执行默认操作。 这是列出待处理任务的逻辑的地方。 调用 getPending() 函数并使用 printTasks() 将生成的任务打印到标准输出。 如果没有待处理的任务,则会显示提示,鼓励用户使用 add 命令添加新任务。

保存并退出您的文件。

现在运行程序而不添加任何命令将列出数据库中所有待处理的任务:

go run main.go

您将看到以下输出:

Output1: Read a book

现在您可以列出未完成的任务,让我们添加另一个允许您仅查看已完成任务的命令。

第 7 步 - 显示已完成的任务

在这一步中,您将添加一个新的 finished 子命令,它从数据库中获取已完成的任务并将它们显示在屏幕上。 这涉及过滤和返回 completed 属性设置为 true 的任务。

打开您的 main.go 文件:

nano main.go

然后在文件末尾添加以下代码:

main.go

. . .
func getFinished() ([]*Task, error) {
    filter := bson.D{
        primitive.E{Key: "completed", Value: true},
    }

    return filterTasks(filter)
}
. . .

getPending() 函数类似,您添加了一个 getFinished() 函数,该函数返回已完成任务的切片。 在这种情况下,过滤器将 completed 属性设置为 true,因此只会返回符合此条件的文档。

接下来,创建一个打印所有已完成任务的 finished 命令:

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Action: func(c *cli.Context) error {
            tasks, err := getPending()
            if err != nil {
                if err == mongo.ErrNoDocuments {
                    fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                    return nil
                }

                return err
            }

            printTasks(tasks)
            return nil
        },
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
            {
                Name:    "all",
                Aliases: []string{"l"},
                Usage:   "list all tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getAll()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
            {
                Name:    "done",
                Aliases: []string{"d"},
                Usage:   "complete a task on the list",
                Action: func(c *cli.Context) error {
                    text := c.Args().First()
                    return completeTask(text)
                },
            },
            {
                Name:    "finished",
                Aliases: []string{"f"},
                Usage:   "list completed tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getFinished()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `done 'task'` to complete a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
        }
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}
. . .

finished 命令通过此处创建的 getFinished() 函数检索 completed 属性设置为 true 的任务。 然后它将它传递给 printTasks 函数,以便将它们打印到标准输出。

保存并退出您的文件。

运行以下命令:

go run main.go finished

您将看到以下输出:

Output1: Learn Go

在最后一步,您将为用户提供从数据库中删除任务的选项。

第 8 步 — 删除任务

在此步骤中,您将添加一个新的 delete 子命令,以允许用户从数据库中删除任务。 要删除单个任务,您将使用 MongoDB 驱动程序中的 collection.DeleteOne() 方法。 它还依赖过滤器来匹配要删除的文档。

再次打开 main.go 文件:

nano main.go

添加此 deleteTask 函数以在 getFinished 函数之后直接从数据库中删除任务:

main.go

. . .
func deleteTask(text string) error {
    filter := bson.D{primitive.E{Key: "text", Value: text}}

    res, err := collection.DeleteOne(ctx, filter)
    if err != nil {
        return err
    }

    if res.DeletedCount == 0 {
        return errors.New("No tasks were deleted")
    }

    return nil
}
. . .

deleteTask 方法采用一个字符串参数,该参数表示要删除的任务项。 构造一个过滤器以匹配其 text 属性设置为字符串参数的任务项。 您将过滤器传递给与集合中的项目匹配的 DeleteOne() 方法并将其删除。

您可以检查 DeleteOne 方法的结果中的 DeletedCount 属性,以确认是否删除了任何文档。 如果过滤器无法匹配要删除的文档,则 DeletedCount 将为零,在这种情况下您可以返回错误。

现在添加一个新的 rm 命令,突出显示:

main.go

. . .
func main() {
    app := &cli.App{
        Name:  "tasker",
        Usage: "A simple CLI program to manage your tasks",
        Action: func(c *cli.Context) error {
            tasks, err := getPending()
            if err != nil {
                if err == mongo.ErrNoDocuments {
                    fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                    return nil
                }

                return err
            }

            printTasks(tasks)
            return nil
        },
        Commands: []*cli.Command{
            {
                Name:    "add",
                Aliases: []string{"a"},
                Usage:   "add a task to the list",
                Action: func(c *cli.Context) error {
                    str := c.Args().First()
                    if str == "" {
                        return errors.New("Cannot add an empty task")
                    }

                    task := &Task{
                        ID:        primitive.NewObjectID(),
                        CreatedAt: time.Now(),
                        UpdatedAt: time.Now(),
                        Text:      str,
                        Completed: false,
                    }

                    return createTask(task)
                },
            },
            {
                Name:    "all",
                Aliases: []string{"l"},
                Usage:   "list all tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getAll()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
            {
                Name:    "done",
                Aliases: []string{"d"},
                Usage:   "complete a task on the list",
                Action: func(c *cli.Context) error {
                    text := c.Args().First()
                    return completeTask(text)
                },
            },
            {
                Name:    "finished",
                Aliases: []string{"f"},
                Usage:   "list completed tasks",
                Action: func(c *cli.Context) error {
                    tasks, err := getFinished()
                    if err != nil {
                        if err == mongo.ErrNoDocuments {
                            fmt.Print("Nothing to see here.\nRun `done 'task'` to complete a task")
                            return nil
                        }

                        return err
                    }

                    printTasks(tasks)
                    return nil
                },
            },
            {
                Name:  "rm",
                Usage: "deletes a task on the list",
                Action: func(c *cli.Context) error {
                    text := c.Args().First()
                    err := deleteTask(text)
                    if err != nil {
                        return err
                    }

                    return nil
                },
            },
        }
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}
. . .

就像之前添加的所有其他子命令一样,rm 命令使用它的第一个参数来匹配数据库中的任务并将其删除。

保存并退出您的文件。

您可以通过运行程序来列出待处理的任务,而无需传递任何子命令:

go run main.go
Output1: Read a book

"Read a book" 任务上运行 rm 子命令会将其从数据库中删除:

go run main.go rm "Read a book"

如果再次列出所有待处理的任务,您会注意到 "Read a book" 任务不再出现,而是显示添加新任务的提示:

go run main.go
OutputNothing to see here
Run `add 'task'` to add a task

在此步骤中,您添加了从数据库中删除任务的功能。

结论

您已成功创建任务管理器命令行程序,并在此过程中学习了使用 MongoDB Go 驱动程序的基础知识。

请务必在 GoDoc 上查看 MongoDB Go 驱动程序的完整文档,以了解有关使用驱动程序提供的功能的更多信息。 描述使用 aggregationstransactions 的文档可能会让您特别感兴趣。

本教程的最终代码可以在这个 GitHub repo 中查看。