Bendi新闻
>
简单的gin-mongo
简单的gin-mongo
4月前
前言
基于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:[email protected]: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")
}
参考
https://www.mongodb.com/docs/drivers/go/current/quick-start/
https://redis.uptrace.dev/guide/go-redis.html
附录
准备数据
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:[email protected]: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()
链接:https://www.cnblogs.com/XY-Heruo/p/16414801.html
(版权归原作者所有,侵删)
微信扫码关注该文公众号作者
来源:马哥Linux运维
相关新闻
不用揉面,一撕一扯就是一张饼!最简单的荤素搭配大法,高温天解馋都靠它学员故事|从欧洲小国数据分析师,到卢森堡风投基金公司,最简单的反而是这6轮的面试……从欧洲小国数据分析师,到卢森堡风投基金公司,最简单的反而是这6轮的面试……巴黎奥运会带空调,不是一个简单的谣言今天的中国消费,不是日本90年代的简单复刻一年掉30磅的简单秘诀 他怎么做到的?彻底根除“论文工厂”的方法,只会是“更简单的论文发表方式”;文献计量分析,让医生彻底解脱开发论文的桎梏![败家] 几把简单的工具刀(男人都爱刀)超过股票和黄金!美国房地产是最好的长期投资!这是最简单的入门方法...女人旺自己最简单的方法!搞钱是一件非常简单的事(马上删)用最简单的方式做投资!一文教会你如何定投改良过3、4次配方,我发现越简单的越好吃!非油炸还更香,它比薯条更受欢迎新加坡合作社要动用储备金来支付股息,不是一件简单的事海外品牌入华,一个复杂问题的简单化一个简单的公式,女儿做事再也不“三分钟热度”了!给自家的草坪施肥原来这么简单的吗?这个方法既能节省成本,效果还很不错!5/25/2024-5/31/2024 法拉盛新龍興:逛超市没有文案,只有快乐,而提升幸福感最简单的方式,当然又是逛超市啦!更简单的京东618,从5月31日晚8点开始!我找到了史上最简单的小甜品!酸甜浓郁很开胃,随便做做就能成功超简单的抗老妙招,垮脸患者的福音用了一个简单的方法,我在港股2个月赚了50万[干货] 分享一个简单的高级句型:more than抖音最简单的赚钱项目