The author selected the Free Software Foundation to receive a donation as part of the Write for DOnations program.
作者选择了自由软件基金会作为Write for DOnations计划的一部分接受捐赠。
介绍 (Introduction)
After relying on community developed solutions for many years, MongoDB announced that they were working on an official driver for Go. In March 2019, this new driver reached a production-ready status with the release of v1.0.0 and has been updated continually since then.
在依靠社区开发的解决方案多年之后, MongoDB宣布他们正在为Go制定官方驱动程序。 在2019年3月,此新驱动程序在v1.0.0版本中达到了生产就绪状态,此后一直在不断更新。
Like the other official MongoDB drivers, the Go driver is idiomatic to the Go programming language and provides an easy way to use MongoDB as the database solution for a Go program. It is fully integrated with the MongoDB API, and exposes all of the query, indexing, and aggregation features of the API, along with other advanced features. Unlike third-party libraries, it will be fully supported by MongoDB engineers so you can be assured of its continued development and maintenance.
像其他官方的MongoDB驱动程序一样, Go驱动程序是Go编程语言的惯用语,它提供了一种简单的方法来将MongoDB用作Go程序的数据库解决方案。 它与MongoDB API完全集成,并公开了API的所有查询,索引和聚合功能以及其他高级功能。 与第三方库不同,它将由MongoDB工程师完全支持,因此您可以放心其继续开发和维护。
In this tutorial you’ll get started with using the official MongoDB Go Driver. You’ll install the driver, connect to a MongoDB database, and perform several CRUD operations. In the process, you’ll create a task manager program for managing tasks through the command line.
在本教程中,您将开始使用官方的MongoDB Go驱动程序。 您将安装驱动程序,连接到MongoDB数据库,并执行一些CRUD操作。 在此过程中,您将创建一个任务管理器程序以通过命令行管理任务。
先决条件 (Prerequisites)
For this tutorial, you’ll need the following:
对于本教程,您将需要以下内容:
Go installed on your machine and a Go workspace configured following How To Install Go and Set Up a Local Programming Environment. In this tutorial, the project will be named
tasker
. You’ll need Go v1.11 or higher installed on your machine with Go Modules enabled.Go已安装在您的计算机上,并已按照如何安装Go和设置本地编程环境配置了Go工作区。 在本教程中,该项目将命名为
tasker
。 您需要在启用了Go模块的计算机上安装Go v1.11或更高版本。MongoDB installed for your operating system following How To Install MongoDB. MongoDB 2.6 or higher is the minimum version supported by the MongoDB Go driver.
按照如何安装MongoDB为您的操作系统安装了MongoDB 。 MongoDB 2.6或更高版本是MongoDB Go驱动程序支持的最低版本。
If you’re using Go v1.11 or 1.12, ensure Go Modules is enabled by setting the GO111MODULE
environment variable to on
as shown following:
如果您使用的是Go v1.11或1.12,请通过将GO111MODULE
环境变量设置为GO111MODULE
来确保启用了Go模块on
如下所示:
- export GO111MODULE="on" 导出GO111MODULE =“ on”
For more information on implementing environment variables, read this tutorial on How To Read and Set Environmental and Shell Variables.
有关实现环境变量的更多信息,请阅读有关如何读取和设置环境变量和Shell变量的本教程。
The commands and code shown in this guide were tested with Go v1.14.1 and MongoDB v3.6.3.
本指南中显示的命令和代码已通过Go v1.14.1和MongoDB v3.6.3进行了测试。
第1步-安装MongoDB Go驱动程序 (Step 1 — Installing the MongoDB Go Driver)
In this step, you’ll install the Go Driver package for MongoDB and import it into your project. You’ll also connect to your MongoDB database and check the status of the connection.
在此步骤中,您将安装MongoDB的Go Driver软件包并将其导入到您的项目中。 您还将连接到MongoDB数据库并检查连接状态。
Go ahead and create a new directory for this tutorial in your filesystem:
继续并在文件系统中为本教程创建一个新目录:
- mkdir tasker mkdir Tasker
Once your project directory is set up, change into it with the following command:
设置项目目录后,请使用以下命令将其更改为:
- cd tasker 光盘任务
Next, initialize the Go project with a go.mod
file. This file defines project requirements and locks dependencies to their correct versions:
接下来,使用go.mod
文件初始化Go项目。 此文件定义了项目要求,并将依赖项锁定为其正确的版本:
- go mod init 进入mod init
If your project directory is outside the $GOPATH
, you need to specify the import path for your module as follows:
如果您的项目目录在$GOPATH
之外,则需要为模块指定导入路径,如下所示:
go mod init github.com/<your_username>/tasker
进入mod init github.com/ <您的用户名> / tasker
At this point, your go.mod
file will look like this:
此时,您的go.mod
文件将如下所示:
module github.com/<your_username>/tasker
go 1.14
Add the MongoDB Go Driver as a dependency for your project using following command:
使用以下命令将MongoDB Go驱动程序添加为项目的依赖项:
- go get go.mongodb.org/mongo-driver 去得到go.mongodb.org/mongo-driver
You’ll see output like the following:
您将看到类似以下的输出:
Output
go: downloading go.mongodb.org/mongo-driver v1.3.2
go: go.mongodb.org/mongo-driver upgrade => v1.3.2
At this point, your go.mod
file will look like this:
此时,您的go.mod
文件将如下所示:
module github.com/<your_username>/tasker
go 1.14
require go.mongodb.org/mongo-driver v1.3.1 // indirect
Next, create a main.go
file in your project root and open it in your text editor:
接下来,在项目根目录中创建一个main.go
文件,并在文本编辑器中将其打开:
- nano main.go 纳米main.go
To get started with the driver, import the following packages into your main.go
file:
要开始使用驱动程序,请将以下软件包导入到main.go
文件中:
package main
import (
"context"
"log"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
Here you add the mongo
and options
packages, which the MongoDB Go driver provides.
在这里,您将添加MongoDB Go驱动程序提供的mongo
和options
软件包。
Next, following your imports, create a new MongoDB client and connect to your running MongoDB server:
接下来,在导入之后,创建一个新的MongoDB客户端并连接到正在运行的MongoDB服务器:
. . .
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()
accepts a Context
and a options.ClientOptions
object, which is used to set the connection string and other driver settings. You can visit the options package documentation to see what configuration options are available.
mongo.Connect()
接受Context
和options.ClientOptions
对象,该对象用于设置连接字符串和其他驱动程序设置。 您可以访问选项包文档以查看可用的配置选项。
Context is like a timeout or deadline that indicates when an operation should stop running and return. It helps to prevent performance degradation on production systems when specific operations are running slow. In this code, you’re passing context.TODO()
to indicate that you’re not sure what context to use right now, but you plan to add one in the future.
上下文就像一个超时或截止期限,指示操作何时应停止运行并返回。 当特定操作运行缓慢时,它有助于防止生产系统上的性能下降。 在此代码中,您传递了context.TODO()
来指示您不确定现在使用哪种上下文,但是您计划在将来添加一个。
Next, let’s ensure that your MongoDB server was found and connected to successfully using the Ping
method. Add the following code inside the init
function:
接下来,让我们确保使用Ping
方法找到了MongoDB服务器并将其成功连接。 在init
函数内添加以下代码:
. . .
log.Fatal(err)
}
err = client.Ping(ctx, nil)
if err != nil {
log.Fatal(err)
}
}
If there are any errors while connecting to the database, the program should crash while you try to fix the problem as there’s no point keeping the program running without an active database connection.
如果连接到数据库时出现任何错误,则在尝试解决该问题时该程序应崩溃,因为没有活动的数据库连接就无法保持该程序的运行。
Add the following code to create a database:
添加以下代码以创建数据库:
. . .
err = client.Ping(ctx, nil)
if err != nil {
log.Fatal(err)
}
collection = client.Database("tasker").Collection("tasks")
}
You create a tasker
database and a task
collection to store the tasks you’ll be creating. You also set up collection
as a package-level variable so you can reuse the database connection throughout the package.
您创建一个tasker
数据库和一个task
集合以存储将要创建的任务。 您还可以将collection
设置为程序包级变量,以便可以在整个程序包中重用数据库连接。
Save and exit the file.
保存并退出文件。
The full main.go
at this point is as follows:
此时完整的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")
}
You’ve set up your program to connect to your MongoDB server using the Go driver. In the next step, you’ll proceed with the creation of your task manager program.
您已经设置程序使用Go驱动程序连接到MongoDB服务器。 在下一步中,您将继续创建任务管理器程序。
第2步-创建CLI程序 (Step 2 — Creating a CLI Program)
In this step, you’ll install the well-known cli
package to aid with the development of your task manager program. It provides an interface that you can take advantage of to rapidly create modern command line tools. For example, this package gives the ability to define subcommands for your program for a more git-like command line experience.
在此步骤中,您将安装著名的cli
软件包以帮助您开发任务管理器程序。 它提供了一个界面,您可以利用它来快速创建现代的命令行工具。 例如,此软件包提供了为您的程序定义子命令的功能,以提供更像git的命令行体验。
Run the following command to add the package as a dependency:
运行以下命令以将软件包添加为依赖项:
- go get github.com/urfave/cli/v2 去获取github.com/urfave/cli/v2
Next, open up your main.go
file again:
接下来,再次打开您的main.go
文件:
- nano main.go 纳米main.go
Add the following highlighted code to your main.go
file:
将以下突出显示的代码添加到您的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"
)
. . .
You import the cli
package as mentioned. You also import the os
package, which you’ll use to pass command line arguments to your program:
您按照提到的方式导入cli
包。 您还将导入os
软件包,该软件包将用于将命令行参数传递给程序:
Add the following code after your init
function to create your CLI program and cause your code to compile:
在init
函数之后添加以下代码,以创建CLI程序并使代码编译:
. . .
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)
}
}
This snippet creates a CLI program called tasker
and adds a short usage description that will be printed out when you run the program. The Commands
slice is where you’ll add commands for your program. The Run
command parses the arguments slice to the appropriate command.
此代码段创建了一个称为tasker
的CLI程序,并添加了简短的用法说明,该说明将在您运行该程序时打印出来。 您可以在“ Commands
切片中为程序添加命令。 Run
命令将参数切片解析为适当的命令。
Save and exit your file.
保存并退出文件。
Here’s the command you need to build and run the program:
这是构建和运行程序所需的命令:
- go run main.go 去运行main.go
You’ll see the following output:
您将看到以下输出:
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)
The program runs and shows help text, which is handy for learning about what the program can do, and how to use it.
该程序将运行并显示帮助文本,这对于了解程序可以做什么以及如何使用它非常有用。
In the next steps, you’ll improve the utility of your program by adding subcommands to help manage your tasks in MongoDB.
在接下来的步骤中,您将通过添加子命令来帮助管理MongoDB中的任务来改善程序的实用性。
第3步-创建任务 (Step 3 — Creating a Task)
In this step, you’ll add a subcommand to your CLI program using the cli
package. At the end of this section, you’ll be able to add a new task to your MongoDB database by using a new add
command in your CLI program.
在此步骤中,您将使用cli
包将子命令添加到CLI程序。 在本节的最后,您将能够通过在CLI程序中使用新的add
命令将新任务添加到MongoDB数据库中。
Begin by opening up your main.go
file:
首先打开您的main.go
文件:
- nano main.go 纳米main.go
Next, import the go.mongodb.org/mongo-driver/bson/primitive
, time
, and errors
packages:
接下来,将go.mongodb.org/mongo-driver/bson/primitive
, time
和errors
的包:
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"
)
. . .
Then create a new struct to represent a single task in the database and insert it immediately preceding the main
function:
然后创建一个新结构以表示数据库中的单个任务,并将其插入到main
函数之前:
. . .
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"`
}
. . .
You use the primitive
package to set the type of the ID of each task since MongoDB uses ObjectID
s for the _id
field by default. Another default behavior of MongoDB is that the lowercase field name is used as the key for each exported field when it is being serialized, but this can be changed using bson
struct tags.
由于MongoDB默认将ObjectID
用于_id
字段,因此您可以使用primitive
包来设置每个任务的ID类型。 MongoDB的另一个默认行为是,在序列化每个导出字段时,将小写字段名称用作每个导出字段的键,但是可以使用bson
struct标记对此进行更改。
Next, create a function that receives an instance of Task
and saves it in the database. Add this snippet following the main
function:
接下来,创建一个接收Task
实例并将其保存在数据库中的函数。 在main
功能后添加以下代码段:
. . .
func createTask(task *Task) error {
_, err := collection.InsertOne(ctx, task)
return err
}
. . .
The collection.InsertOne()
method inserts the provided task in the database collection and returns the ID of the document that was inserted. Since you don’t need this ID, you discard it by assigning to the underscore operator.
collection.InsertOne()
方法将提供的任务插入数据库集合中,并返回插入的文档的ID。 由于不需要此ID,因此可以通过分配给下划线运算符来丢弃它。
The next step is to add a new command to your task manager program for creating new tasks. Let’s call it add
:
下一步是将新命令添加到任务管理器程序中,以创建新任务。 我们称它为add
:
. . .
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)
}
}
Every new command that is added to your CLI program is placed inside the Commands
slice. Each one consists of a name, usage description, and action. This is the code that will run upon command execution.
添加到CLI程序的每个新命令都放置在Commands
切片内。 每一个都包含一个名称,用法说明和操作。 这是将在命令执行后运行的代码。
In this code, you collect the first argument to add
and use it to set the Text
property of a new Task
instance while assigning the appropriate defaults for the other properties. The new task is subsequently passed on to createTask
, which inserts the task into the database and returns nil
if all goes well causing the command to exit.
在此代码中,您收集了第一个参数以add
并使用它来设置新Task
实例的Text
属性,同时为其他属性分配适当的默认值。 新任务随后传递给createTask
,它将所有任务插入数据库,如果一切顺利,则返回nil
,导致命令退出。
Save and exit your file.
保存并退出文件。
Test it out by adding a few tasks using the add
command. If successful, you’ll see no errors printed to your screen:
通过使用add
命令添加一些任务来对其进行测试。 如果成功,您将不会在屏幕上看到任何错误:
- go run main.go add "Learn Go" 运行main.go添加“ Learn Go”
- go run main.go add "Read a book" 去运行main.go添加“读一本书”
Now that you can add tasks successfully, let’s implement a way to display all the tasks that you’ve added to the database.
现在您可以成功添加任务,让我们实现一种显示已添加到数据库中的所有任务的方法。
步骤4 —列出所有任务 (Step 4 — Listing all Tasks)
Listing the documents in a collection can be done using the collection.Find()
method, which expects a filter as well as a pointer to a value into which the result can be decoded. Its return value is a Cursor, which provides a stream of documents that can be iterated over and decoded one at a time. The Cursor is then closed once it has been exhausted.
可以使用collection.Find()
方法完成将文档列出在集合中的操作,该方法需要一个过滤器以及一个指向可以将结果解码为该值的指针。 它的返回值是Cursor ,它提供了可以一次迭代和解码一次的文档流。 一旦游标耗尽,则将其关闭。
Open your main.go
file:
打开您的main.go
文件:
- nano main.go 纳米main.go
Make sure to import the bson
package:
确保导入bson
包:
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"
)
. . .
Then create the following functions immediately after createTask
:
然后在createTask
之后立即创建以下函数:
. . .
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 (Binary-encoded JSON) is how documents are represented in a MongoDB database and the bson
package is what helps us work with BSON objects in Go. The bson.D
type used in the getAll()
function represents a BSON document and it’s used where the order of the properties matter. By passing bson.D{{}}
as your filter to filterTasks()
, you’re indicating that you want to match all the documents in the collection.
BSON(二进制编码的JSON)是在MongoDB数据库中表示文档的方式,而bson
包是帮助我们在Go中处理BSON对象的方式。 getAll()
函数中使用的bson.D
类型表示一个BSON文档,并且在属性顺序很重要的地方使用。 通过将bson.D{{}}
作为过滤器传递给filterTasks()
,表明您要匹配集合中的所有文档。
In the filterTasks()
function, you iterate over the Cursor returned by the collection.Find()
method and decode each document into an instance of Task
. Each Task
is then appended to the slice of tasks created at the start of the function. Once the Cursor is exhausted, it is closed and the tasks
slice is returned.
在filterTasks()
函数中,对collection.Find()
方法返回的Cursor进行迭代,并将每个文档解码为Task
的实例。 然后,将每个Task
附加到在函数开始时创建的任务片中。 游标耗尽后,将其关闭并返回tasks
片。
Before you create a command for listing all tasks, let’s create a helper function that takes a slice of tasks
and prints to the standard output. You’ll be using the color
package to colorize the output.
在创建列出所有任务的命令之前,让我们创建一个帮助程序函数,该函数将一部分tasks
并打印到标准输出。 您将使用color
包对输出进行着色。
Before you can use the this package, install it with:
使用此软件包之前,请使用以下命令进行安装:
- go get gopkg.in/gookit/color.v1 去获得gopkg.in/gookit/color.v1
You’ll see the following output:
您将看到以下输出:
Output
go: downloading gopkg.in/gookit/color.v1 v1.1.6
go: gopkg.in/gookit/color.v1 upgrade => v1.1.6
And import it into your main.go
file along with the fmt
package:
并将其与fmt
包一起导入到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"
)
. . .
Next, create a new printTasks
function following your main
function:
接下来,在您的main
功能之后创建一个新的printTasks
函数:
. . .
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)
}
}
}
. . .
This printTasks
function takes a slice of tasks
, iterates over each one, and prints it out to the standard output using the green color to indicate completed tasks, and yellow for incomplete tasks.
此printTasks
函数采用tasks
,对每个tasks
迭代,然后将其打印为标准输出,其中绿色表示已完成的任务,黄色表示未完成的任务。
Go ahead and add the following highlighted lines to create a new all
command to the Commands
slice. This command will print all added tasks to the standard output:
继续并添加以下突出显示的行,以在Commands
切片中创建一个新的all
命令。 此命令会将所有添加的任务打印到标准输出:
. . .
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)
}
}
. . .
The all
command retrieves all the tasks present in the database and prints them to the standard output. If no tasks are present, a prompt to add a new task is printed instead.
all
命令检索数据库中存在的所有任务,并将它们打印到标准输出。 如果没有任务,则会提示添加新任务。
Save and exit your file.
保存并退出文件。
Build and run your program with the all
command:
使用all
命令生成并运行程序:
- go run main.go all 去运行main.go all
It will list all the tasks that you’ve added so far:
它将列出您到目前为止添加的所有任务:
Output
1: Learn Go
2: Read a book
Now that you can view all the tasks in the database, let’s add the ability to mark a task as completed in the next step.
现在您可以查看数据库中的所有任务,让我们在下一步中添加将任务标记为已完成的功能。
第5步-完成任务 (Step 5 — Completing a Task)
In this step, you’ll create a new subcommand called done
that will allow you to mark an existing task in the database as completed. To mark a task as completed, you can use the collection.FindOneAndUpdate()
method. It allows you to locate a document in a collection and update some or all of its properties. This method requires a filter to locate the document and an update document to describe the operation. Both of these are built using bson.D
types.
在此步骤中,您将创建一个名为done
的新子命令,该子命令将允许您将数据库中的现有任务标记为已完成。 要将任务标记为已完成,可以使用collection.FindOneAndUpdate()
方法。 它使您可以在集合中找到文档并更新其某些或全部属性。 此方法需要一个过滤器来定位文档,并需要一个更新文档来描述操作。 这两个都是使用bson.D
类型构建的。
Start by opening up your main.go
file:
首先打开main.go
文件:
- nano main.go 纳米main.go
Insert the following snippet following your filterTasks
function:
在filterTasks
函数之后插入以下代码段:
. . .
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)
}
. . .
The function matches the first document where the text property is equal to the text
parameter. The update
document specifies that the completed
property be set to true
. If there’s an error in the FindOneAndUpdate()
operation, it will be returned by completeTask()
. Otherwise nil
is returned.
该函数与text属性等于text
参数的第一个文档匹配。 update
文档指定将completed
属性设置为true
。 如果FindOneAndUpdate()
操作中有错误,它将由completeTask()
返回。 否则返回nil
。
Next, let’s add a new done
command to your CLI program that marks a task as completed:
接下来,让我们向CLI程序添加一个新的done
命令,将一个任务标记为已完成:
. . .
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)
}
}
. . .
You use the argument passed to the done
command to find the first document whose text
property matches. If found, the completed
property on the document is set to true
.
您可以使用传递给done
命令的参数来查找text
属性匹配的第一个文档。 如果找到,则文档上的completed
属性设置为true
。
Save and exit your file.
保存并退出文件。
Then run your program with the done
command:
然后使用done
命令运行程序:
- go run main.go done "Learn Go" 去运行main.go完成“学习去”
If you use the all
command again, you will notice that the task that was marked as completed is now printed with green.
如果再次使用all
命令,则会注意到标记为已完成的任务现在以绿色打印。
- go run main.go all 去运行main.go all
Sometimes, you only want to view tasks that have not yet been done. We’ll add that feature next.
有时,您只想查看尚未完成的任务。 接下来,我们将添加该功能。
步骤6 —仅显示待处理任务 (Step 6 — Displaying Pending Tasks Only)
In this step, you’ll incorporate code to retrieve pending tasks from the database using the MongoDB driver. Pending tasks are those whose completed
property is set to false
.
在此步骤中,您将合并代码以使用MongoDB驱动程序从数据库中检索待处理的任务。 待完成的任务是那些其completed
属性设置为false
。
Let’s add a new function that retrieves tasks that have not been completed yet. Open your main.go
file:
让我们添加一个新函数,以检索尚未完成的任务。 打开您的main.go
文件:
- nano main.go 纳米main.go
Then add this snippet following the completeTask
function:
然后在completeTask
函数之后添加此代码段:
. . .
func getPending() ([]*Task, error) {
filter := bson.D{
primitive.E{Key: "completed", Value: false},
}
return filterTasks(filter)
}
. . .
You create a filter using the bson
and primitive
packages from the MongoDB driver, which will match documents whose completed
property is set to false
. The slice of pending tasks is then returned to the caller.
您可以使用MongoDB驱动程序中的bson
和primitive
包创建过滤器,该过滤器将匹配其completed
属性设置为false
文档。 然后,待处理的任务片将返回给调用方。
Instead of creating a new command to list pending tasks, let’s make it the default action when running the program without any commands. You can do this by adding an Action
property to the program as follows:
让我们将其设置为不带任何命令运行程序时的默认操作,而不是创建一个新命令来列出未决任务。 您可以通过将Action
属性添加到程序中来做到这一点,如下所示:
. . .
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)
},
},
. . .
The Action
property performs a default action when the program is executed without any subcommands. This is where logic for listing pending tasks is placed. The getPending()
function is called and the resulting tasks are printed to the standard output using printTasks()
. If there are no pending tasks, a prompt is displayed instead, encouraging the user to add a new task using the add
command.
在没有任何子命令的情况下执行程序时, Action
属性将执行默认操作。 这是列出挂起的任务的逻辑所在。 getPending()
函数,并使用printTasks()
将生成的任务打印到标准输出。 如果没有待处理的任务,则会显示提示,鼓励用户使用add
命令添加新任务。
Save and exit your file.
保存并退出文件。
Running the program now without adding any commands will list all pending tasks in the database:
现在运行该程序而不添加任何命令将列出数据库中所有待处理的任务:
- go run main.go 去运行main.go
You’ll see the following output:
您将看到以下输出:
Output
1: Read a book
Now that you can list incomplete tasks, let’s add another command that allows you to view completed tasks only.
现在您可以列出未完成的任务,让我们添加另一个命令,该命令仅允许您查看已完成的任务。
步骤7 —显示完成的任务 (Step 7 — Displaying Finished Tasks)
In this step, you’ll add a new finished
subcommand that fetches completed tasks from the database and displays them on the screen. This involves filtering and returning tasks whose completed
property is set to true
.
在此步骤中,您将添加一个新的finished
子命令,该命令将从数据库中获取已完成的任务并将其显示在屏幕上。 这涉及过滤和返回其completed
属性设置为true
任务。
Open your main.go
file:
打开您的main.go
文件:
- nano main.go 纳米main.go
Then add in the following code at the end of your file:
然后在文件末尾添加以下代码:
. . .
func getFinished() ([]*Task, error) {
filter := bson.D{
primitive.E{Key: "completed", Value: true},
}
return filterTasks(filter)
}
. . .
Similar to the getPending()
function, you’ve added a getFinished()
function that returns a slice of completed tasks. In this case, the filter has the completed
property set to true
so only the documents that match this condition will be returned.
与getPending()
函数类似,您添加了一个getFinished()
函数,该函数返回一部分已完成的任务。 在这种情况下,过滤器会将completed
属性设置为true
因此仅返回与此条件匹配的文档。
Next, create a finished
command that prints all completed tasks:
接下来,创建一个finished
命令,打印所有完成的任务:
. . .
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)
}
}
. . .
The finished
command retrieves tasks whose completed
property is set to true
via the getFinished()
function created here. It then passes it to the printTasks
function so that they are printed to the standard output.
该finished
命令检索任务,其completed
属性设置为true
通过getFinished()
在这里创建功能。 然后将其传递给printTasks
函数,以便将它们打印到标准输出。
Save and exit your file.
保存并退出文件。
Run the following command:
运行以下命令:
- go run main.go finished 去运行main.go完成
You’ll see the following output:
您将看到以下输出:
Output
1: Learn Go
In the final step, you’ll give users the option to delete tasks from the database.
在最后一步,您将为用户提供从数据库中删除任务的选项。
第8步-删除任务 (Step 8 — Deleting a Task)
In this step, you’ll add a new delete
subcommand to allow users to delete a task from the database. To delete a single task, you’ll use the collection.DeleteOne()
method from the MongoDB driver. It also relies on a filter to match the document to delete.
在此步骤中,您将添加一个新的delete
子命令,以允许用户从数据库中删除任务。 要删除单个任务,您将使用MongoDB驱动程序中的collection.DeleteOne()
方法。 它还依赖于筛选器来匹配要删除的文档。
Open your main.go
file once more:
再次打开您的main.go
文件:
- nano main.go 纳米main.go
Add this deleteTask
function to delete tasks from the database straight after your getFinished
function:
添加以下deleteTask
函数可在getFinished
函数之后getFinished
从数据库中删除任务:
. . .
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
}
. . .
This deleteTask
method takes a string argument that represents the task item to be deleted. A filter is constructed to match the task item whose text
property is set to the string argument. You pass the filter to the DeleteOne()
method that matches the item in the collection and deletes it.
此deleteTask
方法采用一个字符串参数,该参数表示要删除的任务项。 构造一个过滤器以匹配其text
属性设置为string参数的任务项。 您将筛选器传递DeleteOne()
集合中的项目匹配的DeleteOne()
方法,然后将其删除。
You can check the DeletedCount
property on the result from the DeleteOne
method to confirm if any documents were deleted. If the filter is unable to match a document to be deleted, the DeletedCount
will be zero and you can return an error in that case.
您可以在DeleteOne
方法的结果上检查DeletedCount
属性,以确认是否删除了任何文档。 如果过滤器无法匹配要删除的文档,则DeletedCount
将为零,并且在这种情况下可以返回错误。
Now add a new rm
command as highlighted:
现在添加一个新的rm
命令,突出显示:
. . .
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)
}
}
. . .
Just as with all the other subcommands added previously, the rm
command uses its first argument to match a task in the database and deletes it.
就像先前添加的所有其他子命令一样, rm
命令使用其第一个参数来匹配数据库中的任务并将其删除。
Save and exit your file.
保存并退出文件。
You can list pending tasks by running your program without passing any subcommands:
您可以通过运行程序列出挂起的任务,而无需传递任何子命令:
- go run main.go 去运行main.go
Output
1: Read a book
Running the rm
subcommand on the "Read a book"
task will delete it from the database:
在"Read a book"
任务上运行rm
子命令会将其从数据库中删除:
- go run main.go rm "Read a book" 去运行main.go rm“读一本书”
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:
如果再次列出所有待处理的任务,您会注意到"Read a book"
任务不再出现,而是显示添加新任务的提示:
- go run main.go 去运行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.
您已经成功创建了任务管理器命令行程序,并了解了在此过程中使用MongoDB Go驱动程序的基础知识。
Be sure to check out the full documentation for the MongoDB Go Driver at GoDoc to learn more about the features that using the driver provides. The documentation that describes using aggregations or transactions may be of particular interest to you.
请确保在GoDoc上查看MongoDB Go驱动程序的完整文档,以了解有关使用该驱动程序提供的功能的更多信息。 您可能会对描述使用聚合或事务的文档特别感兴趣。
The final code for this tutorial can be viewed in this GitHub repo.
可以在此GitHub存储库中查看本教程的最终代码。