首页
Preview

如何在MongoDB中使用Golang结构体

为了演示如何使用 Golang 结构体和 MongoDB,我创建了一个简单的电子邮件自动回复器作为示例。我们有一些联系人,我们希望向他们发送电子邮件。此外,我们有要在特定时间发送的电子邮件,带有主题和内容。最后,我们还有一个将联系人与电子邮件连接起来的序列。

通过使用 Golang 结构体,我们可以最小化代码中对 BSON 的使用,并增加来自数据库查询结果的可用性。本文可以视为之前一篇文章的续篇,该文章名为 “如何使用 Golang 和 MongoDB.”

接下来,我将逐个解释演示代码的每个部分。

导入相关包

首先,我们将导入必要的 Golang 包。

package main

import (
	"context"
	"fmt"
	"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"

	"time"
)

我们需要导入这些库来执行以下操作:

  • context 是必要的,用于 创建 MongoDB 操作的上下文
  • bson 用于序列化发送到 MongoDB 服务器的数据。
  • mongo 用于连接 MongoDB 服务器并设置数据库和集合。

创建结构体

与我们在 之前的文章 中使用 bson 序列化不同,这里的想法是使用 Golang 结构体。这些结构体需要使用 bson 注释才能正常工作。

type Contact struct {
	ID    primitive.ObjectID `bson:"_id,omitempty"`
	Name  string             `bson:"name,omitempty"`
	Email string             `bson:"email,omitempty"`
	Tags  []string           `bson:"tags,omitempty"`
}

type Email struct {
	ID       primitive.ObjectID `bson:"_id,omitempty"`
	SendDate time.Time          `bson:"senddate,omitempty"`
	Subject  string             `bson:"subject,omitempty"`
	Content  string             `bson:"content,omitempty"`
}

type Sequence struct {
	ID        primitive.ObjectID   `bson:"_id,omitempty"`
	Emails    []interface{}        `bson:"emails,omitempty"`
	Receivers []primitive.ObjectID `bson:"receivers,omitempty"`
}

在上面的代码片段中,我们可以看到各种 bson 注释。

这些注释都遵循相同的模式:

bson:"<fieldname>,omitempty" 其中 <fieldname> 是数据库中实际字段名称,omitempty 表示如果没有给出值,则省略该字段。

请注意,注释也在 下降的重音符号 之间,并且 没有空格

你可以使用其他结构标记(或根本不使用标记)代替 omitempty。请在 此处 了解更多信息。

在第 2、9 和 16 行中,我们将 ID 字段的类型设置为 primitive.ObjectID。通过这样做,我们告诉 BSON 这些字段将是实际的 MongoDB ObjectIDs

此外,即使我们在定义这些结构体的实例时省略了 _id 数据库字段,它们也将始终填充。MongoDB 服务器将填充这些字段并为它们提供唯一的 ObjectIDs。我们将使用这些 ObjectIDs 来定位集合中的单个文档。

在第 5 行中,我们创建一个字符串切片。该切片将自动转换为 bson.A(BSON 数组)。

在第 10 行中,你可以看到还可以使用 time.Time 字段。

在第 17-18 行中,我们将在数据库中放置对其他文档的引用。这些引用将是 ObjectIDs 的列表。在这里,我向你展示了两种方法。

你可以使用 []interface{},这是你可以使用的最通用的方法。危险是你可以在此处放置任何值-包括整数、字符串等。

另一种可能性是使用 []primitive.ObjectID。这是一种声明类型的特定方法。现在只有 ObjectIDs 将被接受。这种方法的问题是我们稍后需要编写更多代码。

MongoDB 连接和数据库设置

main() 函数中要做的第一件事是连接到 MongoDB 服务器并创建包含文档的集合的数据库。

opt := options.Client().ApplyURI("mongodb://localhost:27017")
client, err := mongo.NewClient(opt)
if err != nil {
	panic(err)
}

ctx := context.TODO()

err = client.Connect(ctx)
if err != nil {
	panic(err)
}

defer client.Disconnect(ctx)

autoresponderDB := client.Database("autoresponder")
contactsCollection := autoresponderDB.Collection("contacts")
emailsCollection := autoresponderDB.Collection("emails")
sequencesCollection := autoresponderDB.Collection("sequences")

defer contactsCollection.Drop(ctx)
defer emailsCollection.Drop(ctx)
defer sequencesCollection.Drop(ctx)

