Go+MongoDB的微服務實戰

ice_moss發表於2022-04-12

介紹

本文章我們來學習一下使用Go對MongoDB資料庫的實戰,這裡我們將以微信小程式中的三個微服務為背景,分別來實現對這三個微服務的CRUD實戰,來體驗Go和MongoDB在實際開發中的魅力

環境配置

開發環境:VScode

golang:go語言官方

MongoDB:使用driver@v1.8.4/mongo">MongoDB官方包

微信小程式介紹

該小程式是一款租車小程式dome,使用GRPC框架引領全棧開發,前端:typescrtp+wxml+wxss,後端:Go,GRPC框架,資料庫:MongoDB

還在學習中

微服務的介紹與CRUD的實現

微服務就是把後端服務拆分為多個微小服務,來防止各服務之間的領域入侵,更能有效的開發和後期維護等

微服務一:使用者登入實戰(auth)

先來看看微信小程式登入的時序圖:

這裡的流程其實很清晰,我們看到小程式傳送code至開發者伺服器,接著開發者伺服器需要呼叫相關的方法和api去攜帶appid, appsecret和code上傳至微信相關服務去換取session_key和openid。

那麼重點來了:這裡我們不將openid直接與自定義登入態關聯,而是需要將我們拿到的openid進行儲存,拿出該openid在資料庫中的索引(統一叫id),與自定義登入態關聯,然後往下驗證。

所以這裡我們涉及的內容就是:

  1. 如何將openid存入資料庫中
  2. 如何建立openid的索引id
  3. 如何拿出索引id
  4. 如何給定id拿出openid

下面我們來實現:

這需要先了解一下MongoDB的索引,即:”_id”, 該欄位必須接受的是一個primitive.ObjectID型別的值,所以在使用是就需要涉及到型別轉換了。

我們直接將所有的型別轉換方法放入一個包中:objid

package objid

