如何使用 MongoDB Go 驱动程序将 Go 与 MongoDB 结合使用

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

介绍

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

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

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

先决条件

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

如果您使用的是 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 驱动程序包并将其导入到您的项目中。您还将连接到 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 Driver 添加为项目的依赖项:

  • go get go.mongodb.org/mongo-driver

您将看到如下输出:

Output
go: 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"
)

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

接下来,在您的导入之后,创建一个新的 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()接受一个Context和一个options.ClientOptions对象,用于设置连接字符串和其他驱动程序设置。您可以访问选项包文档以查看可用的配置选项。

上下文就像一个超时或截止时间,指示操作应该何时停止运行并返回。当特定操作运行缓慢时,它有助于防止生产系统的性能下降。在此代码中,您通过传递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)
    }
}

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

保存并退出您的文件。

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

  • go run main.go

您将看到以下输出:

Output
NAME: 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 程序添加子命令在本节结束时,您将能够通过add在 CLI 程序中使用新命令向 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并使用它来设置TextTask实例属性,同时为其他属性分配适当的默认值。新任务随后被传递到createTask,它将任务插入到数据库中,nil如果一切顺利导致命令退出,则返回

保存并退出您的文件。

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

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

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

第 4 步 — 列出所有任务

可以使用该collection.Find()方法列出集合中的文档,该方法需要一个过滤器以及一个指向可以解码结果的值的指针。它的返回值是一个Cursor,它提供了一个文档流,可以一次迭代和解码一个。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 对象。函数中bson.D使用类型getAll()表示 BSON 文档,并且在属性顺序很重要的地方使用它。通过将bson.D{{}}过滤器传递filterTasks(),您表示要匹配集合中的所有文档。

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

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

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

  • go get gopkg.in/gookit/color.v1

您将看到以下输出:

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

并将其main.gofmt一起导入到您的文件中

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"
)
. . .

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

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,迭代每个切片,并使用绿色表示已完成的任务和黄色表示未完成的任务将其打印到标准输出。

继续并添加以下突出显示的行以allCommands切片创建新命令此命令会将所有添加的任务打印到标准输出:

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

它将列出您到目前为止添加的所有任务:

Output
1: 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返回。

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

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

您将看到以下输出:

Output
1: 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命令通过此处创建函数检索其completed属性设置为 的任务然后它将它传递给函数,以便将它们打印到标准输出。truegetFinished()printTasks

保存并退出您的文件。

运行以下命令:

  • go run main.go finished

您将看到以下输出:

Output
1: Learn Go

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

步骤 8 — 删除任务

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

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()给与集合中的项目匹配方法并将其删除。

您可以检查方法DeletedCount结果属性,DeleteOne以确认是否删除了任何文档。如果过滤器无法匹配要删除的文档,则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命令使用其第一个参数来匹配数据库中的任务并将其删除。

保存并退出您的文件。

You can list pending tasks by running your program without passing any subcommands:

  • go run main.go
Output
1: Read a book

Running the rm subcommand on the "Read a book" task will delete it from the database:

  • go run main.go rm "Read a book"

If you list all pending tasks again, you’ll notice that the "Read a book" task does not appear anymore and a prompt to add a new task is shown instead:

  • go run main.go
Output
Nothing to see here Run `add 'task'` to add a task

In this step you added a function to delete tasks from the database.

Conclusion

You’ve successfully created a task manager command line program and learned the fundamentals of using the MongoDB Go driver in the process.

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

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

觉得文章有用?

点个广告表达一下你的爱意吧 !😁