MongoDB是一个非常强大的数据库,它提供了一个非常简单的接口,可以让我们在Go中使用它。 前几天用gin+gorm实现一个小功能,根据操作的方法,发现很容易实现通用的CRUD操作。 比如批量查询数据时,可以定义一个interface或具体结构体去绑定结果集:

# 返回值也可以定义为interface{}
func FindAllAd(filter interface{}, sort interface{}) ([]model.Ad, error) {
    cursor, err := model.Mongo.Collection((&model.Ad{}).CollectionName()).Find(context.Background(), filter, options.Find().SetSort(sort))
    if err != nil {
        return nil, err
    }
    defer cursor.Close(context.Background())
    var results []model.Ad
    for cursor.Next(context.Background()) {
        # 根据返回值的类型,可以定义为interface{}
        var result model.Ad
        err := cursor.Decode(&result)
        if err != nil {
            return nil, err
        }
        results = append(results, result)
    }
    return results, nil
}

上述的代码里,加入返回值是interface{},那么返回结果的格式是这样的:

[[{Key:_id Value:3644d977-d2ff-430e-a8ca-b19c643fcb91} {Key:title Value:重版来袭} {Key:url Value:} {Key:type Value:0} {Key:sort Value:0} {Key:create_at Value:1659175157} {Key:update_at Value:1659175157}]]

这样的格式不方便前端处理。

如果是绑定具体的结构体,那么返回结果的格式是这样的:

[{ID:3644d977-d2ff-430e-a8ca-b19c643fcb91 Title:重版来袭 Url: Type:0 Sort:0 CreateAt:1659175157 UpdateAt:1659175157}]

显然下面这种方式更加方便处理。 如果不做封装的情况下,需要在每个模型结构体去定义查询方法,那么就需要额外增加代码,通用型不强。 于是我就尝试用范型去封装通用查询方法:

package model

import (
    "context"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

type IMongo interface {
    CollectionName() string
}

type Dao[T IMongo] []T

func (d *Dao[T]) CollectionName() string {
    var m T
    return m.CollectionName()
}

// InsertOne 新增单条记录
func (d *Dao[T]) InsertOne(document T) (*mongo.InsertOneResult, error) {
    return Mongo.Collection(d.CollectionName()).InsertOne(context.Background(), document)
}

// FindAll 获取所有记录
func (d *Dao[T]) FindAll(filter interface{}, sort interface{}) ([]T, error) {
    cursor, err := Mongo.Collection(d.CollectionName()).Find(context.Background(), filter, options.Find().SetSort(sort))
    if err != nil {
        return nil, err
    }
    defer cursor.Close(context.Background())
    var results []T
    for cursor.Next(context.Background()) {
        var result T
        err := cursor.Decode(&result)
        if err != nil {
            return nil, err
        }
        results = append(results, result)
    }
    return results, nil
}

至于要额外封装IMongo的接口,主要考虑两个原因:

  • 每个模型结构体要额外定义一个返回文档名称的方法,作为操作目标;
  • 目前interface只能约束基本类型,但结构体不支持,比如:
    # 如果是结构体不支持约束
    type Number interface {
    int | int32 | int64 | float32 | float64
    }

    即使后面能支持约束,也不方便维护,因为每增加一个模型就要去修改这个约束接口。