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

作者选择了自由软件基金会作为 写给捐款计划的一部分接受捐款。

介绍

在多年来依赖社区开发的解决方案之后,MongoDB宣布(https://engineering.mongodb.com/post/considering-the-community-effects-of-introducing-an-official-golang-mongodb-driver)正在为Go开发一个官方驱动程序,2019年3月,这个新驱动程序达到生产准备状态(随着v1.0.0的发布)(https://github.com/mongodb/mongo-go-driver/releases/tag/v1.0.0)并自那时以来一直在不断更新。

与其他官方 MongoDB 驱动程序一样,Go 驱动程序(https://github.com/mongodb/mongo-go-driver)与 Go 编程语言相同,提供了一个简单的方法来使用 MongoDB 作为 Go 程序的数据库解决方案。它与 MongoDB API 完全集成,并暴露了 API 的所有查询、索引和汇总功能,以及其他高级功能。

在本教程中,您将开始使用官方 MongoDB Go 驱动程序. 您将安装驱动程序,连接到 MongoDB 数据库,并执行多个 CRUD 操作。

前提条件

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

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

1export GO111MODULE="on"

有关实施环境变量的更多信息,请阅读本教程中的 如何阅读和设置环境和壳变量

本指南中显示的命令和代码已在 Go v1.14.1 和 MongoDB v3.6.3 中测试。

步骤1:安装 MongoDB Go 驱动程序

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

继续,并在您的文件系统中创建此教程的新目录:

1mkdir tasker

一旦您的项目目录已设置,请使用以下命令更改它:

1cd tasker

接下来,用 go.mod 文件初始化 Go 项目,该文件定义了项目的要求,并将依赖性锁定为正确的版本:

1go mod init

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

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

在此时,你的 go.mod 文件将看起来像这样:

1[label go.mod]
2module github.com/<your_username>/tasker
3
4go 1.14

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

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

你会看到输出如下:

1[secondary_label Output]
2go: downloading go.mongodb.org/mongo-driver v1.3.2
3go: go.mongodb.org/mongo-driver upgrade => v1.3.2

在此时,你的 go.mod 文件将看起来像这样:

1[label go.mod]
2module github.com/<your_username>/tasker
3
4go 1.14
5
6require go.mongodb.org/mongo-driver v1.3.1 // indirect

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

1nano main.go

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

 1[label main.go]
 2package main
 3
 4import (
 5    "context"
 6    "log"
 7
 8    "go.mongodb.org/mongo-driver/mongo"
 9    "go.mongodb.org/mongo-driver/mongo/options"
10)

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

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

 1[label main.go]
 2. . .
 3var collection *mongo.Collection
 4var ctx = context.TODO()
 5
 6func init() {
 7    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
 8    client, err := mongo.Connect(ctx, clientOptions)
 9    if err != nil {
10    	log.Fatal(err)
11    }
12}

mongo.Connect() 接受一个 Context 和一个 options.ClientOptions 对象,用于设置连接字符串和其他驱动程序设置。

Context就像一个时间或截止日期,指示一个操作何时停止运行并返回。它有助于防止特定操作运行缓慢时的生产系统的性能下降。

接下来,让我们确保您的 MongoDB 服务器被找到并成功连接到使用Ping方法。

 1[label main.go]
 2. . .
 3    log.Fatal(err)
 4  }
 5
 6  err = client.Ping(ctx, nil)
 7  if err != nil {
 8    log.Fatal(err)
 9  }
10}

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

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

1[label main.go]
2. . .
3  err = client.Ping(ctx, nil)
4  if err != nil {
5    log.Fatal(err)
6  }
7
8  collection = client.Database("tasker").Collection("tasks")
9}

您创建一个任务数据库和一个任务集合来存储您正在创建的任务,您还将收藏设置为包级变量,以便在整个包中重复使用数据库连接。

保存和退出文件。

