在Golang中使用MongoDB的一对多关系与嵌入式文档

358 阅读1分钟

在这个例子中,我们要把多个文档放在另一个文档中。这在MongoDB中被称为 "嵌入式文档模型"。我们的例子使用 "tokens "集合。每个token文档将包含零个或多个 "权限 "文档在其中。

数据库内容

准备工作

db.createCollection("tokens")
db.tokens.createIndex({"uuid":1},{unique:true,name:"UQ_uuid"})

令牌数据

[
  {
    "_id": {
      "$oid": "6050986944799a96c0f7b1f4"
    },
    "uuid": "7d5e2a9f-b6b9-4356-af3c-be772b3cf821",
    "type": "access token",
    "permissions": [
      {
        "uuid": "f2222ef5-7f9b-46fa-a7e6-c88b4126e9fe",
        "type": "accounts",
        "actions": [
          "read",
          "write"
        ]
      },
      {
        "uuid": "2cffcc36-0efc-4d09-bb0a-a36f2b6433be",
        "type": "balances",
        "actions": [
          "read"
        ]
      }
    ]
  },
  {
    "_id": {
      "$oid": "605098e921410aa61d2be92b"
    },
    "uuid": "38a934d7-73cf-4874-8af1-874d4c03335f",
    "type": "refresh token",
    "permissions": null
  }
]

存储

模型

package storage

import "context"

type TokenStorer interface {
	Insert(ctx context.Context, token Token) error
	Find(ctx context.Context, uuid string) (Token, error)
}

type Token struct {
	ID          string       `bson:"_id,omitempty"`
	UUID        string       `bson:"uuid"`
	Type        string       `bson:"type"`
	Permissions []Permission `bson:"permissions"`
}

type Permission struct {
	UUID    string   `bson:"uuid"`
	Type    string   `bson:"type"`
	Actions []string `bson:"actions"`
}

存储器

package mongodb

import (
	"context"
	"log"
	"time"

	"github.com/you/mongo/internal/pkg/domain"
	"github.com/you/mongo/internal/pkg/storage"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

var _ storage.TokenStorer = TokenStorage{}

type TokenStorage struct {
	Database *mongo.Database
	Timeout  time.Duration
}

func (t TokenStorage) Insert(ctx context.Context, token storage.Token) error {
	ctx, cancel := context.WithTimeout(ctx, t.Timeout)
	defer cancel()

	if _, err := t.Database.Collection("tokens").InsertOne(ctx, token); err != nil {
		log.Println(err)

		if er, ok := err.(mongo.WriteException); ok && er.WriteErrors[0].Code == 11000 {
			return domain.ErrConflict
		}

		return domain.ErrInternal
	}

	return nil
}

func (t TokenStorage) Find(ctx context.Context, uuid string) (storage.Token, error) {
	ctx, cancel := context.WithTimeout(ctx, t.Timeout)
	defer cancel()

	var tok storage.Token

	qry := bson.M{"uuid": uuid}

	err := t.Database.Collection("tokens").FindOne(ctx, qry).Decode(&tok)
	if err != nil {
		log.Println(err)

		if err == mongo.ErrNoDocuments {
			return storage.Token{}, domain.ErrNotFound
		}

		return storage.Token{}, domain.ErrInternal
	}

	return tok, nil
}

测试

插入

storer := TokenStorage{Database: database, Timeout: time.Second * 5}

if err := storer.Insert(context.Background(), Token{
    UUID: uuid.New().String(),
    Type: "access token",
    Permissions: []Permission{
        {
            UUID:    uuid.New().String(),
            Type:    "accounts",
            Actions: []string{"read", "write"},
        },
        {
            UUID:    uuid.New().String(),
            Type:    "balances",
            Actions: []string{"read"},
        },
    },
}); err != nil {
    log.Fatalln(err)
}

查找

storer := TokenStorage{Database: database, Timeout: time.Second * 5}

token, err := storer.Find(context.Background(), "7d5e2a9f-b6b9-4356-af3c-be772b3cf821")
if err != nil {
    log.Fatalln(err)
}

data, _ := json.MarshalIndent(token, "", "  ")
log.Println(string(data))
{
  "ID": "6050986944799a96c0f7b1f4",
  "UUID": "7d5e2a9f-b6b9-4356-af3c-be772b3cf821",
  "Type": "access token",
  "Permissions": [
    {
      "UUID": "f2222ef5-7f9b-46fa-a7e6-c88b4126e9fe",
      "Type": "accounts",
      "Actions": [
        "read",
        "write"
      ]
    },
    {
      "UUID": "2cffcc36-0efc-4d09-bb0a-a36f2b6433be",
      "Type": "balances",
      "Actions": [
        "read"
      ]
    }
  ]
}