項(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'