使用 gin 和 mongoDB 完成的 IoT 管理平臺(tái)后端

項(xiàng)目結(jié)構(gòu)

├── db
├── middleware
├── models
└── pkg
    ├── api
    └── handler

db

db 中主要存放數(shù)據(jù)庫(kù)的連接邏輯

var (
    // Session stores mongo session
    Session *mgo.Session

    // Mongo stores the mongodb connection string information
    Mongo *mgo.DialInfo
)

const (
    // MongoDBUrl is the default mongodb url that will be used to connect to the database.
    MongoDBUrl = "mongodb://localhost:27017/IoT-admin"
)

// Connect connects to mongodb
func Connect() {
    uri := os.Getenv("MONGODB_URL")

    if len(uri) == 0 {
        uri = MongoDBUrl
    }

    mongo, err := mgo.ParseURL(uri)
    s, err := mgo.Dial(uri)
    if err != nil {
        fmt.Printf("Can't connect to mongo, go error %v\n", err)
        panic(err.Error())
    }
    s.SetSafe(&mgo.Safe{})
    fmt.Println("Connected to", uri)
    Session = s
    Mongo = mongo
}

middleware

middleware 中主要存放中間件镐依。

cors

處理跨域問題

func Cors() gin.HandlerFunc {
    return func(c *gin.Context) {
        method := c.Request.Method
        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
        c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE")
        c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
        c.Header("Access-Control-Allow-Credentials", "true")

        // 放行所有OPTIONS方法,因?yàn)橛械哪0迨且?qǐng)求兩次的
        if method == "OPTIONS" {
            c.AbortWithStatus(http.StatusNoContent)
        }

        // 處理請(qǐng)求
        c.Next()
    }
}

dbConnector

數(shù)據(jù)庫(kù)連接中間件:克隆每一個(gè)數(shù)據(jù)庫(kù)會(huì)話靠抑,并且確保 db 屬性在每一個(gè) handler 里均有效

func Connect(context *gin.Context) {
    s := db.Session.Clone()
    defer s.Clone()

    context.Set("db", s.DB(db.Mongo.Database))
    context.Next()

}

jwt

JWTAuth 中間件仰剿,檢查token

func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.Request.Header.Get("Authorization")
        if token == "" {
            c.JSON(http.StatusOK, gin.H{
                "status": -1,
                "msg":    "請(qǐng)求未攜帶token,無權(quán)限訪問",
            })
            c.Abort()
            return
        }

        log.Print("get token: ", token)

        j := NewJWT()
        // parseToken 解析token包含的信息
        claims, err := j.ParseToken(token)
        if err != nil {
            if err == TokenExpired {
                c.JSON(http.StatusOK, gin.H{
                    "status": -1,
                    "msg":    "授權(quán)已過期",
                })
                c.Abort()
                return
            }
            c.JSON(http.StatusOK, gin.H{
                "status": -1,
                "msg":    err.Error(),
            })
            c.Abort()
            return
        }
        // 繼續(xù)交由下一個(gè)路由處理,并將解析出的信息傳遞下去
        c.Set("claims", claims)
    }
}

models

主要存放數(shù)據(jù)結(jié)構(gòu)體
其中注意一點(diǎn),在定義 ID 時(shí),即會(huì)在 MongoDB 中自動(dòng)生成的 _id ,必須加上 omitempty 域那,忽略該字段,否則在創(chuàng)建時(shí)此字段為空會(huì)報(bào)錯(cuò)

    ID               bson.ObjectId   `json:"_id,omitempty" bson:"_id,omitempty"`

api

主要存放路由
統(tǒng)一 api prefix /api/v1alpha1/
在部分路由前加上中間件 v1.Use(middleware.JWTAuth())
路由遵循 RESTful 規(guī)范

handler

主要存放業(yè)務(wù)邏輯

如:

GET

// Get a product
func GetProduct(c *gin.Context) {
    db := c.MustGet("db").(*mgo.Database)
    var product models.Product

    err := db.C(models.CollectionProduct).
        FindId(bson.ObjectIdHex(c.Param("_id"))).
        One(&product)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "status": 500,
            "msg":    err.Error(),
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "status": 200,
        "msg":    "Success",
        "data":   product,
    })
}

CREATE
首先從 token 中解析出用戶的 id, 從而加到 product 的 CreatedBy 字段中
并且每新增一個(gè) product 都往 customer 和 organization 中的 productCount 字段加一猜煮,且把 productId 加到這兩張表的 productId 數(shù)組中

// Create a product
func CreateProduct(c *gin.Context) {
    db := c.MustGet("db").(*mgo.Database)

    var product models.Product
    err := c.BindJSON(&product)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "status": 500,
            "msg":    err.Error(),
        })
        return
    }
    claims := c.MustGet("claims").(*middleware.CustomClaims)
    product.CreatedBy = claims.ID
    product.ID = bson.NewObjectId()

    err = db.C(models.CollectionProduct).Insert(product)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "status": 500,
            "msg":    err.Error(),
        })
        return
    }

    err = db.C(models.CollectionUser).Update(bson.M{"_id": product.CreatedBy},
        bson.M{"$inc": bson.M{"productCount": 1}})
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "status": 500,
            "msg":    err.Error(),
        })
        return
    }

    for _, id := range product.CustomerID {
        err = db.C(models.CollectionCustomer).Update(bson.M{"_id": id},
            bson.M{"$inc": bson.M{"productCount": 1}})
        err = db.C(models.CollectionCustomer).Update(bson.M{"_id": id},
            bson.M{"$push": bson.M{"productId": product.ID}})
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "status": 500,
                "msg":    err.Error(),
            })
            return
        }
    }

    err = db.C(models.CollectionOrg).Update(bson.M{"_id": product.OrganizationID},
        bson.M{"$inc": bson.M{"productCount": 1}})
    err = db.C(models.CollectionOrg).Update(bson.M{"_id": product.OrganizationID},
        bson.M{"$push": bson.M{"productId": product.ID}})
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "status": 500,
            "msg":    err.Error(),
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "status": 200,
        "msg":    "Success",
    })
}

