如何使用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 驱动程序提供的 mongo 和 options 包。
接下来,在导入之后,创建一个新的 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 类似于超时或截止日期,指示操作何时应停止运行并返回。 当特定操作运行缓慢时,它有助于防止生产系统的性能下降。 在此代码中,您传递 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/primitive、time 和 errors 包:
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 驱动程序中的 bson 和 primitive 包创建过滤器,它将匹配 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 驱动程序的完整文档,以了解有关使用驱动程序提供的功能的更多信息。 描述使用 aggregations 或 transactions 的文档可能会让您特别感兴趣。
本教程的最终代码可以在这个 GitHub repo 中查看。