[gin]简单的gin-mongo

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: [gin]简单的gin-mongo

前言

基于Gin框架编写的Web API,实现简单的CRUD功能,数据存放在MongoDB,并设置Redis缓存。

代码需要简单的分模块组织。

go mod init buildginapp

代码参考自《Building Distributed Application in Gin》

定义数据模型

  • 代码文件:buildginapp/models/recipe.go
package models
import (
  "time"
  "go.mongodb.org/mongo-driver/bson/primitive"
)
type Recipe struct {
  ID           primitive.ObjectID `json:"id"           bson:"_id"`
  Name         string             `json:"name"         bson:"name"`
  Tags         []string           `json:"tags"         bson:"tags"`
  Ingredients  []string           `json:"ingredients"  bson:"ingredients"`
  Instructions []string           `json:"instructions" bson:"instructions"`
  PublishedAt  time.Time          `json:"publishedAt"  bson:"publishedAt"`
}

设计API

http method resource description
GET /recipes 返回一列recipe数据
POST /recipes 创建新食谱
PUT /recipes/{id} 更新一个已存在的食谱
DELETE /recipes/{id} 删除一个已存在的食谱
GET /recipes/search?tag=X 根据标签查询食谱

编写API方法

  • 代码文件:buildapp/handlers/handler.go
package handlers
import (
  "context"
  "encoding/json"
  "fmt"
  "log"
  "net/http"
  "time"
  "github.com/gin-gonic/gin"
  "github.com/go-redis/redis"
  "go.mongodb.org/mongo-driver/bson"
  "go.mongodb.org/mongo-driver/bson/primitive"
  "go.mongodb.org/mongo-driver/mongo"
  "buildginapp/models"
)
type RecipesHandler struct {
  collection  *mongo.Collection
  ctx         context.Context
  redisClient *redis.Client
}
func NewRecipesHandler(ctx context.Context, collection *mongo.Collection, redisClient *redis.Client) *RecipesHandler {
  return &RecipesHandler{
    collection:  collection,
    ctx:         ctx,
    redisClient: redisClient,
  }
}
// GET /recipes
func (handler *RecipesHandler) ListRecipesHandler(c *gin.Context) {
  // 先查redis, 无则查MongoDB
  val, err := handler.redisClient.Get("recipes").Result()
  if err == redis.Nil {
    log.Println("Request to MongoDB")
    cur, err := handler.collection.Find(handler.ctx, bson.M{})
    if err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{
        "error": err.Error(),
      })
      return
    }
    defer cur.Close(handler.ctx)
    recipes := make([]models.Recipe, 0)
    for cur.Next(handler.ctx) {
      var recipe models.Recipe
      cur.Decode(&recipe)
      recipes = append(recipes, recipe)
    }
    // 将查询结果存到redis中, 过期时间为1小时
    // 数据量很多的时候,这会是一个大Key,可能有一定的性能隐患
    data, _ := json.Marshal(recipes)
    handler.redisClient.Set("recipes", string(data), 3600*time.Second)
    c.JSON(http.StatusOK, recipes)
  } else if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{
      "error": err.Error(),
    })
    return
  } else {
    // 如果从redis中查询到了,那么直接返回redis的查询结果
    log.Println("Request to redis")
    recipes := make([]models.Recipe, 0)
    json.Unmarshal([]byte(val), &recipes)
    c.JSON(http.StatusOK, recipes)
  }
}
// POST /recipes
func (handler *RecipesHandler) NewRecipeHandler(c *gin.Context) {
  var recipe models.Recipe
  if err := c.ShouldBindJSON(&recipe); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }
  recipe.ID = primitive.NewObjectID()
  recipe.PublishedAt = time.Now()
  _, err := handler.collection.InsertOne(handler.ctx, recipe)
  if err != nil {
    fmt.Println(err)
    c.JSON(http.StatusInternalServerError, gin.H{
      "error": "Error while inserting a new recipe",
    })
    return
  }
  log.Println("RRemove data from redis")
  handler.redisClient.Del("recipes")
  c.JSON(http.StatusOK, recipe)
}
// PUT /recipes/:id
func (handler *RecipesHandler) UpdateRecipeHandler(c *gin.Context) {
  id := c.Param("id")
  var recipe models.Recipe
  if err := c.ShouldBindJSON(&recipe); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }
  objectId, _ := primitive.ObjectIDFromHex(id)
  _, err := handler.collection.UpdateOne(handler.ctx, bson.M{
    "_id": objectId,
  }, bson.D{{"$set", bson.D{
    {"name", recipe.Name},
    {"instructions", recipe.Instructions},
    {"ingredients", recipe.Ingredients},
    {"tags", recipe.Tags},
  }}})
  if err != nil {
    fmt.Println(err)
    c.JSON(http.StatusInternalServerError, gin.H{
      "error": err.Error(),
    })
    return
  }
  c.JSON(http.StatusOK, gin.H{
    "message": "update success",
  })
}
// DELETE /recipes/:id
func (handler *RecipesHandler) DeleteRecipeHandler(c *gin.Context) {
  id := c.Param("id")
  objectId, _ := primitive.ObjectIDFromHex(id)
  _, err := handler.collection.DeleteOne(handler.ctx, bson.M{
    "_id": objectId,
  })
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{
      "error": err.Error(),
    })
    return
  }
  c.JSON(http.StatusOK, gin.H{
    "message": "delete success",
  })
}
// GET /recipes/:id
func (handler *RecipesHandler) GetOneRecipeHandler(c *gin.Context) {
  id := c.Param("id")
  objectId, _ := primitive.ObjectIDFromHex(id)
  cur := handler.collection.FindOne(handler.ctx, bson.M{
    "_id": objectId,
  })
  var recipe models.Recipe
  err := cur.Decode(&recipe)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{
      "error": err.Error(),
    })
    return
  }
  c.JSON(http.StatusOK, recipe)
}