在第 1 和 2 行中,我们使用 NewClient() 和正确的 URI 创建了 MongoDB 的新客户端。请注意,我的 MongoDB 在本地主机上运行,端口为 27017。未设置用户名或密码,因为这是我的测试服务器。

在第 7 行中,我们使用 context.TODO() 创建上下文。这是可能的最基本的上下文。

在第 9 行中,我们让客户端使用给定的上下文连接到 MongoDB 服务器。

在第 14 行中,我们 defer 断开与 MongoDB 服务器的连接。鉴于 Disconnect() 函数被推迟执行,它将在运行 main() 函数中的所有其他语句之后执行。

在第 16 行中,我们创建了一个名为 autoresponder 的数据库。

在第 17-19 行中,我们在此数据库中创建了三个集合。这些集合分别称为 contactsemailssequences

在第 21-23 行中,我们 defer 删除数据库集合。这仅适用于此处的示例,因为我不希望每次运行代码并测试新内容时都填满数据库。如果你希望保留数据库中的数据,请删除这些行。

数据库插入

将联系人插入数据库

让我们创建一些模拟联系人并将它们插入 contactsCollection

manyContacts := []interface{}{
		Contact{
			Name:  "John Doe",
			Email: "john@doe.com",
			Tags:  []string{"Lead", "Subscriber"},
		},
		Contact{
			Name:  "Mia May",
			Email: "mm@example.com",
			Tags:  []string{"Lead", "Customer"},
		},
		Contact{
			Name:  "Lia Shmia",
			Email: "lia@shmia.com",
			Tags:  []string{"Lead", "Customer"},
		},
}

insertResult, err := contactsCollection.InsertMany(ctx, manyContacts)
if err != nil {
  panic(err)
}

contactIDs := insertResult.InsertedIDs

var contactIDs_ []primitive.ObjectID
for _, id := range contactIDs {
  contactIDs_ = append(contactIDs_, id.(primitive.ObjectID))
}

fmt.Printf("Inserted %v %T\n", contactIDs_, contactIDs_)

在第 1-17 行中,我们创建了三个联系人(MongoDB 术语中的“文档”),方式与创建常规结构体相同。请注意,这与我们在 这里 使用 BSON 不同(请向下滚动到页面的中间)。

在第 19 行中,我们使用 InsertMany() 将这些联系人插入 contact 集合,因为我们希望一次插入多个文档。第24行,我们使用第19行的结果insertResult和属性InsertedIDs获取插入联系人的ID。

第26行,我们创建一个类型为[]primitive.ObjectID的切片contactIDs_

在第17至29行,我们循环遍历返回的contactIDs,将每个元素转换为primitive.ObjectID并将其附加到contactIDs_。显然,无法从[]interface{}转换为[]primitive.ObjectID;这就是为什么我们使用for循环的原因。

请注意,我们没有自己定义ID。在插入文档时,MongoDB自动分配了ID。这些ID保存在实际数据库的_id字段中,也可以使用ID属性在结构中访问。

在第31行,我们打印出contactIDs_切片,以证明我们插入了三个联系人并且类型是正确的。

我们还可以使用MongoDB Compass来检查此操作。

将电子邮件插入数据库

现在,让我们也将一些电子邮件插入到emailsCollection中。

manyEmails := []interface{}{
	Email{
		SendDate: time.Now(),
		Subject:  "[Newsletter] Thank you!",
		Content:  "Thank you for subscribing to our weekly newsletter!"},
	Email{
		SendDate: time.Now(),
		Subject:  "[Newsletter] The first email",
		Content:  "In this email we'll discuss..."},
	Email{
		SendDate: time.Now(),
		Subject:  "[Newsletter] The second email",
		Content:  "In the previous email I mentioned that..."},
}

insertResult2, err := emailsCollection.InsertMany(ctx, manyEmails)
if err != nil {
  panic(err)
}

emailIDs := insertResult2.InsertedIDs

fmt.Printf("Inserted %v %T\n", emailIDs, emailIDs)

这与插入联系人几乎相同。

要注意的是,在第3、7和11行,我们使用time.Now()作为变量值。

在第21行,我们从第16行的InsertMany()操作的返回结果中检索emailIDs

将序列插入数据库

最后,让我们也在sequencesCollection中插入一个序列。

oneSequence := Sequence{Emails: emailIDs, Receivers: contactIDs_}