此时的main.go全文如下:

 1[label main.go]
 2package main
 3
 4import (
 5    "context"
 6    "log"
 7
 8    "go.mongodb.org/mongo-driver/mongo"
 9    "go.mongodb.org/mongo-driver/mongo/options"
10)
11
12var collection *mongo.Collection
13var ctx = context.TODO()
14
15func init() {
16    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
17    client, err := mongo.Connect(ctx, clientOptions)
18    if err != nil {
19    	log.Fatal(err)
20    }
21
22    err = client.Ping(ctx, nil)
23    if err != nil {
24    	log.Fatal(err)
25    }
26
27    collection = client.Database("tasker").Collection("tasks")
28}

你已经设置了你的程序连接到你的 MongoDB 服务器使用 Go 驱动程序. 在下一步,你将继续创建你的任务管理程序。

步骤 2:创建一个 CLI 计划

在此步骤中,您将安装已知的 cli包,以帮助开发您的任务管理程序. 它提供了您可以快速创建现代命令行工具的接口。

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

1go get github.com/urfave/cli/v2

接下来,重新打开您的 main.go 文件:

1nano main.go

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

 1[label main.go]
 2package main
 3
 4import (
 5    "context"
 6    "log"
 7    "os"
 8
 9    "github.com/urfave/cli/v2"
10    "go.mongodb.org/mongo-driver/mongo"
11    "go.mongodb.org/mongo-driver/mongo/options"
12)
13. . .

您还将导入os包,您将使用该包将命令行参数传递给您的程序:

init函数后添加以下代码来创建您的 CLI 程序,并使您的代码编译:

 1[label main.go]
 2. . .
 3func main() {
 4    app := &cli.App{
 5    	Name:     "tasker",
 6    	Usage:    "A simple CLI program to manage your tasks",
 7    	Commands: []*cli.Command{},
 8    }
 9
10    err := app.Run(os.Args)
11    if err != nil {
12    	log.Fatal(err)
13    }
14}

此片段创建了一个名为tasker的 CLI 程序,并添加一个简短的使用描述,在运行该程序时将被打印出来。

保存和退出您的文件。

以下是您需要构建和运行该程序的命令:

1go run main.go

您将看到以下输出:

 1[secondary_label Output]
 2NAME:
 3   tasker - A simple CLI program to manage your tasks
 4
 5USAGE:
 6   main [global options] command [command options] [arguments...]
 7
 8COMMANDS:
 9   help, h Shows a list of commands or help for one command
10
11GLOBAL OPTIONS:
12   --help, -h show help (default: false)

该程序运行并显示帮助文本,这是方便的学习该程序可以做什么,以及如何使用它。

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

步骤三:创建一个任务

在此步骤中,您将使用cli包添加一个子命令到您的 CLI 程序. 在本节的末尾,您将能够通过在您的 CLI 程序中使用一个新的add命令添加一个新的任务到您的 MongoDB 数据库。

首先,打开你的 main.go 文件:

1nano main.go

接下来,输入 go.mongodb.org/mongo-driver/bson/primitive, timeerrors的包裹:

 1[label main.go]
 2package main
 3
 4import (
 5    "context"
 6    "errors"
 7    "log"
 8    "os"
 9    "time"
10
11    "github.com/urfave/cli/v2"
12    "go.mongodb.org/mongo-driver/bson/primitive"
13    "go.mongodb.org/mongo-driver/mongo"
14    "go.mongodb.org/mongo-driver/mongo/options"
15)
16. . .

然后创建一个新的结构,以代表数据库中的单个任务,并将其插入在主要函数之前:

 1[label main.go]
 2. . .
 3type Task struct {
 4    ID primitive.ObjectID `bson:"_id"`
 5    CreatedAt time.Time          `bson:"created_at"`
 6    UpdatedAt time.Time          `bson:"updated_at"`
 7    Text string             `bson:"text"`
 8    Completed bool               `bson:"completed"`
 9}
10. . .

您使用原始包来设置每个任务的ID类型,因为MongoDB默认情况下使用ObjectID用于_id字段。MongoDB的另一个默认行为是,下级字段名称在进行序列化时被用作每个导出字段的密钥,但可以使用bson结构标签进行更改。

接下来,创建一个函数,该函数接收一个任务的实例并将其保存到数据库中。