main.go

  • 代码文件:buildginapp/main.go
package main
import (
  "context"
  "log"
  "net/http"
  "os"
  "os/signal"
  "syscall"
  "time"
  "github.com/gin-gonic/gin"
  "github.com/go-redis/redis"
  "go.mongodb.org/mongo-driver/mongo"
  "go.mongodb.org/mongo-driver/mongo/options"
  "go.mongodb.org/mongo-driver/mongo/readpref"
  "buildginapp/handlers"
)
var err error
var client *mongo.Client
var recipesHandler *handlers.RecipesHandler
func init() {
  ctx := context.Background()
  // demo这个数据库需要先创建
  var url string = "mongodb://root:123456@192.168.0.20:27017/demo?authSource=admin&maxPoolSize=20"
  client, err = mongo.Connect(ctx, options.Client().ApplyURI(url))
  if err = client.Ping(context.TODO(), readpref.Primary()); err != nil {
    log.Fatal("connect ot mongodb failed: ", err)
  }
  log.Println("Connected to MongoDB")
  collection := client.Database("demo").Collection("recipes")
  redisClient := redis.NewClient(&redis.Options{
    Addr:     "192.168.0.20:6379",
    Password: "",
    DB:       0,
  })
  status := redisClient.Ping()
  log.Println("redis ping: ", status)
  recipesHandler = handlers.NewRecipesHandler(ctx, collection, redisClient)
}
func main() {
  gin.SetMode(gin.ReleaseMode)
  router := gin.Default()
  router.GET("/recipes", recipesHandler.ListRecipesHandler)
  router.POST("/recipes", recipesHandler.NewRecipeHandler)
  router.PUT("/recipes/:id", recipesHandler.UpdateRecipeHandler)
  router.DELETE("/recipes/:id", recipesHandler.DeleteRecipeHandler)
  router.GET("/recipes/:id", recipesHandler.GetOneRecipeHandler)
  // 优雅关闭web服务
  srv := &http.Server{
    Addr:    "127.0.0.1:8080",
    Handler: router,
  }
  go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
      log.Fatal("listen failed, ", err)
    }
  }()
  defer func() {
    if err = client.Disconnect(context.TODO()); err != nil {
      panic(err)
    }
  }()
  quit := make(chan os.Signal, 1)
  signal.Notify(quit, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
  <-quit
  ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  defer cancel()
  if err := srv.Shutdown(ctx); err != nil {
    log.Fatalf("server shutdown failed, err: %v\n", err)
  }
  select {
  case <-ctx.Done():
    log.Println("timeout of 2 seconds")
  }
  log.Println("server shutdown")
}