insertResult3, err := sequencesCollection.InsertOne(ctx, oneSequence)
if err != nil {
    panic(err)
}
fmt.Println("Inserted", insertResult3.InsertedID, insertResult3.InsertedID)

在第1行,我们使用Sequence结构创建一个序列。在此结构中,我们将从先前的插入结果中检索的emailIDscontactIDS_放入其中。

在第3行,我们使用InsertOne()而不是我们之前使用的InsertMany()

数据库查询

查找特定联系人

在下面的代码中,我演示了如何使用自定义过滤器查找联系人。

var contacts []Contact
contactCursor, err := contactsCollection.Find(ctx, bson.M{"tags": "Customer"})
if err != nil {
	panic(err)
}

if err = contactCursor.All(ctx, &contacts); err != nil {
	panic(err)
}

for _, c := range contacts {
	fmt.Printf("%v of type %T\n", c.Name, c.Name)
	fmt.Printf("%v of type %T\n", c.Email, c.Email)
	fmt.Printf("%v of type %T\n", c.Tags, c.Tags)
}

在第2行,我们使用Find()函数查找满足过滤器bson.M{"tags": "Customer"}的所有contacts。使用此过滤器,我们将找到所有在其标记列表中具有"Customer"的联系人。

当然,你可以根据自己的需要调整此过滤器。如果要查找所有联系人,则可以使用bson.M{}。或者,如果你希望找到将mm@example.com作为电子邮件地址的联系人,则可以编写bson.M{"email":"mm@example.com"}

此外,你可以使用bson.D格式而不是使用bson.M格式作为过滤器。更多信息请参见此处

在第7行,查询结果使用All()加载到名为contacts[]Contact切片中。

在第11至15行,我们只是打印出一些Contact结构的属性,就像我们处理任何结构一样。

这样,我们可以最大限度地利用Golang结构并减少对BSON的使用来过滤查询。

查找序列并检索联系人和电子邮件

以下,我们从sequencesCollection中恢复序列。我们从此序列中获取电子邮件ID和接收者的ID。然后,使用这些ID检索相应的电子邮件和联系人。

var sequences []Sequence
sequenceCursor, err := sequencesCollection.Find(ctx, bson.M{})
if err != nil {
    panic(err)
}

if err = sequenceCursor.All(ctx, &sequences); err != nil {
    panic(err)
}
fmt.Println(sequences[0].ID)

emailSequence := sequences[0].Emails
receivers := sequences[0].Receivers

for ix, e := range emailSequence {
    var result Email
    err := emailsCollection.FindOne(ctx, bson.M{"_id": e}).Decode(&result)
    if err != nil {
        panic(err)
    }
    for _, e2 := range receivers {
        var result2 Contact
        err := contactsCollection.FindOne(ctx, bson.M{"_id": e2}).Decode(&result2)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Sending email %v with subject %v to %v\n", ix, result.Subject, result2.Email)

    }

}

在第10至13行中,我们使用[0],因为只返回了一个序列。

在第17行和第23行中,我们使用FindOne()bson.M{"_id": <id>}一起,仅基于其ObjectID(在<id>中)查找并过滤电子邮件和联系人中的一个特定文档。由于只有一个结果,我们也可以使用Decode()而不是All()将结果提取到EmailContact结构中。

完整的代码

下面是完整的代码。在执行代码之前,请确保在mongodb://localhost:27017上运行MongoDB服务器-或更改URI。

package main

import (
	"context"
	"fmt"
	"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"

	"time"
)

type Contact struct {
	ID    primitive.ObjectID `bson:"_id,omitempty"`
	Name  string             `bson:"name,omitempty"`
	Email string             `bson:"email,omitempty"`
	Tags  []string           `bson:"tags,omitempty"`
}

type Email struct {
	ID       primitive.ObjectID `bson:"_id,omitempty"`
	SendDate time.Time          `bson:"senddate,omitempty"`
	Subject  string             `bson:"subject,omitempty"`
	Content  string             `bson:"content,omitempty"`
}

type Sequence struct {
	ID        primitive.ObjectID   `bson:"_id,omitempty"`
	Emails    []interface{}        `bson:"emails,omitempty"`
	Receivers []primitive.ObjectID `bson:"receivers,omitempty"`
}

