前言

基于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()

[gin]简单的gin-mongo的更多相关文章

  1. Gin实战:Gin+Mysql简单的Restful风格的API(二)

    上一篇介绍了Gin+Mysql简单的Restful风格的API,但代码放在一个文件中,还不属于restful风格,接下来将进行进一步的封装. 目录结构 ☁ gin_restful2 tree . ├─ ...

  2. Gin实战:Gin+Mysql简单的Restful风格的API

    我们已经了解了Golang的Gin框架.对于Webservice服务,restful风格几乎一统天下.Gin也天然的支持restful.下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全.我们先以一 ...

  3. 1.1 安装gin框架&使用gin编写简单服务端

    01.安装gin框架 1)go环境配制 a)配制环境变量 GOPATH修改为go的工作文件夹路径 D:\Golang\goproject GOROOT修改为go的安装路径 D:\Golang\go1. ...

  4. Gin Web框架简单介绍

    翻译自: https://github.com/gin-gonic/gin/blob/develop/README.md Gin Web框架 branch=master"> Gin是用 ...

  5. Go最火的Gin框架简单入门

    Gin 介绍 Gin 是一个 Golang 写的 web 框架,具有高性能的优点,,基于 httprouter,它提供了类似martini但更好性能(路由性能约快40倍)的API服务.官方地址:htt ...

  6. Golang 微框架 Gin 简介

    框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了.成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些方面都得心应 ...

  7. GIN+GORILLA=A GOLANG WEBSOCKET SERVER

    鉴于聊天已然成为大部分app的基础功能,而大部分app用户基数有没有辣么大,常用的聊天server架构如xmpp或者消息队列实现之类的用起来还挺麻烦的,有比较难跟网页端做交互,加之H5标准落地,所以w ...

  8. Go实战--通过gin-gonic框架搭建restful api服务(github.com/gin-gonic/gin)

    生命不止,继续 go go go !!! 先插播一条广告,给你坚持学习golang的理由: <2017 软件开发薪酬调查:Go 和 Scala 是最赚钱的语言> 言归正传! 之前写过使用g ...

  9. Go语言web框架 gin

    Go语言web框架 GIN gin是go语言环境下的一个web框架, 它类似于Martini, 官方声称它比Martini有更好的性能, 比Martini快40倍, Ohhhh….看着不错的样子, 所 ...

  10. Gin框架 - 自定义错误处理

    目录 概述 错误处理 自定义错误处理 panic 和 recover 推荐阅读 概述 很多读者在后台向我要 Gin 框架实战系列的 Demo 源码,在这里再说明一下,源码我都更新到 GitHub 上, ...

随机推荐

  1. 3 分钟利用 FastGPT 和 Laf 将 ChatGPT 接入企业微信

    原文链接:https://forum.laf.run/d/556 FastGPT 是一个超级的 ChatGPT 平台项目,功能非常强大: 集成了 ChatGPT.GPT4 和 Claude 可以使用任 ...

  2. K8S in Action 读后感(概念简介)

    一.K8S的用武之地 今天,大型单体应用正被逐渐拆分成小的.可独立运行的组件,我们称之为微服务.微服务彼此之间解耦,所以它们可以被独立开发.部署.升级.伸缩.这使得我们可以对每一个微服务实现快速迭代, ...

  3. 10 款 VS Code 插件神器,第 7 款超级实用!

    VS Code 是这两年非常热门的一款 IDE,它不仅有提升开发体验的界面.轻量化的编辑器,还有丰富而强大的插件,这些优秀的插件使得 VS Code 生态体系更加吸引人,让开发效率大大提升.本文来介绍 ...

  4. 二维数组初始化vector, 以及类型转换问题

    //二维数组的初始化1 vector<vector<float>> _box_parm(class_row_num, vector<float>(class_col ...

  5. Helm实战案例二:在Kubernetes(k8s)上使用helm安装部署日志管理系统EFK

    目录 一.系统环境 二.前言 三.日志管理系统EFK简介 四.helm安装EFK 4.1 helm在线安装EFK 4.2 helm离线安装EFK(推荐) 五.访问kibana 5.1 数据分片 六.卸 ...

  6. 一分钟学一个 Linux 命令 - ps

    前言 大家好,我是 god23bin.欢迎来到<一分钟学一个 Linux 命令>系列,每天只需一分钟,记住一个 Linux 命令不成问题.今天要说的是 ps 命令. 什么是 ps 命令? ...

  7. 一文了解Go语言的函数

    1. 引言 函数是编程中不可或缺的组成部分,无论是在Go语言还是其他编程语言中,函数都扮演着重要的角色.函数能够将一系列的操作封装在一起,使得代码更加模块化.可重用和易于维护. 在本文中,我们将详细介 ...

  8. 前端Vue自定义服务说明弹窗弹框 自下而上底部弹框

    前端Vue自定义服务说明弹窗弹框 自下而上底部弹框, 请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=13108 效果图如下: cc-serv ...

  9. 1. Spring相关概念

    1. 初始 Spring ‍ 1.1 Spring 家族 ‍ 官网:​https://spring.io,从官网我们可以大概了解到: Spring 能做什么:用以开发 web.微服务以及分布式系统等, ...

  10. Python编程和数据科学中的机器学习:如何处理和可视化具有噪声和干扰的数据

    目录 随着数据科学和机器学习的快速发展,处理和分析具有噪声和干扰的数据成为了一个日益重要的挑战.在数据科学和机器学习中,噪声和干扰通常来自于各种因素,例如随机性和非随机性,数据缺失,数据集中的错误或错 ...