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

128 阅读1分钟

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

数据库内容

准备工作

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

令牌数据

[
  {
    "_id": {
      "$oid": "6050b16571b3921d9c825d66"
    },
    "uuid": "2f249152-6f8f-4873-81ef-ae6d7de0b582",
    "type": "access token",
    "permission": {
      "uuid": "33e5140b-80ac-42ff-b6aa-59e1f855847a",
      "type": "accounts",
      "actions": [
        "read",
        "write"
      ]
    }
  },
  {
    "_id": {
      "$oid": "6050b16571b3921d9c825d67"
    },
    "uuid": "e2513685-bfed-4962-a897-230eb149fbac",
    "type": "refresh token",
    "permission": {
      
    }
  }
]

存储

模型

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"`
	Permission Permission `bson:"permission"`
}

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

存储器

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",
    Permission: Permission{
        UUID:    uuid.New().String(),
        Type:    "accounts",
        Actions: []string{"read", "write"},
    },
}); err != nil {
    log.Fatalln(err)
}

查找

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

token, err := storer.Find(context.Background(), "2f249152-6f8f-4873-81ef-ae6d7de0b582")
if err != nil {
    log.Fatalln(err)
}

data, _ := json.MarshalIndent(token, "", "  ")
log.Println(string(data))
{
  "ID": "6050b16571b3921d9c825d66",
  "UUID": "2f249152-6f8f-4873-81ef-ae6d7de0b582",
  "Type": "access token",
  "Permission": {
    "UUID": "33e5140b-80ac-42ff-b6aa-59e1f855847a",
    "Type": "accounts",
    "Actions": [
      "read",
      "write"
    ]
  }
}