1[label main.go]
2. . .
3func createTask(task *Task) error {
4    _, err := collection.InsertOne(ctx, task)
5  return err
6}
7. . .

collection.InsertOne() 方法将所提供的任务插入数据库集合,并返回已插入的文档的 ID。

下一步是将新命令添加到你的任务管理程序中,以创建新任务。

 1[label main.go]
 2. . .
 3func main() {
 4    app := &cli.App{
 5    	Name:  "tasker",
 6    	Usage: "A simple CLI program to manage your tasks",
 7    	Commands: []*cli.Command{
 8    		{
 9    			Name:    "add",
10    			Aliases: []string{"a"},
11    			Usage:   "add a task to the list",
12    			Action: func(c *cli.Context) error {
13    				str := c.Args().First()
14    				if str == "" {
15    					return errors.New("Cannot add an empty task")
16    				}
17
18    				task := &Task{
19    					ID:        primitive.NewObjectID(),
20    					CreatedAt: time.Now(),
21    					UpdatedAt: time.Now(),
22    					Text:      str,
23    					Completed: false,
24    				}
25
26    				return createTask(task)
27    			},
28    		},
29    	},
30    }
31
32    err := app.Run(os.Args)
33    if err != nil {
34    	log.Fatal(err)
35    }
36}

添加到您的 CLI 程序的每个新命令都放置在命令片段中,每个命令都包含一个名称、使用描述和操作。

在此代码中,您收集添加的第一个参数,并使用它来设置一个新的任务实例的文本属性,同时为其他属性分配适当的默认值。

保存和退出您的文件。

通过使用添加命令添加几个任务来测试它,如果成功,您将看到没有任何错误打印到您的屏幕上:

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

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

步骤4:列出所有任务

可以使用collection.Find()方法列出集合中的文档,该方法预期将一个过滤器以及一个指针列为可以解码结果的值。 其返回值是 Cursor,该值提供了可以重复和一次解码的文档流。

打开您的 main.go 文件:

1nano main.go

请确保导入 bson包:

 1[label main.go]
 2package main
 3
 4import (
 5    "context"
 6    "errors"
 7    "log"
 8    "os"
 9    "time"
10
11    "github.com/urfave/cli/v2"
12    "go.mongodb.org/mongo-driver/bson"
13    "go.mongodb.org/mongo-driver/bson/primitive"
14    "go.mongodb.org/mongo-driver/mongo"
15    "go.mongodb.org/mongo-driver/mongo/options"
16)
17. . .

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

 1[label main.go]
 2. . .
 3func getAll() ([]*Task, error) {
 4  // passing bson.D{{}} matches all documents in the collection
 5    filter := bson.D{{}}
 6    return filterTasks(filter)
 7}
 8
 9func filterTasks(filter interface{}) ([]*Task, error) {
10    // A slice of tasks for storing the decoded documents
11    var tasks []*Task
12
13    cur, err := collection.Find(ctx, filter)
14    if err != nil {
15    	return tasks, err
16    }
17
18    for cur.Next(ctx) {
19    	var t Task
20    	err := cur.Decode(&t)
21    	if err != nil {
22    		return tasks, err
23    	}
24
25    	tasks = append(tasks, &t)
26    }
27
28    if err := cur.Err(); err != nil {
29    	return tasks, err
30    }
31
32  // once exhausted, close the cursor
33    cur.Close(ctx)
34
35    if len(tasks) == 0 {
36    	return tasks, mongo.ErrNoDocuments
37    }
38
39    return tasks, nil
40}

BSON(二进制编码的JSON)是如何在MongoDB数据库中表示文档的,而bson包是帮助我们在Go中使用BSON对象的。

FilterTasks()函数中,您重复通过collection.Find()方法返回的标记,并将每个文档解码为一个Task实例。

在创建列出所有任务的命令之前,让我们创建一个帮助函数,该函数将一小块任务和打印到标准输出中。

在您可以使用此包之前,请将其安装在:

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

您将看到以下输出:

1[secondary_label Output]
2go: downloading gopkg.in/gookit/color.v1 v1.1.6
3go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

然后将其导入到您的 main.go 文件中,并将其导入到 fmt 包中:

 1[label main.go]
 2package main
 3
 4import (
 5    "context"
 6    "errors"
 7  "fmt"
 8    "log"
 9    "os"
10    "time"
11
12    "github.com/urfave/cli/v2"
13    "go.mongodb.org/mongo-driver/bson"
14    "go.mongodb.org/mongo-driver/bson/primitive"
15    "go.mongodb.org/mongo-driver/mongo"
16    "go.mongodb.org/mongo-driver/mongo/options"
17    "gopkg.in/gookit/color.v1"
18)
19. . .

接下来,创建一个新的printTasks函数,按照你的main函数:

 1[label main.go]
 2. . .
 3func printTasks(tasks []*Task) {
 4    for i, v := range tasks {
 5    	if v.Completed {
 6    		color.Green.Printf("%d: %s\n", i+1, v.Text)
 7    	} else {
 8    		color.Yellow.Printf("%d: %s\n", i+1, v.Text)
 9    	}
10    }
11}
12. . .

打印任务函数采用一个任务片段,重复每个任务,并将其打印到标准输出中,使用绿色表示已完成的任务,黄色表示未完成的任务。

继续,添加以下突出的行,以在命令片段中创建一个新的所有命令。

 1[label main.go]
 2. . .
 3func main() {
 4    app := &cli.App{
 5    	Name:  "tasker",
 6    	Usage: "A simple CLI program to manage your tasks",
 7    	Commands: []*cli.Command{
 8    		{
 9    			Name:    "add",
10    			Aliases: []string{"a"},
11    			Usage:   "add a task to the list",
12    			Action: func(c *cli.Context) error {
13    				str := c.Args().First()
14    				if str == "" {
15    					return errors.New("Cannot add an empty task")
16    				}
17
18    				task := &Task{
19    					ID:        primitive.NewObjectID(),
20    					CreatedAt: time.Now(),
21    					UpdatedAt: time.Now(),
22    					Text:      str,
23    					Completed: false,
24    				}
25
26    				return createTask(task)
27    			},
28    		},
29    		{
30    			Name:    "all",
31    			Aliases: []string{"l"},
32    			Usage:   "list all tasks",
33    			Action: func(c *cli.Context) error {
34    				tasks, err := getAll()
35    				if err != nil {
36    					if err == mongo.ErrNoDocuments {
37    						fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
38    						return nil
39    					}
40
41    					return err
42    				}
43
44    				printTasks(tasks)
45    				return nil
46    			},
47    		},
48    	},
49    }
50
51    err := app.Run(os.Args)
52    if err != nil {
53    	log.Fatal(err)
54    }
55}
56
57. . .

所有命令会检索数据库中的所有任务并将其打印到标准输出中,如果没有任务,则会打印添加新任务的提示。

保存和退出您的文件。

全部命令构建和运行您的程序:

1go run main.go all

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

1[secondary_label Output]
21: Learn Go
32: Read a book

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

步骤五:完成任务

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

首先,打开你的 main.go 文件:

1nano main.go

插入以下片段,按照您的filterTasks函数:

 1[label main.go]
 2. . .
 3func completeTask(text string) error {
 4    filter := bson.D{primitive.E{Key: "text", Value: text}}
 5
 6    update := bson.D{primitive.E{Key: "$set", Value: bson.D{
 7    	primitive.E{Key: "completed", Value: true},
 8    }}}
 9
10    t := &Task{}
11    return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
12}
13. . .

