项目结构
1 2 3 4 5 6
| ├── db ├── middleware ├── models └── pkg ├── api └── handler
|
db
db 中主要存放数据库的连接逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| var ( Session *mgo.Session Mongo *mgo.DialInfo ) const ( MongoDBUrl = "mongodb://localhost:27017/IoT-admin" ) 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
处理跨域问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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") if method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) } c.Next() } }
|
dbConnector
数据库连接中间件:克隆每一个数据库会话,并且确保 db
属性在每一个 handler 里均有效
1 2 3 4 5 6 7 8
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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": "请求未携带token,无权限访问", }) c.Abort() return } log.Print("get token: ", token) j := NewJWT() claims, err := j.ParseToken(token) if err != nil { if err == TokenExpired { c.JSON(http.StatusOK, gin.H{ "status": -1, "msg": "授权已过期", }) c.Abort() return } c.JSON(http.StatusOK, gin.H{ "status": -1, "msg": err.Error(), }) c.Abort() return } c.Set("claims", claims) } }
|
models
主要存放数据结构体
其中注意一点,在定义 ID
时,即会在 MongoDB 中自动生成的 _id
,必须加上 omitempty ,忽略该字段,否则在创建时此字段为空会报错
1
| ID bson.ObjectId `json:"_id,omitempty" bson:"_id,omitempty"`
|
api
主要存放路由
统一 api prefix /api/v1alpha1/
在部分路由前加上中间件 v1.Use(middleware.JWTAuth())
路由遵循 RESTful 规范
handler
主要存放业务逻辑
如:
GET
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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 字段中
并且每新增一个 product 都往 customer 和 organization 中的 productCount 字段加一,且把 productId 加到这两张表的 productId 数组中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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 打包整个后端
Dockerfile:(注意:需要设置时区)
1 2 3 4 5 6 7 8 9 10 11
| 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 ENTRYPOINT ["./IoT-admin-backend"]
|
除了 IoT-admin 以外,还需要 mongo , 直接使用 dockerhub 上的最新 mongo 镜像跑一个 mongo container 之后,使用 docker-compose 跑两个容器
docker-compose: (version 是 2.0 是因为服务器上的 docker 版本较低)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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