Golang-Gin光速入门

安装

sh 复制代码
go get -u github.com/gin-gonic/gin

初始化项目并启动服务

go mod init gin-project

go 复制代码
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

上面启动了一个服务器,gin.Default方法返回一个带有日志和错误捕获中间件的引擎

项目目录

参数

项目目录

main.go中

go 复制代码
package main
import (
	"gin-project/global"
	"gin-project/initialize"
)
func main() {
	global.GVB_ENG = initialize.InitEngine()
	if global.GVB_ENG != nil {
		initialize.InitRouter()
		global.GVB_ENG.Run()
	}
}

router/params.go

go 复制代码
package router

import (
	"gin-project/controller"
	"gin-project/global"
)

func InitParamsGroup() {
	paramsRouter := global.GVB_ENG.Group("/use")
	{
		paramsRouter.GET("/uri/:id", controller.ParamsController.GetURI)
		paramsRouter.GET("/query", controller.ParamsController.GetQuery)
		paramsRouter.POST("/formdata", controller.ParamsController.GetFomdata)
	}
}

initialize/router.go

go 复制代码
package initialize

import "gin-project/router"

func InitRouter() {
	router.InitParamsGroup()
}

uri参数

文档

uri参数长什么样?->http://fancy_fish.top/123456

在这个url后面的123456就是uri参数

获取uri参数方式一ShouldBindUri

如何获取uri参数呢

  1. 创建一个结构体字段要和uri占位符一致
  2. 给结构体对应字段设置关联标签
  3. 调用ShouldBindUri获取即可
go 复制代码
paramsRouter := global.GVB_ENG.Group("/use")
paramsRouter.GET("/uri/:id/:name", 
GetURI)

func (receiver *params) GetURI(c *gin.Context) {
	type Person struct {
		ID   string `uri:"id" binding:"required"`
		Name string `uri:"name"`
	}
	var p Person
	if err := c.ShouldBindUri(&p); err != nil {
		c.JSON(400, gin.H{"msg": err.Error()})
		return
	}
	c.JSON(200, gin.H{
		"data": p,
	})
}

获取URI参数方式二Param

go 复制代码
func (receiver *params) GetURI(c *gin.Context) {
	type Person struct {
		ID   string `uri:"id" binding:"required"`
		Name string `uri:"name"`
	}
	var p Person

	p.ID = c.Param("id")
	p.Name = c.Param("name")
	c.JSON(200, gin.H{
		"data": p,
	})
}

query参数

query参数长什么样?http://127.0.0.1:8080/use/query?id=12452&name=fancy_fish

go 复制代码
paramsRouter.GET("/query", GetQuery)

func (receiver *params) GetQuery(c *gin.Context) {
	type Person struct {
		ID   string
		Name string
	}
	p := Person{}
	p.ID = c.Query("id")
	p.Name = c.DefaultQuery("name", "默认Query")
	c.JSON(200, gin.H{
		"data": p,
	})
}

DefaultQuery

当没有获取到指定query参数,会给默认值。如上图所示。

formdata参数

go 复制代码
paramsRouter.POST("/formdata",GetFormdata)

func (receiver *params) GetFormdata(c *gin.Context) {
	type Person struct {
		ID   string
		Name string
	}
	p := Person{}
	p.ID = c.PostForm("id")
	p.Name = c.DefaultPostForm("name", "默认postform")
	c.JSON(200, gin.H{
		"data": p,
	})
}

DefaultPostForm

当没有获取到指定postform参数,会给默认值。如图所示,代码同上。

上传单个文件FormFile

我们先上传个文件打印一下看看获取到的结果是什么

go 复制代码
paramsRouter.POST("/file/upload", controller.ParamsController.GetFile)
func (receiver *params) GetFile(c *gin.Context) {
	f, err := c.FormFile("file")
	fmt.Println(f.Size, f.Filename, f.Header)// 266438  1.png  map[Content-Disposition:[form-data; name="file"; filename="1.png"] Content-Type:[image/png]]
	if err != nil {
		c.String(500, "上传文件失败")
	}
	c.JSON(200, gin.H{
		"data": "",
	})
}

可以看到可以得到文件的文件名、文件大小、格式等

保存到本地

go 复制代码
func (receiver *params) GetFile(c *gin.Context) {
	f, err := c.FormFile("file")
	if err != nil {
		c.String(500, "上传文件失败")
		return
	}
	if err := c.SaveUploadedFile(f, path.Join("./assets", f.Filename)); err != nil {
		fmt.Println(err.Error(), "文件保存失败")
		return
	}
	c.JSON(200, gin.H{
		"message":  "success",
		"code":     1,
		"fileName": f.Filename,
	})
}

上传多个文件MultipartForm