该函数与第一个文本属性等于文本参数的文档相匹配。更新文档指定完成属性设置为。如果在FindOneAndUpdate()操作中出现错误,则将返回completeTask()

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

 1[label main.go]
 2. . .
 3func main() {
 4    app := &cli.App{
 5    	Name:  "tasker",
 6    	Usage: "A simple CLI program to manage your tasks",
 7    	Commands: []*cli.Command{
 8    		{
 9    			Name:    "add",
10    			Aliases: []string{"a"},
11    			Usage:   "add a task to the list",
12    			Action: func(c *cli.Context) error {
13    				str := c.Args().First()
14    				if str == "" {
15    					return errors.New("Cannot add an empty task")
16    				}
17
18    				task := &Task{
19    					ID:        primitive.NewObjectID(),
20    					CreatedAt: time.Now(),
21    					UpdatedAt: time.Now(),
22    					Text:      str,
23    					Completed: false,
24    				}
25
26    				return createTask(task)
27    			},
28    		},
29    		{
30    			Name:    "all",
31    			Aliases: []string{"l"},
32    			Usage:   "list all tasks",
33    			Action: func(c *cli.Context) error {
34    				tasks, err := getAll()
35    				if err != nil {
36    					if err == mongo.ErrNoDocuments {
37    						fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
38    						return nil
39    					}
40
41    					return err
42    				}
43
44    				printTasks(tasks)
45    				return nil
46    			},
47    		},
48    		{
49    			Name:    "done",
50    			Aliases: []string{"d"},
51    			Usage:   "complete a task on the list",
52    			Action: func(c *cli.Context) error {
53    				text := c.Args().First()
54    				return completeTask(text)
55    			},
56    		},
57    	},
58    }
59
60    err := app.Run(os.Args)
61    if err != nil {
62    	log.Fatal(err)
63    }
64}
65
66. . .

使用完成命令传递的参数来查找第一个与文本属性匹配的文档。

保存和退出您的文件。

然后用完成命令运行您的程序:

1go run main.go done "Learn Go"

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

1go run main.go all

Screenshot of terminal output after completing a task

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

步骤 6 – 显示只待执行的任务

在此步骤中,您将嵌入代码,以使用 MongoDB 驱动程序从数据库中获取等待任务。

让我们添加一个新的函数来检索尚未完成的任务。

1nano main.go

然后在completeTask函数下添加此片段:

 1[label main.go]
 2. . .
 3func getPending() ([]*Task, error) {
 4    filter := bson.D{
 5    	primitive.E{Key: "completed", Value: false},
 6    }
 7
 8    return filterTasks(filter)
 9}
10. . .

您使用 MongoDB 驱动程序的bsonprimitive包创建过滤器,将匹配其完成属性设置为false的文档。

