项目作者: FogDong

项目描述 :
使用 gin 和 MongoDB 完成的 IoT 管理平台后端
高级语言: Go
项目地址: git://github.com/FogDong/IoT-admin-backend.git
创建时间: 2018-10-12T08:57:31Z
项目社区:https://github.com/FogDong/IoT-admin-backend

开源协议:

关键词:
gin mongo

下载


IoT-admin-backend

是某次作业的代码,不知道为什么居然有人 star 于是补充一下 readme,大概大家都在为作业苦恼叭(

Run

go run main.go

Build

go build main.go

项目结构

  1. ├── db
  2. ├── middleware
  3. ├── models
  4. └── pkg
  5. ├── api
  6. └── handler

db

db 中主要存放数据库的连接逻辑

  1. var (
  2. // Session stores mongo session
  3. Session *mgo.Session
  4. // Mongo stores the mongodb connection string information
  5. Mongo *mgo.DialInfo
  6. )
  7. const (
  8. // MongoDBUrl is the default mongodb url that will be used to connect to the database.
  9. MongoDBUrl = "mongodb://localhost:27017/IoT-admin"
  10. )
  11. // Connect connects to mongodb
  12. func Connect() {
  13. uri := os.Getenv("MONGODB_URL")
  14. if len(uri) == 0 {
  15. uri = MongoDBUrl
  16. }
  17. mongo, err := mgo.ParseURL(uri)
  18. s, err := mgo.Dial(uri)
  19. if err != nil {
  20. fmt.Printf("Can't connect to mongo, go error %v\n", err)
  21. panic(err.Error())
  22. }
  23. s.SetSafe(&mgo.Safe{})
  24. fmt.Println("Connected to", uri)
  25. Session = s
  26. Mongo = mongo
  27. }

middleware

middleware 中主要存放中间件。

cors

处理跨域问题

  1. func Cors() gin.HandlerFunc {
  2. return func(c *gin.Context) {
  3. method := c.Request.Method
  4. c.Header("Access-Control-Allow-Origin", "*")
  5. c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
  6. c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE")
  7. c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
  8. c.Header("Access-Control-Allow-Credentials", "true")
  9. // 放行所有OPTIONS方法,因为有的模板是要请求两次的
  10. if method == "OPTIONS" {
  11. c.AbortWithStatus(http.StatusNoContent)
  12. }
  13. // 处理请求
  14. c.Next()
  15. }
  16. }

dbConnector

数据库连接中间件:克隆每一个数据库会话,并且确保 db 属性在每一个 handler 里均有效

  1. func Connect(context *gin.Context) {
  2. s := db.Session.Clone()
  3. defer s.Clone()
  4. context.Set("db", s.DB(db.Mongo.Database))
  5. context.Next()
  6. }

jwt

JWTAuth 中间件,检查token

  1. func JWTAuth() gin.HandlerFunc {
  2. return func(c *gin.Context) {
  3. token := c.Request.Header.Get("Authorization")
  4. if token == "" {
  5. c.JSON(http.StatusOK, gin.H{
  6. "status": -1,
  7. "msg": "请求未携带token,无权限访问",
  8. })
  9. c.Abort()
  10. return
  11. }
  12. log.Print("get token: ", token)
  13. j := NewJWT()
  14. // parseToken 解析token包含的信息
  15. claims, err := j.ParseToken(token)
  16. if err != nil {
  17. if err == TokenExpired {
  18. c.JSON(http.StatusOK, gin.H{
  19. "status": -1,
  20. "msg": "授权已过期",
  21. })
  22. c.Abort()
  23. return
  24. }
  25. c.JSON(http.StatusOK, gin.H{
  26. "status": -1,
  27. "msg": err.Error(),
  28. })
  29. c.Abort()
  30. return
  31. }
  32. // 继续交由下一个路由处理,并将解析出的信息传递下去
  33. c.Set("claims", claims)
  34. }
  35. }

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. // Get a product
  2. func GetProduct(c *gin.Context) {
  3. db := c.MustGet("db").(*mgo.Database)
  4. var product models.Product
  5. err := db.C(models.CollectionProduct).
  6. FindId(bson.ObjectIdHex(c.Param("_id"))).
  7. One(&product)
  8. if err != nil {
  9. c.JSON(http.StatusInternalServerError, gin.H{
  10. "status": 500,
  11. "msg": err.Error(),
  12. })
  13. return
  14. }
  15. c.JSON(http.StatusOK, gin.H{
  16. "status": 200,
  17. "msg": "Success",
  18. "data": product,
  19. })
  20. }

CREATE
首先从 token 中解析出用户的 id, 从而加到 product 的 CreatedBy 字段中
并且每新增一个 product 都往 customer 和 organization 中的 productCount 字段加一,且把 productId 加到这两张表的 productId 数组中

  1. // Create a product
  2. func CreateProduct(c *gin.Context) {
  3. db := c.MustGet("db").(*mgo.Database)
  4. var product models.Product
  5. err := c.BindJSON(&product)
  6. if err != nil {
  7. c.JSON(http.StatusInternalServerError, gin.H{
  8. "status": 500,
  9. "msg": err.Error(),
  10. })
  11. return
  12. }
  13. claims := c.MustGet("claims").(*middleware.CustomClaims)
  14. product.CreatedBy = claims.ID
  15. product.ID = bson.NewObjectId()
  16. err = db.C(models.CollectionProduct).Insert(product)
  17. if err != nil {
  18. c.JSON(http.StatusInternalServerError, gin.H{
  19. "status": 500,
  20. "msg": err.Error(),
  21. })
  22. return
  23. }
  24. err = db.C(models.CollectionUser).Update(bson.M{"_id": product.CreatedBy},
  25. bson.M{"$inc": bson.M{"productCount": 1}})
  26. if err != nil {
  27. c.JSON(http.StatusInternalServerError, gin.H{
  28. "status": 500,
  29. "msg": err.Error(),
  30. })
  31. return
  32. }
  33. for _, id := range product.CustomerID {
  34. err = db.C(models.CollectionCustomer).Update(bson.M{"_id": id},
  35. bson.M{"$inc": bson.M{"productCount": 1}})
  36. err = db.C(models.CollectionCustomer).Update(bson.M{"_id": id},
  37. bson.M{"$push": bson.M{"productId": product.ID}})
  38. if err != nil {
  39. c.JSON(http.StatusInternalServerError, gin.H{
  40. "status": 500,
  41. "msg": err.Error(),
  42. })
  43. return
  44. }
  45. }
  46. err = db.C(models.CollectionOrg).Update(bson.M{"_id": product.OrganizationID},
  47. bson.M{"$inc": bson.M{"productCount": 1}})
  48. err = db.C(models.CollectionOrg).Update(bson.M{"_id": product.OrganizationID},
  49. bson.M{"$push": bson.M{"productId": product.ID}})
  50. if err != nil {
  51. c.JSON(http.StatusInternalServerError, gin.H{
  52. "status": 500,
  53. "msg": err.Error(),
  54. })
  55. return
  56. }
  57. c.JSON(http.StatusOK, gin.H{
  58. "status": 200,
  59. "msg": "Success",
  60. })
  61. }

PUT

  1. func UpdateProduct(c *gin.Context) {
  2. db := c.MustGet("db").(*mgo.Database)
  3. var product models.Product
  4. err := c.BindJSON(&product)
  5. if err != nil {
  6. c.JSON(http.StatusInternalServerError, gin.H{
  7. "status": 500,
  8. "msg": err.Error(),
  9. })
  10. return
  11. }
  12. // 查找原来的文档
  13. query := bson.M{
  14. "_id": bson.ObjectIdHex(c.Param("_id")),
  15. }
  16. // 更新
  17. err = db.C(models.CollectionProduct).Update(query, product)
  18. if err != nil {
  19. c.JSON(http.StatusInternalServerError, gin.H{
  20. "status": 500,
  21. "msg": err.Error(),
  22. })
  23. return
  24. }
  25. c.JSON(http.StatusOK, gin.H{
  26. "status": 200,
  27. "msg": "Success",
  28. "data": product,
  29. })
  30. }

部署

使用 docker 打包整个后端
Dockerfile:(注意:需要设置时区)

  1. #源镜像
  2. FROM golang:latest
  3. ENV TZ=Asia/Shanghai
  4. RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
  5. WORKDIR $GOPATH/src/IoT-admin-backend
  6. COPY . $GOPATH/src/IoT-admin-backend
  7. RUN go build .
  8. #暴露端口
  9. EXPOSE 9002
  10. #最终运行docker的命令
  11. ENTRYPOINT ["./IoT-admin-backend"]

除了 IoT-admin 以外,还需要 mongo , 直接使用 dockerhub 上的最新 mongo 镜像跑一个 mongo container 之后,使用 docker-compose 跑两个容器
docker-compose: (version 是 2.0 是因为服务器上的 docker 版本较低)

  1. version: '2.0'
  2. services:
  3. api:
  4. container_name: 'IoT-admin'
  5. build: '.'
  6. ports:
  7. - '9002:9002'
  8. volumes:
  9. - '.:/go/src/IoT-admin'
  10. links:
  11. - mongo
  12. environment:
  13. MONGODB_URL: mongodb://mongo:27017/IoT-admin
  14. mongo:
  15. image: 'mongo:latest'
  16. container_name: 'mongo'
  17. ports:
  18. - '27010:27017'