go 复制代码
paramsRouter.POST("/files/upload", controller.ParamsController.GetFiles)
func (receiver *params) GetFiles(c *gin.Context) {
	form, err := c.MultipartForm()
	if err != nil {
		c.String(500, "上传文件失败")
		return
	}
	files := form.File["files"]

	fileNames := make([]string, 0)
	for index, f := range files {
		fmt.Println(index, f.Filename, f.Size)
		fileNames = append(fileNames, f.Filename)
		if err := c.SaveUploadedFile(f, path.Join("./assets", f.Filename)); err != nil {
			fmt.Println(err.Error(), "文件保存失败")
			return
		}
	}
	c.JSON(200, gin.H{
		"message":  "success",
		"code":     1,
		"fileName": fileNames,
	})
}

数据绑定

GIN提供我们API可以让我们将客户端传递的参数直接绑定到结构体,我们只需要对结构体字段打标签即可。GIN可以绑定一下几类数据,接下来直接展示。

Gin使用 go-playground/validator/v10 进行验证,可以查看标签用法的全部文档。

我们使用ShouildBindWith去绑定就行了

ShouldBindJSON绑定JSON

go 复制代码
paramsRouter.POST("/bind_json", controller.ParamsController.GetBindJson)
// 控制器
func (receiver *params) GetBindJson(c *gin.Context) {
	type user struct {
		Name     string `json:"name" binding:"required"`
		Password uint   `json:"password" binding:"required"`
	}
	var u user
	if err := c.ShouldBindJSON(&u); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if u.Name != "fancy_fish" || u.Password != 123 {
		c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "成功登录"})
}

ShouldBindUri绑定URI

go 复制代码
paramsRouter.GET("/bind_uri/:name/:password", controller.ParamsController.GetBindUri)
func (receiver *params) GetBindUri(c *gin.Context) {
	type user struct {
		Name     string `uri:"name" binding:"required"`
		Password uint   `uri:"password" binding:"required"`
	}
	var u user
	if err := c.ShouldBindUri(&u); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if u.Name != "fancy_fish" || u.Password != 123 {
		c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "成功登录", "data": u})
}

其余的绑定自己查文档体会即可。

参数校验

1.先注册校验器

go 复制代码
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
	// 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
	v.RegisterValidation("ValidatePassword", utils.ValidatePassword)
}

2.定义校验标签

go 复制代码
	type user struct {
		Name     string `uri:"name" binding:"required"`
		Password uint   `uri:"password" binding:"required,ValidatePassword"`
	}

3.定义校验函数

go 复制代码
package utils
import (
	"fmt"
	"github.com/go-playground/validator/v10"
)
var ValidatePassword validator.Func = func(fl validator.FieldLevel) bool {
	fmt.Println("开启验证")
	return false
}

路由分组

路由分组可以将相同业务类型的路由划分到一起,便于项目维护和开发。

go 复制代码
func main() {
   // 1.创建路由
   r := gin.Default()
   // 路由组v1 ,处理GET请求
   v1 := r.Group("/v1")
   // {} 是书写规范
   {
      v1.GET("/login", login)  //相当于/v1/login
      v1.GET("/submit", submit) //相当于/v1/submit
   }
   v2 := r.Group("/v2")
   {
      v2.POST("/login", login)  //相当于/v2/login
      v2.POST("/submit", submit) //相当于/v2/submit
   }
   r.Run(":8000")
}

路由封装

为了提高项目可维护性会将项目结构划分,我们上面的示例都是划分好的。

1.创建router目录管理路由

go 复制代码
package router

import (
	"gin-project/controller"
	"gin-project/global"
)

func InitParamsGroup() {
	paramsRouter := global.GVB_ENG.Group("/use")
	{
		paramsRouter.GET("/uri/:id/:name", controller.ParamsController.GetURI)
		paramsRouter.GET("/query", controller.ParamsController.GetQuery)
		paramsRouter.POST("/formdata", controller.ParamsController.GetFormdata)
		paramsRouter.POST("/file/upload", controller.ParamsController.GetFile)
		paramsRouter.POST("/files/upload", controller.ParamsController.GetFiles)
	}
}

2.创建controller抽离控制层

go 复制代码
package controller

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"path"
)

type params struct{}

func (receiver *params) GetURI(c *gin.Context) {}
func (receiver *params) GetQuery(c *gin.Context) {}
func (receiver *params) GetFormdata(c *gin.Context) {}
func (receiver *params) GetFile(c *gin.Context) {}
func (receiver *params) GetFiles(c *gin.Context) {}

var ParamsController = new(params)

3.创建initialize/router.go初始化路由函数

go 复制代码
package initialize

import "gin-project/router"

func InitRouter() {
	router.InitParamsGroup()
}

4.main.go启动服务

go 复制代码
	r := gin.Default()
	if r != nil {
		initialize.InitRouter()
		r.Run()
	}

中间件

gin的中间件和js中koa一样都是使用的洋葱圈模型