不要创建一个新的命令来列出等待任务,让我们在没有任何命令的情况下运行程序时将其作为默认操作。

 1[label main.go]
 2. . .
 3func main() {
 4    app := &cli.App{
 5    	Name:  "tasker",
 6    	Usage: "A simple CLI program to manage your tasks",
 7    	Action: func(c *cli.Context) error {
 8    		tasks, err := getPending()
 9    		if err != nil {
10    			if err == mongo.ErrNoDocuments {
11    				fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
12    				return nil
13    			}
14
15    			return err
16    		}
17
18    		printTasks(tasks)
19    		return nil
20    	},
21    	Commands: []*cli.Command{
22    		{
23    			Name:    "add",
24    			Aliases: []string{"a"},
25    			Usage:   "add a task to the list",
26    			Action: func(c *cli.Context) error {
27    				str := c.Args().First()
28    				if str == "" {
29    					return errors.New("Cannot add an empty task")
30    				}
31
32    				task := &Task{
33    					ID:        primitive.NewObjectID(),
34    					CreatedAt: time.Now(),
35    					UpdatedAt: time.Now(),
36    					Text:      str,
37    					Completed: false,
38    				}
39
40    				return createTask(task)
41    			},
42    		},
43. . .

当程序在没有任何子命令的情况下执行默认操作时,该Action属性会执行默认操作. 此处放置了列出等待任务的逻辑。 呼叫了getPending()函数,并使用printTasks()打印到结果的标准输出中。

保存和退出您的文件。

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

1go run main.go

您将看到以下输出:

1[secondary_label Output]
21: Read a book

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

步骤 7 – 显示完成的任务

在此步骤中,您将添加一个新的完成子命令,从数据库中获取已完成的任务并将其显示在屏幕上。

打开您的 main.go 文件:

1nano main.go

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

 1[label main.go]
 2. . .
 3func getFinished() ([]*Task, error) {
 4    filter := bson.D{
 5    	primitive.E{Key: "completed", Value: true},
 6    }
 7
 8    return filterTasks(filter)
 9}
10. . .

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

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

 1[label main.go]
 2. . .
 3func main() {
 4    app := &cli.App{
 5    	Name:  "tasker",
 6    	Usage: "A simple CLI program to manage your tasks",
 7    	Action: func(c *cli.Context) error {
 8    		tasks, err := getPending()
 9    		if err != nil {
10    			if err == mongo.ErrNoDocuments {
11    				fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
12    				return nil
13    			}
14
15    			return err
16    		}
17
18    		printTasks(tasks)
19    		return nil
20    	},
21    	Commands: []*cli.Command{
22    		{
23    			Name:    "add",
24    			Aliases: []string{"a"},
25    			Usage:   "add a task to the list",
26    			Action: func(c *cli.Context) error {
27    				str := c.Args().First()
28    				if str == "" {
29    					return errors.New("Cannot add an empty task")
30    				}
31
32    				task := &Task{
33    					ID:        primitive.NewObjectID(),
34    					CreatedAt: time.Now(),
35    					UpdatedAt: time.Now(),
36    					Text:      str,
37    					Completed: false,
38    				}
39
40    				return createTask(task)
41    			},
42    		},
43    		{
44    			Name:    "all",
45    			Aliases: []string{"l"},
46    			Usage:   "list all tasks",
47    			Action: func(c *cli.Context) error {
48    				tasks, err := getAll()
49    				if err != nil {
50    					if err == mongo.ErrNoDocuments {
51    						fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
52    						return nil
53    					}
54
55    					return err
56    				}
57
58    				printTasks(tasks)
59    				return nil
60    			},
61    		},
62    		{
63    			Name:    "done",
64    			Aliases: []string{"d"},
65    			Usage:   "complete a task on the list",
66    			Action: func(c *cli.Context) error {
67    				text := c.Args().First()
68    				return completeTask(text)
69    			},
70    		},
71    		{
72    			Name:    "finished",
73    			Aliases: []string{"f"},
74    			Usage:   "list completed tasks",
75    			Action: func(c *cli.Context) error {
76    				tasks, err := getFinished()
77    				if err != nil {
78    					if err == mongo.ErrNoDocuments {
79    						fmt.Print("Nothing to see here.\nRun `done 'task'` to complete a task")
80    						return nil
81    					}
82
83    					return err
84    				}
85
86    				printTasks(tasks)
87    				return nil
88    			},
89    		},
90    	}
91    }
92
93    err := app.Run(os.Args)
94    if err != nil {
95    	log.Fatal(err)
96    }
97}
98. . .

完成命令通过在这里创建的getFinished()函数获取具有完成属性的任务,然后将其传输到printTasks函数,以便将其打印到标准输出中。

保存和退出您的文件。

运行以下命令:

1go run main.go finished

您将看到以下输出:

1[secondary_label Output]
21: Learn Go

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

步骤 8 - 删除任务

在此步骤中,您将添加一个新的删除子命令,以允许用户从数据库中删除任务. 要删除单个任务,您将使用MongoDB驱动程序的collection.DeleteOne()方法。

再次打开你的main.go文件:

1nano main.go

添加此删除任务函数以在完成函数后立即从数据库中删除任务:

 1[label main.go]
 2. . .
 3func deleteTask(text string) error {
 4    filter := bson.D{primitive.E{Key: "text", Value: text}}
 5
 6    res, err := collection.DeleteOne(ctx, filter)
 7    if err != nil {
 8    	return err
 9    }
10
11    if res.DeletedCount == 0 {
12    	return errors.New("No tasks were deleted")
13    }
14
15    return nil
16}
17. . .

删除任务方法采用一个字符串参数,代表要删除的任务项目. 一个过滤器被构建以匹配任务项目,其文本属性被设置为字符串参数. 您将过滤器传输到删除One()方法,该方法匹配集合中的项目并删除它。

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

现在添加一个新的rm命令,如下所示:

  1[label main.go]
  2. . .
  3func main() {
  4    app := &cli.App{
  5    	Name:  "tasker",
  6    	Usage: "A simple CLI program to manage your tasks",
  7    	Action: func(c *cli.Context) error {
  8    		tasks, err := getPending()
  9    		if err != nil {
 10    			if err == mongo.ErrNoDocuments {
 11    				fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
 12    				return nil
 13    			}
 14
 15    			return err
 16    		}
 17
 18    		printTasks(tasks)
 19    		return nil
 20    	},
 21    	Commands: []*cli.Command{
 22    		{
 23    			Name:    "add",
 24    			Aliases: []string{"a"},
 25    			Usage:   "add a task to the list",
 26    			Action: func(c *cli.Context) error {
 27    				str := c.Args().First()
 28    				if str == "" {
 29    					return errors.New("Cannot add an empty task")
 30    				}
 31
 32    				task := &Task{
 33    					ID:        primitive.NewObjectID(),
 34    					CreatedAt: time.Now(),
 35    					UpdatedAt: time.Now(),
 36    					Text:      str,
 37    					Completed: false,
 38    				}
 39
 40    				return createTask(task)
 41    			},
 42    		},
 43    		{
 44    			Name:    "all",
 45    			Aliases: []string{"l"},
 46    			Usage:   "list all tasks",
 47    			Action: func(c *cli.Context) error {
 48    				tasks, err := getAll()
 49    				if err != nil {
 50    					if err == mongo.ErrNoDocuments {
 51    						fmt.Print("Nothing to see here.\nRun `add 'task'` to add a task")
 52    						return nil
 53    					}
 54
 55    					return err
 56    				}
 57
 58    				printTasks(tasks)
 59    				return nil
 60    			},
 61    		},
 62    		{
 63    			Name:    "done",
 64    			Aliases: []string{"d"},
 65    			Usage:   "complete a task on the list",
 66    			Action: func(c *cli.Context) error {
 67    				text := c.Args().First()
 68    				return completeTask(text)
 69    			},
 70    		},
 71    		{
 72    			Name:    "finished",
 73    			Aliases: []string{"f"},
 74    			Usage:   "list completed tasks",
 75    			Action: func(c *cli.Context) error {
 76    				tasks, err := getFinished()
 77    				if err != nil {
 78    					if err == mongo.ErrNoDocuments {
 79    						fmt.Print("Nothing to see here.\nRun `done 'task'` to complete a task")
 80    						return nil
 81    					}
 82
 83    					return err
 84    				}
 85
 86    				printTasks(tasks)
 87    				return nil
 88    			},
 89    		},
 90    		{
 91    			Name:  "rm",
 92    			Usage: "deletes a task on the list",
 93    			Action: func(c *cli.Context) error {
 94    				text := c.Args().First()
 95    				err := deleteTask(text)
 96    				if err != nil {
 97    					return err
 98    				}
 99
100    				return nil
101    			},
102    		},
103    	}
104    }
105
106    err := app.Run(os.Args)
107    if err != nil {
108    	log.Fatal(err)
109    }
110}
111. . .

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

保存和退出您的文件。

您可以通过运行程序列出未完成的任务,而无需通过任何子命令:

1go run main.go
1[secondary_label Output]
21: Read a book

阅读一本书任务上运行rm子命令将从数据库中删除它:

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

如果您再次列出所有尚未完成的任务,您会注意到阅读一本书任务不再显示,而显示了添加新任务的提示:

1go run main.go
1[secondary_label Output]
2Nothing to see here
3Run `add 'task'` to add a task

在此步骤中,您添加了一个函数来从数据库中删除任务。

结论

您已经成功创建了一个任务管理器命令行程序,并了解了使用MongoDB Go驱动程序的基本知识。

请确保在 GoDoc查看 MongoDB Go 驱动程序的完整文档,以了解使用驱动程序提供的功能。

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

Published At
Categories with 技术
comments powered by Disqus