import (
    "coolcar/shared/id"
    "fmt"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

//ToAccount將 primitive.ObjectID轉換為string id
func ToAccountID(oid primitive.ObjectID) id.AccountID {
    return id.AccountID(oid.Hex())
}

這裡還有一個問題我們傳入的是一個openid是string型別,我們拿出的_id也需要轉換為string型別,返回出來,萬一我們把openid和_id弄反了怎麼辦,大家都是string,編輯器也不會提示,所以這裡需要做強化型別處理。

強型別化包:id

package id

//強型別化: AccountID定義account id物件型別
type AccountID string

func (a AccountID) String() string {
    return string(a)
}

這樣我們拿出的_id轉換成一個AccountID型別

接下來我們來回答這四個問題:

  1. 如何將openid存入資料庫中
  2. 如何建立openid的索引id
  3. 如何拿出索引id
  4. 如何給定id拿出openid

方法宣告:

func (*mongo.Collection).FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult

介紹:

FindOneAndUpdate 執行 findAndModify 命令以更新集合中的最多一個文件,並返回更新前的文件。

完整實現:見註釋

package dao

import (
    "context"
    "coolcar/shared/id"
    "coolcar/shared/mongo/objid"
    "fmt"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
  "go.mongodb.org/mongo-driver/bson/primitive"
)

const (
    IDFieldName = "_id"  //存入資料庫的欄位名   
    openidfield = "open_id"//定義一個 Mongo 型別
type Mongo struct {
    col *mongo.Collection
}

//初始化資料庫, 類似建構函式
func NewMongo(db *mongo.Database) *Mongo {
    return &Mongo{
        col: db.Collection("auth"),
    }
}

//將openID存入資料庫,返回對應_id給使用者
func (m *Mongo) ResolveAccountID(c context.Context, openID string) (id.AccountID, error) {
  //篩選器,以openID為篩選條件
    filter := bson.M{
        openidfield: openID,
    }
  //生成一個primitive.ObjectID型別作為文件索引
    var insertedID primitive.ObjectID
  //更新的資料
    updata := bson.M{
        "$setOnInsert": bson.M{
            IDFieldName: insertedID,
            openidfield: openID,
        },
    }
    //去查詢openID,如果查到的openID則將對應_id返回出來,沒有openID則插入我們固定的insertedID,然後將對應_id返回出來
    res := m.col.FindOneAndUpdate(c, filter, updata, options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After))
    //檢測是否返回成功
    if err := res.Err(); err != nil {
        return "", fmt.Errorf("cannot findOneAndUpdate: %v", err)
    }

  //解碼格式,我們在解碼的時候必須確定資料結構
    var row struct{
        ID primitive.ObjectID `bson:"_id"`
    }
    //解碼
    err := res.Decode(&row)
    if err != nil {
        return "", fmt.Errorf("cannot Decode result: %v", err)
    }
  //做型別轉換,再返回
    return objid.ToAccountID(row.ID), nil
}

這樣我們就完成了第一個登入服務的CRUD

接下來開始第二個服務的實戰吧!

微服務二:行程服務(trip)

在微服務二中我們需要做四件事情

  1. 建立行程
  2. 獲取單個行程
  3. 根據條件批次獲取行程
  4. 更新行程

在該服務中,我們同樣需要做型別轉換, 強型別化:

型別轉換:

package objid

import (
    "coolcar/shared/id"
    "fmt"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

//FromID將一個id轉換為Object id
func FromID(id fmt.Stringer) (primitive.ObjectID, error) {
    return primitive.ObjectIDFromHex(id.String())
}

//MustFromID將一個id轉換為Object id
func MustFromID(id fmt.Stringer) primitive.ObjectID {
    oid, err := FromID(id)
    if err != nil {
        panic(err)
    }
    return oid
}

//ToAccount將 primitive.ObjectID轉換為string id
func ToAccountID(oid primitive.ObjectID) id.AccountID {
    return id.AccountID(oid.Hex())
}

//ToTripID將 primitive.ObjectID轉換為string id
func ToTripID(oid primitive.ObjectID) id.TripID {
    return id.TripID(oid.Hex())
}

強型別化:

package id

//強型別化: AccountID定義account id物件型別
type AccountID string

func (a AccountID) String() string {
    return string(a)
}

//TripID 定義一個trip id
type TripID string

func (t TripID) String() string {
    return string(t)
}

//Identity定義一個使用者身份
type IdentityID string

func (i IdentityID) String() string {
    return string(i)
}

//CarId定義一個車輛id
type CarId string

func (c CarId) String() string {
    return string(c)
}

這一節中我們將大量使用到MongoDB的知識,所以我們將一些可以程式碼作為公共程式碼(微服務三也會用到),這樣我們就可以將CRUD中的變數,常量全都提出。

mgo:

package mgo

import (
    "coolcar/shared/mongo/objid"
    "fmt"
    "time"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

const (
    IDFieldName        = "_id"
    UpdatedAtFieldName = "updatedat"
)

//ObjID defines the object field
type IDField struct {
    ID primitive.ObjectID `bson:"_id"`
}

//UpdatedAtField 定義一個時間篩選器
type UpdatedAtField struct {
    UpdatedAt int64 `bson:"updatedat"`
}

//NewObjectID 生成一個object id , NewObjID是一個函式
var NewObjID = primitive.NewObjectID

//NewObjIDWithValue 生成id 為下一個NewObjID,對id進一步包裝,
func NewObjIDWithValue(id fmt.Stringer) {
    NewObjID = func() primitive.ObjectID {
        return objid.MustFromID(id)
    }
}

//Updateda 返回一個合適的值,你賦值給它
var UpdatedAt = func() int64 {
    return time.Now().UnixNano() //當前時間取納秒
}

//Set return a $set updata document
func Set(V interface{}) bson.M {
    return bson.M{
        "$set": V,
    }
}

func SetInsert(V interface{}) bson.M {
    return bson.M{
        "$setOnInsert": V,
    }
}

注意:_id為每一個行程記錄的索引,即行程ID,每一個使用者也會有一個accountID,他們存放在一條文件中,但行程ID為資料庫文件索引。

完成準備工作開始CRUD:

  1. 建立行程

    //欄位
    const (
        tripField      = "trip"
        accountIDField = tripField + ".accountid"
        statusField    = tripField + ".status"
    
    )
    //定義資料儲存結果
    type TripRecord struct {
        mgo.IDField        `bson:"inline"`
        mgo.UpdatedAtField `bson:"inline"` //時間戳
        Trip               *rentalpb.Trip  `bson:"trip"`
    }
    //建立行程, 將初始化資料放入資料庫中並分配Trip ID和時間戳
    func (m *Mongo) CreateTrip(c context.Context, trip *rentalpb.Trip) (*TripRecord, error) {
        r := &TripRecord{
            Trip: trip,
        }
        r.ID = mgo.NewObjID()
        r.UpdatedAt = mgo.UpdatedAt()
        _, err := m.col.InsertOne(c, r)
        if err != nil {
            return nil, err
        }
        return r, nil
    }
  2. 獲取當個行程

    //根據條件獲取行程資訊
    func (m *Mongo) GetTrip(c context.Context, id id.TripID, accountId id.AccountID) (*TripRecord, error) {
        //將id做型別轉換
        ojbid, err := objid.FromID(id)
        if err != nil {
            return nil, fmt.Errorf("不能將id轉換: %v", err)
        }
    
      //註釋為另一種寫法
        // filter := bson.M{
        //     "id": ojbid,
        //     "trip.accountid": accountId,
        // }
        // res := m.col.FindOne(c, filter)
    
      //需要根據tripID和accountID進行篩選
        res := m.col.FindOne(c, bson.M{
            mgo.IDFieldName: ojbid,
            accountIDField:  accountId,
        })
    
        //將res以TripRecord的結構解碼
        var tr TripRecord
        err = res.Decode(&tr)
        if err != nil {
            fmt.Errorf("不能解碼: %v", err)
        }
        return &tr, nil
    }
  3. 根據條件批次獲取行程

    這裡我們還需要根據行程狀態(未開始, 進行中, 已完成)進行獲取

    //GetTrips 根據條件去批次獲取使用者的行程資訊
    func (m *Mongo) GetTrips(c context.Context, accountID id.AccountID, status rentalpb.TripStatus) ([]*TripRecord, error) {
        filter := bson.M{
            accountIDField: accountID.String(),
        }
        if status != rentalpb.TripStatus_TS_NOT_SPECIFIED {
            filter[statusField] = status
        }
    
        res, err := m.col.Find(c, filter, options.Find().SetSort(bson.M{
            mgo.IDFieldName: -1,
        }))
    
        if err != nil {
            return nil, fmt.Errorf("cannot Find matching documents: %v", err)
        }
    
        var trips []*TripRecord
        for res.Next(c) {
    
            //將res以TripRecord的結構解碼
            var trip TripRecord
            err := res.Decode(&trip)
            if err != nil {
                fmt.Errorf("不能解碼: %v", err)
            }
            trips = append(trips, &trip)
        }
        return trips, nil
    }
  4. 更新行程

    
    //UpdateTrip 根據輸入更新資料
    func (m *Mongo) UpdateTrip(c context.Context, tripid id.TripID, accountid id.AccountID, updatedAt int64, trip *rentalpb.Trip) error {
        ojbid, err := objid.FromID(tripid)
        if err != nil {
            //fmt.Errorf("型別轉換失敗: %v", err)
            return err
        }
        newUpdateAt := mgo.UpdatedAt()
        //篩選器,根據行程ID,使用者ID和行程的時間戳進行篩選
        filter := bson.M{
            mgo.IDFieldName:        ojbid,
            accountIDField:         accountid.String(),
            mgo.UpdatedAtFieldName: updatedAt,
        }
        //更新資料
        change := mgo.Set(bson.M{
            tripField:              trip,
            mgo.UpdatedAtFieldName: newUpdateAt,
        })
    
        res, err := m.col.UpdateOne(c, filter, change)
        if err != nil {
            return err
        }
        if res.MatchedCount == 0 {
            return mongo.ErrNoDocuments
        }
    
        return nil
    }
    

    這就是整個微服務二的CRUD實戰內容,下面是完整程式碼:

    package dao
    
    import (
        "context"
        rentalpb "coolcar/rental/api/gen/v1"
        "coolcar/shared/id"
        mgo "coolcar/shared/mongo"
        objid "coolcar/shared/mongo/objid"
        "fmt"
    
        "go.mongodb.org/mongo-driver/bson"
        "go.mongodb.org/mongo-driver/mongo"
        "go.mongodb.org/mongo-driver/mongo/options"
    )
    
    const (
        tripField      = "trip"
        accountIDField = tripField + ".accountid"
        statusField    = tripField + ".status"
    )
    
    //定義一個 Mongo 型別
    type Mongo struct {
        col *mongo.Collection
    }
    
    //初始化資料庫, 類似建構函式
    func NewMongo(db *mongo.Database) *Mongo {
        return &Mongo{
            col: db.Collection("trip"),
        }
    }
    
    type TripRecord struct {
        mgo.IDField        `bson:"inline"`
        mgo.UpdatedAtField `bson:"inline"` //時間戳
        Trip               *rentalpb.Trip  `bson:"trip"`
    }
    
    //建立行程, 將初始化資料放入資料庫中並分配Trip ID和時間戳
    func (m *Mongo) CreateTrip(c context.Context, trip *rentalpb.Trip) (*TripRecord, error) {
        r := &TripRecord{
            Trip: trip,
        }
        r.ID = mgo.NewObjID()
        r.UpdatedAt = mgo.UpdatedAt()
        _, err := m.col.InsertOne(c, r)
        if err != nil {
            return nil, err
        }
        return r, nil
    }
    
    //根據條件獲取行程資訊
    func (m *Mongo) GetTrip(c context.Context, id id.TripID, accountId id.AccountID) (*TripRecord, error) {
        //將id做型別轉換
        ojbid, err := objid.FromID(id)
        if err != nil {
            return nil, fmt.Errorf("不能將id轉換: %v", err)
        }
        // filter := bson.M{
        //     "id": ojbid,
        //     "trip.accountid": accountId,
        // }
        // res := m.col.FindOne(c, filter)
    
        res := m.col.FindOne(c, bson.M{
            mgo.IDFieldName: ojbid,
            accountIDField:  accountId,
        })
    
        //將res以TripRecord的結構解碼
        var tr TripRecord
        err = res.Decode(&tr)
        if err != nil {
            fmt.Errorf("不能解碼: %v", err)
        }
        return &tr, nil
    }
    
    //GetTrips 根據條件去批次獲取使用者的行程資訊
    func (m *Mongo) GetTrips(c context.Context, accountID id.AccountID, status rentalpb.TripStatus) ([]*TripRecord, error) {
        filter := bson.M{
            accountIDField: accountID.String(),
        }
        if status != rentalpb.TripStatus_TS_NOT_SPECIFIED {
            filter[statusField] = status
        }
    
        res, err := m.col.Find(c, filter, options.Find().SetSort(bson.M{
            mgo.IDFieldName: -1,
        }))
    
        if err != nil {
            return nil, fmt.Errorf("cannot Find matching documents: %v", err)
        }
    
        var trips []*TripRecord
        for res.Next(c) {
    
            //將res以TripRecord的結構解碼
            var trip TripRecord
            err := res.Decode(&trip)
            if err != nil {
                fmt.Errorf("不能解碼: %v", err)
            }
            trips = append(trips, &trip)
        }
        return trips, nil
    }
    
    //UpdateTrip 根據輸入更新資料
    func (m *Mongo) UpdateTrip(c context.Context, tripid id.TripID, accountid id.AccountID, updatedAt int64, trip *rentalpb.Trip) error {
        ojbid, err := objid.FromID(tripid)
        if err != nil {
            //fmt.Errorf("型別轉換失敗: %v", err)
            return err
        }
        //篩選器
        newUpdateAt := mgo.UpdatedAt()
    
        filter := bson.M{
            mgo.IDFieldName:        ojbid,
            accountIDField:         accountid.String(),
            mgo.UpdatedAtFieldName: updatedAt,
        }
        //更改資料
        change := mgo.Set(bson.M{
            tripField:              trip,
            mgo.UpdatedAtFieldName: newUpdateAt,
        })
    
        res, err := m.col.UpdateOne(c, filter, change)
        if err != nil {
            return err
        }
        if res.MatchedCount == 0 {
            return mongo.ErrNoDocuments
        }
    
        return nil
    }
微服務三:身份資訊的驗證(profile)

profile服務應該是和trip服務是一個微服務的,因為這裡需要涉及到blob微服務,所以我將profile單獨介紹,該服務的作用是將使用者上傳的身份資訊程式儲存和獲取,以及和另一個blob微服務進行互動

這裡我們先來實現使用者身份儲存CRUD:

通樣我們需要型別轉換:

package objid

import (
    "coolcar/shared/id"
    "fmt"

    //"strings"

    "go.mongodb.org/mongo-driver/bson/primitive"
)

//FromID將一個id轉換為Object id
func FromID(id fmt.Stringer) (primitive.ObjectID, error) {
    return primitive.ObjectIDFromHex(id.String())
}

//MustFromID將一個id轉換為Object id
func MustFromID(id fmt.Stringer) primitive.ObjectID {
    oid, err := FromID(id)
    if err != nil {
        panic(err)
    }
    return oid
}

//ToAccount將 primitive.ObjectID轉換為string id
func ToAccountID(oid primitive.ObjectID) id.AccountID {
    return id.AccountID(oid.Hex())
}

//ToTripID將 primitive.ObjectID轉換為string id
func ToTripID(oid primitive.ObjectID) id.TripID {
    return id.TripID(oid.Hex())
}

強型別化:

package id

//強型別化: AccountID定義account id物件型別
type AccountID string

func (a AccountID) String() string {
    return string(a)
}

//TripID 定義一個trip id
type TripID string

func (t TripID) String() string {
    return string(t)
}

//Identity定義一個使用者身份
type IdentityID string

func (i IdentityID) String() string {
    return string(i)
}

//CarId定義一個車輛id
type CarId string

func (c CarId) String() string {
    return string(c)
}

//BlobID定義一個blobID
type BlobID string

func (b BlobID) String() string {
    return string(b)
}

公共程式碼:

package mgo

import (
    "coolcar/shared/mongo/objid"
    "fmt"
    "time"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

const (
    IDFieldName        = "_id"
    UpdatedAtFieldName = "updatedat"
)

//ObjID defines the object field
type IDField struct {
    ID primitive.ObjectID `bson:"_id"`
}

//UpdatedAtField 定義一個時間篩選器
type UpdatedAtField struct {
    UpdatedAt int64 `bson:"updatedat"`
}

//NewObjectID 生成一個object id , NewObjID是一個函式
var NewObjID = primitive.NewObjectID

//NewObjIDWithValue 生成id 為下一個NewObjID,對id進一步包裝,
func NewObjIDWithValue(id fmt.Stringer) {
    NewObjID = func() primitive.ObjectID {
        return objid.MustFromID(id)
    }
}

//Updateda 返回一個合適的值,你賦值給它
var UpdatedAt = func() int64 {
    return time.Now().UnixNano() //當前時間取納秒
}

//Set return a $set updata document
func Set(V interface{}) bson.M {
    return bson.M{
        "$set": V,
    }
}

func SetInsert(V interface{}) bson.M {
    return bson.M{
        "$setOnInsert": V,
    }
}

//ZeroOrDoesNotExist是一個生成篩選器的表示式去篩選zero或者不存在的值
func ZeroOrDoesNotExist(field string, zero interface{}) bson.M {
    return bson.M{
        "$or": []bson.M{
            {
                field: zero,
            },
            {
                field: bson.M{
                    "$exists": false,
                },
            },
        },
    }
}

兩方法個請求:

  1. 獲取身份資訊
  2. 更新身份資訊
  3. 更新個人資料照片和blob id
package dao

import (
    "context"
    rentalpb "coolcar/rental/api/gen/v1"
    "coolcar/shared/id"
    mgo "coolcar/shared/mongo"
    "fmt"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

const (
    accountIDField      = "accountid"
    profileField        = "profile"
    identityStatusField = profileField + ".identitystatus"
    photoblobIDField    = "photoblobid"
)

type Mongo struct {
    col *mongo.Collection
}

//建構函式
func NewMongo(db *mongo.Database) *Mongo {
    return &Mongo{
        col: db.Collection("profile"),
    }
}

//ProfileRecord定義profile在資料庫中的解碼方式
type ProfileRecord struct {
    AccountID   string            `bson:"accountid"`
    Profile     *rentalpb.Profile `bson:"profile"`
    PhotoBlobID string            `bson:"photoblobid"`
}

//獲取身份資訊
func (m *Mongo) GetProfile(c context.Context, aid id.AccountID) (*ProfileRecord, error) {
    filter := bson.M{
        accountIDField: aid.String(),
    }
    res := m.col.FindOne(c, filter)
    //如果文件為空
    if err := res.Err(); err != nil {
        return nil, err
    }
    //對res進行解碼
    var pr ProfileRecord
    err := res.Decode(&pr)
    if err != nil {
        return nil, fmt.Errorf("解碼失敗: %v", err)
    }
    return &pr, nil
}

//更新身份資訊
func (m *Mongo) UpdateProfile(c context.Context, aid id.AccountID, prevState rentalpb.IdentityStatus, p *rentalpb.Profile) error {
    filter := bson.M{
        identityStatusField: prevState,
    }
    if prevState == rentalpb.IdentityStatus_UNSUBMITTED {
        filter = mgo.ZeroOrDoesNotExist(identityStatusField, prevState)

    }

    filter[accountIDField] = aid.String()

    change := mgo.Set(bson.M{
        accountIDField: aid.String(),
        profileField:   p,
    })
    _, err := m.col.UpdateOne(c, filter, change,
        options.Update().SetUpsert(true))
    if err != nil {
        return fmt.Errorf("更新失敗:%v", err)
    }
    return nil
}

//UpdateProfilePhoto 更新個人資料照片和blob id。
func (m *Mongo) UpdateProfilePhoto(c context.Context, aid id.AccountID, bid id.BlobID) error {
    filter := bson.M{
        accountIDField: aid.String(),
    }
    change := mgo.Set(bson.M{
        accountIDField:   aid.String(),
        photoblobIDField: bid.String(),
    })
    _, err := m.col.UpdateOne(c, filter, change,
        options.Update().SetUpsert(true))
    if err != nil {
        return fmt.Errorf("更新失敗:%v", err)
    }
    return err
}

微服務blob:工作流程圖:

blob根據profile提供的accountID,並根據blob資料庫該文件的索引和提供的accountID生成path,然後將一起存入資料庫,再返回對應資料, 圖片上傳完成後,profile向blob提供blobID(即:blbo資料庫文件索引),就可以拿到path,去雲端獲取圖片了,下面來實現blob的CRUD:

同樣型別轉換,強化型別和mgo和profile中一致

CRUD:

  1. 建立一個blod記錄
  2. 根據blobID或者資訊
package dao

import (
    "context"
    "coolcar/shared/id"
    mgo "coolcar/shared/mongo"
    "coolcar/shared/mongo/objid"
    "fmt"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
)

type Mongo struct {
    col *mongo.Collection
}

//建構函式
func NewMongo(db *mongo.Database) *Mongo {
    return &Mongo{
        col: db.Collection("blob"),
    }
}

type BlobRecord struct {
    mgo.IDField `bson:"inline"`
    AccountID   string `bson:"accountid"`
    Path        string `bson:"path"`
}

//CreateBlob建立一個blod記錄
func (m *Mongo) CreateBlob(c context.Context, aid id.AccountID) (*BlobRecord, error) {
    br := &BlobRecord{
        AccountID: aid.String(),
    }
    objID := mgo.NewObjID()
    br.ID = objID
    br.Path = fmt.Sprintf("%s/%s", aid, objID.Hex())
    fmt.Printf("MYRUL:%s\n", br.Path)

    _, err := m.col.InsertOne(c, br)
    if err != nil {
        return nil, err
    }
    return br, nil
}

//根據blobID或者資訊
func (m *Mongo) GetBlob(c context.Context, bid id.BlobID) (*BlobRecord, error) {
    objID, err := objid.FromID(bid)
    if err != nil {
        return nil, fmt.Errorf("失效的objid id: %v", err)
    }
    filter := bson.M{
        mgo.IDFieldName: objID,
    }
    res := m.col.FindOne(c, filter)

    if err = res.Err(); err != nil {
        return nil, err
    }
    var br BlobRecord
    err = res.Decode(&br)
    if err != nil {
        return nil, fmt.Errorf("解碼失敗: %v", err)
    }
    return &br, nil
}

這裡我們的幾個微服務都做完了

總結

這就是Go和MongoDB在實戰中是應用, 其實也很簡單,我們需要注意資料庫的索引_id的使用,以及各欄位的寫入,解碼結構,和資料型別的強型別化,雖然看上去增加了程式碼的量,但是這保證了我們資料不會出問題。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章