tip:中间件的注册一定在路由之前,否则不会生效。

中间件中使用协程

在Gin框架中,当你在中间件或处理程序(handler)中启动新的Goroutine时,需拷贝上下文对象。应该使用只读副本的原因是为了

这样做的目的是什么?1.避免竞态条件(race condition)2.避免上下文污染(context pollution)。

在Gin框架中,每个请求都有一个独立的上下文(Context),用于存储请求相关的信息和数据。而中间件和处理程序是按顺序执行的,它们可能会在同一个请求中启动多个Goroutine。如果你在Goroutine中直接使用原始的上下文,那么这个Goroutine和处理程序之间就会共享同一个上下文对象。这样一来,如果多个Goroutine同时对上下文进行读写操作,就可能引发竞态条件,导致数据不一致或错误的结果。

当多个Goroutine同时对上下文进行修改时,它们可能会相互影响,导致数据被意外覆盖或混乱。

go 复制代码
func main() {
	r := gin.Default()

	r.GET("/long_async", func(c *gin.Context) {
		// 创建在 goroutine 中使用的副本
		cCp := c.Copy()
		go func() {
			// 用 time.Sleep() 模拟一个长任务。
			time.Sleep(5 * time.Second)

			// 请注意您使用的是复制的上下文 "cCp",这一点很重要
			log.Println("Done! in path " + cCp.Request.URL.Path)
		}()
	})

	r.GET("/long_sync", func(c *gin.Context) {
		// 用 time.Sleep() 模拟一个长任务。
		time.Sleep(5 * time.Second)

		// 因为没有使用 goroutine,不需要拷贝上下文
		log.Println("Done! in path " + c.Request.URL.Path)
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run(":8080")
}

全局中间件

所有请求都会经过此中间件

go 复制代码
package main
import (
    "fmt"
    "time"
    "github.com/gin-gonic/gin"
)
// 定义中间
func GlobalMiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到Context的key中,可以通过Get()取
        c.Set("request", "中间件")
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1.创建路由
    r := gin.Default()
    // 注册中间件
    r.Use(GlobalMiddleWare())
    {
        r.GET("/c", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 页面接收
            c.JSON(200, gin.H{"request": req})
        })
    }
    r.Run()
}

中间件之间传值

比如有两个中间件A和B ,A执行完B执行,B中间件依赖A中间件的某个数据怎么办呢?看代码

go 复制代码
// 定义中间
type AMiddleware struct{}

func (receiver AMiddleware) CreateAMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Println("aaaaaa")
		c.Set("AKey", "AValue")
		c.Next()
	}
}
type BMiddleware struct{}
func (receiver BMiddleware) CreateBMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		v, exist := c.Get("AKey")
		if !exist {
			fmt.Println("没有传递Avalue")
		} else {
			fmt.Println(v)
		}
		c.Next()
	}
}

func main() {
    // 1.创建路由
    r := gin.Default()
    // 注册中间件
    r.Use(AMiddleWare(),BMiddleWare())
    {
        r.GET("/c", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 页面接收
            c.JSON(200, gin.H{"request": req})
        })
    }
    r.Run()
}

gin渲染模版

定义模版

  1. 我们需要在项目根目录下创建template文件夹
  2. 然后配置gin引擎加载
go 复制代码
global.GVB_ENG.LoadHTMLGlob("template/**/*")
  1. 之后创建.tmpl文件
  2. 添加如下代码
go 复制代码
{{ define "header/index.tmpl" }}
<html>
<header>
    <h1>
        {{ .title }}
    </h1>
</header>
</html>
{{ end }}

5.控制器,gin.H会将参数传递进去,然后我们可以看到页面。

go 复制代码
func (receiver params) GetTemplate(c *gin.Context) {
	c.HTML(http.StatusOK, "header/index.tmpl", gin.H{
		"title": "这是传递给模版的参数",
	})
}
相关推荐
2401_8582861114 分钟前
C6.【C++ Cont】cout的格式输出
开发语言·c++
海害嗨27 分钟前
牛客网Java高频面试题(2024最新版含答案)
java·开发语言
今天我又学废了1 小时前
scala学习记录,Set,Map
开发语言·学习·scala
What_can_i_say jdk?1 小时前
初学Java基础Day22---枚举
java·开发语言
豆本-豆豆奶1 小时前
用 Python 写了一个天天酷跑(附源码)
开发语言·python·游戏·pygame·零基础教程
stm 学习ing2 小时前
C语言 循环高级
c语言·开发语言·单片机·嵌入式硬件·算法·嵌入式实时数据库
白子寰2 小时前
【C++打怪之路Lv13】- “继承“篇
开发语言·c++
lly2024062 小时前
Scala IF...ELSE 语句
开发语言
王俊山IT2 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(1)
开发语言·c++·笔记·学习