PUT

func UpdateProduct(c *gin.Context) {
    db := c.MustGet("db").(*mgo.Database)

    var product models.Product
    err := c.BindJSON(&product)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "status": 500,
            "msg":    err.Error(),
        })
        return
    }

    // 查找原來的文檔
    query := bson.M{
        "_id": bson.ObjectIdHex(c.Param("_id")),
    }

    // 更新
    err = db.C(models.CollectionProduct).Update(query, product)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "status": 500,
            "msg":    err.Error(),
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "status": 200,
        "msg":    "Success",
        "data":   product,
    })
}

部署

使用 docker 打包整個(gè)后端
Dockerfile:(注意:需要設(shè)置時(shí)區(qū))

#源鏡像
FROM golang:latest
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR $GOPATH/src/IoT-admin-backend
COPY . $GOPATH/src/IoT-admin-backend
RUN go build .
#暴露端口
EXPOSE 9002
#最終運(yùn)行docker的命令
ENTRYPOINT  ["./IoT-admin-backend"]

除了 IoT-admin 以外琉雳,還需要 mongo , 直接使用 dockerhub 上的最新 mongo 鏡像跑一個(gè) mongo container 之后样眠,使用 docker-compose 跑兩個(gè)容器
docker-compose: (version 是 2.0 是因?yàn)榉?wù)器上的 docker 版本較低)

version: '2.0'
services:
  api:
    container_name: 'IoT-admin'
    build: '.'
    ports:
      - '9002:9002'
    volumes:
      - '.:/go/src/IoT-admin'
    links:
      - mongo
    environment:
      MONGODB_URL: mongodb://mongo:27017/IoT-admin
  mongo:
    image: 'mongo:latest'
    container_name: 'mongo'
    ports:
      - '27010:27017'

源碼

見 GitHub:https://github.com/FogDong/IoT-admin-backend

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市翠肘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辫秧,老刑警劉巖束倍,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異盟戏,居然都是意外死亡绪妹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門柿究,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邮旷,“玉大人,你說我怎么就攤上這事蝇摸∩艏纾” “怎么了?”我有些...
    開封第一講書人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵貌夕,是天一觀的道長(zhǎng)律歼。 經(jīng)常有香客問我,道長(zhǎng)啡专,這世上最難降的妖魔是什么险毁? 我笑而不...
    開封第一講書人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮们童,結(jié)果婚禮上畔况,老公的妹妹穿的比我還像新娘。我一直安慰自己慧库,他們只是感情好跷跪,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著完沪,像睡著了一般域庇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上覆积,一...
    開封第一講書人閱讀 52,821評(píng)論 1 314
  • 那天听皿,我揣著相機(jī)與錄音,去河邊找鬼宽档。 笑死尉姨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吗冤。 我是一名探鬼主播又厉,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼九府,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了覆致?” 一聲冷哼從身側(cè)響起侄旬,我...
    開封第一講書人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎煌妈,沒想到半個(gè)月后儡羔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡璧诵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年汰蜘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片之宿。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡族操,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出比被,到底是詐尸還是另有隱情色难,我是刑警寧澤,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布姐赡,位于F島的核電站莱预,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏项滑。R本人自食惡果不足惜依沮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枪狂。 院中可真熱鬧危喉,春花似錦、人聲如沸州疾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽严蓖。三九已至薄嫡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颗胡,已是汗流浹背毫深。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留毒姨,地道東北人哑蔫。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親闸迷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嵌纲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 負(fù)載均衡及高可用一直是我們線上服務(wù)架構(gòu)所關(guān)注的重點(diǎn),本文內(nèi)容腥沽,將介紹如何在 Docker 環(huán)境下實(shí)現(xiàn) Mo...
    魏文弟閱讀 2,064評(píng)論 0 51
  • 《Docker從入門到實(shí)踐》閱讀筆記 原書地址: https://yeasy.gitbooks.io/docker...
    GuoYuebo閱讀 11,392評(píng)論 1 39
  • 1.Compose介紹 Docker Compose是一個(gè)用來定義和運(yùn)行復(fù)雜應(yīng)用的Docker工具巡球。一個(gè)使用Doc...
    渝味閱讀 7,235評(píng)論 0 2
  • 這幾天言沐,青島小雨霏霏,似進(jìn)入了南方的梅雨季節(jié)。在春雨貴如油的時(shí)節(jié)酣栈,著實(shí)令人欣喜。 但陰雨綿綿的早晨汹押,也給上班的人們...
    種豆南山上閱讀 518評(píng)論 8 9
  • 一 “當(dāng)下”一詞流行于世很多年棚贾,最起碼是近些年傳入中國(guó)的窖维。 我在人生的波瀾起伏中,專注于工作妙痹,卻一直找不到真正專注...
    銀城閱讀 1,175評(píng)論 14 44