参考

附录

准备数据

package main
import (
  "context"
  "encoding/json"
  "fmt"
  "io/ioutil"
  "log"
  "time"
  "go.mongodb.org/mongo-driver/mongo"
  "go.mongodb.org/mongo-driver/mongo/options"
  "go.mongodb.org/mongo-driver/mongo/readpref"
)
type Recipe struct {
  ID           string    `json:"id"`
  Name         string    `json:"name"`
  Tags         []string  `json:"tags"`
  Ingredients  []string  `json:"ingredients"`
  Instructions []string  `json:"instructions"`
  PublishedAt  time.Time `json:"publishedAt"`
}
// 保存recipes
var recipes []Recipe
var ctx context.Context
var err error
var client *mongo.Client
func init() {
  recipes = make([]Recipe, 0)
  // 读取当前目录下的json文件
  file, _ := ioutil.ReadFile("recipes.json")
  _ = json.Unmarshal([]byte(file), &recipes)
  ctx = context.Background()
  // demo这个数据库可能需要先创建
  client, err = mongo.Connect(ctx, options.Client().ApplyURI("mongodb://root:123456@192.168.0.20:27017/demo?authSource=admin"))
  if err = client.Ping(context.TODO(), readpref.Primary()); err != nil {
    log.Fatal("connect ot mongodb failed: ", err)
  }
  log.Println("Connected to MongoDB")
  var listOfRecipes []interface{}
  for _, recipe := range recipes {
    listOfRecipes = append(listOfRecipes, recipe)
  }
  collection := client.Database("demo").Collection("recipes")
  insertManyResult, err := collection.InsertMany(ctx, listOfRecipes)
  if err != nil {
    log.Fatal(err)
  }
  log.Println("Inserted recipes: ", len(insertManyResult.InsertedIDs))
}
func main() {
  fmt.Println("insert many data to mongodb")
}
  • 也可以使用mongoimport将json数据直接插入到数据库中
mongoimport --username admin --password password --authenticationDatabase admin \
    --db demo --collection recipes --file recipes.json --jsonArray

python测试

import requests
import json
def post_test(data):
    url = "http://127.0.0.1:8080/recipes"
    resp = requests.post(url=url, data=json.dumps(data))
    # print("post test")
    print(resp.text)
def get_test():
    url = "http://127.0.0.1:8080/recipes"
    resp = requests.get(url)
    # print("get test")
    print(resp.text)
def put_test(data, id):
    url = f"http://127.0.0.1:8080/recipes/{id}"
    # data["id"] = id
    resp = requests.put(url=url, data=json.dumps(data))
    # print("put test")
    print(json.loads(resp.text))
def delete_test(id):
    url = f"http://127.0.0.1:8080/recipes/{id}"
    resp = requests.delete(url=url)
    print("delete test")
    print(resp.text)
def get_test_search(id):
    url = f"http://127.0.0.1:8080/recipes/{id}"
    resp = requests.get(url=url)
    print("get test search")
    print(resp.text)
