前言

基于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. MySQL 中读写分离数据延迟

    MySQL 中读写分离可能遇到的问题 前言 读写分离的架构 基于客户端实现读写分离 基于中间代理实现读写分离 MySQL 中如何保证主从数据一致 循环复制问题 主从同步延迟 主从同步延迟的原因 主从延 ...

  2. cefsharp学习笔记

    环境:VS2015+cefsharp 57.0 全部代码如下: 1.要初始化,否则不能刷新 using System; using CefSharp; using System.Collections ...

  3. Rsync文件同步及备份

    Rsync文件同步及备份 目录 Rsync文件同步及备份 Rsync基本概述 远程文件传输 服务端口 Rsync的三种传输模式 本地方式(类似cp) 远程方式(类似scp) 守护进程(C/S结构) R ...

  4. Prompt learning 教学[进阶篇]:简介Prompt框架并给出自然语言处理技术:Few-Shot Prompting、Self-Consistency等;项目实战搭建知识库内容机器人

    Prompt learning 教学[进阶篇]:简介Prompt框架并给出自然语言处理技术:Few-Shot Prompting.Self-Consistency等:项目实战搭建知识库内容机器人 1. ...

  5. 2022-11-07:给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,其中每个节点 至多 有一条出边。 图用一个大小为 n 下标从 0 开始的数组 edges 表示, 节点 i 到

    2022-11-07:给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,其中每个节点 至多 有一条出边. 图用一个大小为 n 下标从 0 开始的数组 edges 表示, 节点 i 到 ...

  6. 2022-09-19:给定字符串 S and T,找出 S 中最短的(连续)子串 W ,使得 T 是 W 的 子序列 。 如果 S 中没有窗口可以包含 T 中的所有字符,返回空字符串 ““。 如果有不

    2022-09-19:给定字符串 S and T,找出 S 中最短的(连续)子串 W ,使得 T 是 W 的 子序列 . 如果 S 中没有窗口可以包含 T 中的所有字符,返回空字符串 "&q ...

  7. 2022-01-04:一个无序数组长度为n,所有数字都不一样,并且值都在[0...n-1]范围上。 返回让这个无序数组变成有序数组的最小交换次数。 来自小红书。

    2022-01-04:一个无序数组长度为n,所有数字都不一样,并且值都在[0-n-1]范围上. 返回让这个无序数组变成有序数组的最小交换次数. 来自小红书. 答案2022-01-04: 下标循环怼. ...

  8. simplejwt配置大全

    # simplejwt配置大全SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # 设置token有效时间 'REFRESH_ ...

  9. vue 中render执行流程梳理

    用了多年vue 今天对自己了解的render 做一个梳理 一.使用template模板 先从vue 初始化开始: 众所周知项目的main.js中定义了 var app = new Vue({})这vu ...

  10. svn is already locked 最终解决方案

    今日执行项目更新时,手贱点击了cancel 中断了操作,最后导致项目被锁,杯具了. 首先想到了Clean up 直接提示 看来不行呀 -// 省略 n 多种尝试 最后使用删除db 中的 lock 表来 ...