在这篇文章中
- 发表于2021年8月21日
如何在MongoDB中使用Go
本文最初于2020年4月8日发表于DigitalOcean博客。一些小的细节已被修改,以使其符合最新的要求。
在依靠社区开发的解决方案多年后,MongoDB宣布他们正在为Go开发一个官方驱动程序。2019年3月,这个新驱动随着v1.0.0版本的发布达到了生产就绪的状态,并从那时起不断地更新。
与其他MongoDB官方驱动一样,Go驱动对Go编程语言来说是习以为常的,并提供了一种简单的方法来使用MongoDB作为Go程序的数据库解决方案。它与MongoDB的API完全集成,并公开了API的所有查询、索引和聚合功能,以及其他高级功能。与第三方库不同的是,它将得到MongoDB工程师的全面支持,因此你可以确信它将得到持续的发展和维护。
在本教程中,你将开始使用官方的MongoDB Go驱动。你将安装该驱动,连接到MongoDB数据库,并执行若干CRUD操作。在此过程中,你将创建一个简单的任务管理器程序,通过命令行管理任务。
前提条件
对于本教程,你需要在机器上安装最新版本的Go和MongoDB,MongoDB 2.6或更高版本是MongoDB Go驱动支持的最低版本。本指南中显示的命令和代码是用Go v1.14.1和MongoDB v3.6.3测试的。
安装 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 文件将看起来像这样。
go.mod
module github.com/<your_username>/tasker
go 1.14
使用下面的命令将MongoDB Go驱动作为你项目的依赖项。
$ go get go.mongodb.org/mongo-driver
你会看到如下的输出。
输出
go: downloading go.mongodb.org/mongo-driver v1.3.2
go: go.mongodb.org/mongo-driver upgrade => v1.3.2
在这一点上,你的go.mod 文件将看起来像这样。
go.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"
)
在这里,你添加了 mongo和 options包,这是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 options包的文档,看看有哪些配置选项可用。
上下文就像一个超时或最后期限,表明一个操作应该何时停止运行并返回。它有助于防止生产系统在特定操作运行缓慢时出现性能下降。在这段代码中,你通过context.TODO() ,表示你现在不确定要使用什么上下文,但你计划在将来添加一个。
接下来,让我们确保你的MongoDB服务器被找到并使用Ping 方法成功连接。在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服务器。在接下来的步骤中,你将继续创建你的任务管理器程序。
创建一个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
你会看到下面的输出。
输出
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中的任务,从而提高程序的效用。
创建一个任务
在这一步中,你将使用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 slice里面。每个命令都包括一个名称、使用说明和动作。这是命令执行时要运行的代码。
在这段代码中,你收集了add 的第一个参数,并使用它来设置一个新的Task 实例的Text 属性,同时为其他属性分配了适当的默认值。新的任务随后被传递给createTask ,它将任务插入到数据库中,如果一切顺利,则返回nil ,使命令退出。
保存并退出你的文件。通过使用add 命令添加一些任务来测试它。如果成功,你会看到屏幕上没有打印出错误。
$ go run main.go add "Learn Go"
$ go run main.go add "Read a book"
现在你可以成功地添加任务,让我们实现一种方法来显示你已经添加到数据库的所有任务。
列出所有任务
列出一个集合中的文件可以用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 slice被返回。
在你创建一个列出所有任务的命令之前,让我们创建一个辅助函数,该函数获取tasks ,并打印到标准输出。你将使用 color包来对输出进行着色。
在你使用这个包之前,请先安装它。
$ go get gopkg.in/gookit/color.v1
go: 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 slice中创建一个新的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
它将列出到目前为止你所添加的所有任务。
main.go
1: Learn Go
2: Read a book
现在你可以查看数据库中的所有任务了,让我们在下一步中添加将任务标记为已完成的功能。
完成一项任务
在这一步中,你将创建一个新的子命令,名为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

有时,你只想查看尚未完成的任务。我们接下来会增加这个功能。
只显示未完成的任务
在这一步,你将加入代码,使用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
你会看到下面的输出。
输出
1: Read a book
现在你可以列出未完成的任务了,让我们再添加一条命令,只允许你查看已完成的任务。
显示已完成的任务
在这一步,你将添加一个新的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
你会看到下面的输出。
输出
1: Learn Go
在最后一步,你将让用户选择从数据库中删除任务。
删除一个任务
在这一步,你将添加一个新的delete 子命令,允许用户从数据库中删除一个任务。要删除一个任务,你将使用MongoDB驱动的collection.DeleteOne() 方法。它也依赖于一个过滤器来匹配要删除的文件。
再一次打开你的main.go 文件。
$ nano main.go
在你的getFinished 函数之后直接添加这个deleteTask 函数来从数据库中删除任务。
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
输出
1: Read a book
对"Read a book" 任务运行rm 子命令将从数据库中删除它。
$ go run main.go rm "Read a book"
如果你再次列出所有待办任务,你会注意到"Read a book" 任务不再出现,而是显示了一个添加新任务的提示。
$ go run main.go
输出
Nothing to see here
Run `add 'task'` to add a task
在这一步,你添加了一个从数据库中删除任务的功能。
总结
你已经成功地创建了一个任务管理器命令行程序,并在此过程中学习了使用MongoDB Go驱动的基本原理。