if __name__ == "__main__":
    data1 = {
        "name": "Homemade Pizza",
        "tags": ["italian", "pizza", "dinner"],
        "ingredients": [
            "1 1/2 cups (355 ml) warm water (105°F-115°F)",
            "1 package (2 1/4 teaspoons) of active dry yeast",
            "3 3/4 cups (490 g) bread flour",
            "feta cheese, firm mozzarella cheese, grated",
        ],
        "instructions": [
            "Step 1.",
            "Step 2.",
            "Step 3.",
        ],
    }
    data2 = {
        "name": "西红柿炒鸡蛋",
        "tags": ["家常菜", "新手必会"],
        "ingredients": [
            "2个鸡蛋",
            "1个番茄, 切片",
            "葱花, 蒜瓣等",
        ],
        "instructions": [
            "步骤1",
            "步骤2",
            "步骤3",
        ],
    }
    data3 = {
        "name": "蒸蛋",
        "tags": ["家常菜", "新手必会"],
        "ingredients": [
            "2个鸡蛋",
            "葱花, 蒜瓣等",
        ],
        "instructions": [
            "步骤1",
            "步骤2",
            "步骤3",
            "步骤4",
        ],
    }
    # post_test(data1)
    # post_test(data2)
    # put_test(data2, id="62b7d298bb2ffa932f0d213d")
    # get_test_search(id="62b6e5746202e6a3c26b0afb")
    # delete_test(id="123456")
    get_test()
相关文章
|
网络协议 API Go
Flask 作者 Armin Ronacher:我不觉得有异步压力
Flask 作者 Armin Ronacher:我不觉得有异步压力
211 0
|
存储 Shell Linux
【Shell 命令集合 磁盘管理 】Linux读取、转换并输出数据 dd命令使用教程
【Shell 命令集合 磁盘管理 】Linux读取、转换并输出数据 dd命令使用教程
375 0
|
10月前
|
消息中间件 编解码 开发者
深入解析 Flutter兼容鸿蒙next全体生态的横竖屏适配与多屏协作兼容架构
本文深入探讨了 Flutter 在屏幕适配、横竖屏切换及多屏协作方面的兼容架构。介绍了 Flutter 的响应式布局、逻辑像素、方向感知、LayoutBuilder 等工具,以及如何通过 StreamBuilder 和 Provider 实现多屏数据同步。结合实际应用场景,如移动办公和教育应用,展示了 Flutter 的强大功能和灵活性。
433 6
|
12月前
|
存储 JSON Go
在Gin框架中优雅地处理HTTP请求体中的JSON数据
在Gin框架中优雅地处理HTTP请求体中的JSON数据
|
设计模式 开发框架 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(1)
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(1)
|
JSON 安全 Java
2024年的选择:为什么Go可能是理想的后端语言
【4月更文挑战第27天】Go语言在2024年成为后端开发的热门选择,其简洁设计、内置并发原语和强大工具链备受青睐。文章探讨了Go的设计哲学,如静态类型、垃圾回收和CSP并发模型,并介绍了使用Gin和Echo框架构建Web服务。Go的并发通过goroutines和channels实现,静态类型确保代码稳定性和安全性,快速编译速度利于迭代。Go广泛应用在云计算、微服务等领域,拥有丰富的生态系统和活跃社区,适合作为应对未来技术趋势的语言。
2084 0
|
存储 SQL 关系型数据库
为了让你彻底弄懂 MySQL 事务日志,我通宵赶出了这份图解!
在当今社会,充斥着大量的数据。从众多APP上的账户资料到银行信用体系等个人档案,都离不开对大量数据的组织、存储和管理。而这,便是数据库存在的目的和价值。本文将为大家详细讲解 MySQL 事务日志的相关知识。
4882 0
为了让你彻底弄懂 MySQL 事务日志,我通宵赶出了这份图解!
|
存储 负载均衡 监控
「 ASK 免费试用中 」 详细规则说明
关于领取资格,与ASK中资源免费抵扣规则的介绍。
1145 0
|
JSON 数据可视化 数据格式
Mac 下免费JSON可视化工具Visual Json
Mac 下免费JSON可视化工具Visual Json
350 0
|
缓存 资源调度 前端开发
一步步实现Nginx +Docker + Jenkins前端自动化部署
一步步实现Nginx +Docker + Jenkins前端自动化部署
872 1