func main() {
	opt := options.Client().ApplyURI("mongodb://localhost:27017")
	client, err := mongo.NewClient(opt)
	if err != nil {
		panic(err)
	}

	ctx := context.TODO()

	err = client.Connect(ctx)
	if err != nil {
		panic(err)
	}

	defer client.Disconnect(ctx)



	autoresponderDB := client.Database("autoresponder")
	contactsCollection := autoresponderDB.Collection("contacts")
	emailsCollection := autoresponderDB.Collection("emails")
	sequencesCollection := autoresponderDB.Collection("sequences")



	defer contactsCollection.Drop(ctx)
	defer emailsCollection.Drop(ctx)
	defer sequencesCollection.Drop(ctx)



	manyContacts := []interface{}{
		Contact{
			Name:  "John Doe",
			Email: "john@doe.com",
			Tags:  []string{"Lead", "Subscriber"},
		},
		Contact{
			Name:  "Mia May",
			Email: "mm@example.com",
			Tags:  []string{"Lead", "Customer"},
		},
		Contact{
			Name:  "Lia Shmia",
			Email: "lia@shmia.com",
			Tags:  []string{"Lead", "Customer"},
		},
	}

	insertResult, err := contactsCollection.InsertMany(ctx, manyContacts)
	if err != nil {
		panic(err)
	}

	contactIDs := insertResult.InsertedIDs

	var contactIDs_ []primitive.ObjectID
	for _, id := range contactIDs {
		contactIDs_ = append(contactIDs_, id.(primitive.ObjectID))
	}

	fmt.Printf("Inserted %v %T\n", contactIDs_, contactIDs_)



	manyEmails := []interface{}{
		Email{
			SendDate: time.Now(),
			Subject:  "[Newsletter] Thank you!",
			Content:  "Thank you for subscribing to our weekly newsletter!"},
		Email{
			SendDate: time.Now(),
			Subject:  "[Newsletter] The first email",
			Content:  "In this email we'll discuss..."},
		Email{
			SendDate: time.Now(),
			Subject:  "[Newsletter] The second email",
			Content:  "In the previous email I mentioned that..."},
	}

	insertResult2, err := emailsCollection.InsertMany(ctx, manyEmails)
	if err != nil {
		panic(err)
	}

	emailIDs := insertResult2.InsertedIDs

	fmt.Printf("Inserted %v %T\n", emailIDs, emailIDs)



	oneSequence := Sequence{Emails: emailIDs, Receivers: contactIDs_}

	insertResult3, err := sequencesCollection.InsertOne(ctx, oneSequence)
	if err != nil {
		panic(err)
	}
	fmt.Println("Inserted", insertResult3.InsertedID, insertResult3.InsertedID)



	var contacts []Contact
	contactCursor, err := contactsCollection.Find(ctx, bson.M{"tags": "Customer"})
	if err != nil {
		panic(err)
	}

	if err = contactCursor.All(ctx, &contacts); err != nil {
		panic(err)
	}
	for _, c := range contacts {
		fmt.Printf("%v of type %T\n", c.Name, c.Name)
		fmt.Printf("%v of type %T\n", c.Email, c.Email)
		fmt.Printf("%v of type %T\n", c.Tags, c.Tags)
	}



	var sequences []Sequence
	sequenceCursor, err := sequencesCollection.Find(ctx, bson.M{})
	if err != nil {
		panic(err)
	}

	if err = sequenceCursor.All(ctx, &sequences); err != nil {
		panic(err)
	}
	fmt.Println(sequences[0].ID)



	emailSequence := sequences[0].Emails
	receivers := sequences[0].Receivers



	for ix, e := range emailSequence {
		var result Email
		err := emailsCollection.FindOne(ctx, bson.M{"_id": e}).Decode(&result)
		if err != nil {
			panic(err)
		}

		for _, e2 := range receivers {
			var result2 Contact
			err := contactsCollection.FindOne(ctx, bson.M{"_id": e2}).Decode(&result2)
			if err != nil {
				panic(err)
			}
			fmt.Printf("Sending email %v with subject %v to %v\n", ix, result.Subject, result2.Email)

		}

	}

	// time.Sleep(40 * time.Second)

}

参考资料

MongoDB发布的“Quick Start: Golang & MongoDB — Modeling Documents with Go Data Structures”

MongoDB文档中关于“Work with BSON”的说明

StackOverflow上关于“bson.D vs bson.M for find queries”的问题

译自:https://betterprogramming.pub/how-to-use-golang-structs-with-mongodb-f1772e4a1da3

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
菜鸟一只
你就是个黄焖鸡,又黄又闷又垃圾。

评论(0)

添加评论