[GO]Gin框架学习笔记

2021年11月20日 阅读数:0
这篇文章主要向大家介绍[GO]Gin框架学习笔记,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

1.Gin框架入门

Gin框架的基本逻辑html

func main() {
    //Default返回一个默认的路由引擎
	r := gin.Default()
    //定义一个路由 能够返回一个渲染的html页面或者是json等格式的数据
	r.GET("/test", func(c *gin.Context) { 
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
		})
	})
    //运行
	r.Run()
}

Gin框架支持RESTful API的开发,即便用不一样的方式可使用同一个页面进行不一样的交互.前端

func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		c.JSON(http.StastsOK, gin.H{
			"message": "GET",
		})
	})

	r.POST("/test", func(c *gin.Context) {
		c.JSON(http.StastsOK, gin.H{
			"message": "POST",
		})
	})

	r.PUT("/test", func(c *gin.Context) {
		c.JSON(http.StastsOK, gin.H{
			"message": "PUT",
		})
	})

	r.DELETE("/test", func(c *gin.Context) {
		c.JSON(http.StastsOK, gin.H{
			"message": "DELETE",
		})
	})
    r.Any("/test", func(c *gin.Context) {...}) //Any能够匹配全部请求方法
    //为没有配置路由的地址设置一个默认的返回页面
    r.NoRoute(func(c *gin.Context) { c.HTML(http.StatusNotFound, "views/404.html", nil) }) }

Gin框架中的路由使用的是httprouter库。git

基本原理是构造一个路由地址的前缀树。github

 gin框架支持路由组,好比这样,能够方便配置二级路由.golang

userGroup := r.Group("/user")
	{
		userGroup.GET("/index", func(c *gin.Context) {...})
		userGroup.GET("/login", func(c *gin.Context) {...})
		userGroup.POST("/login", func(c *gin.Context) {...})

	}
	shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {...})
		shopGroup.GET("/cart", func(c *gin.Context) {...})
		shopGroup.POST("/checkout", func(c *gin.Context) {...})
	}

一样也能够嵌套路由组json

shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {...})
		shopGroup.GET("/cart", func(c *gin.Context) {...})
		shopGroup.POST("/checkout", func(c *gin.Context) {...})
		// 嵌套路由组
		xx := shopGroup.Group("xx")
		xx.GET("/oo", func(c *gin.Context) {...})
	}

2.使用Gin获取参数

2.1 querystring参数

  获取前端传递的,显示在url后面的querystring参数,如127.0.0.1:8080/get?username="yyy"浏览器

r.GET("/get", func(c *gin.Context) {
  		//若是没有传这个参数,就使用默认的zzz
		username := c.DefaultQuery("username", "zzz")
		username := c.Query("username")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
		})

2.2 form参数

获取前端使用form形式传递的参数.
r.POST("/posttest", func(c *gin.Context) {
		// 取不到值时会返回设置的默认值
		//username := c.DefaultPostForm("username", "zzz")
		username := c.PostForm("username")
		address := c.PostForm("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})

使用postman测试服务器

2.3 JSON参数

获取前端以JSON形式传递的参数.框架

r.POST("/json", func(c *gin.Context) {
  	// 从c.Request.Body读取请求数据 t为一个json类型的数据
	t, _ := c.GetRawData()  
	// 定义map或结构体 用于存放数据
	var m map[string]interface{} 
	// 将JSON格式的数据反序列化 放到m这个映射中
	_ = json.Unmarshal(t, &m)
	//返回m数据
	c.JSON(http.StatusOK, m)
})

使用postman测试async

2.4 path参数

请求的参数经过URL路径传递,例如:127.0.01:8080/test/我爱你

r.GET("/test/:username/:talk", func(c *gin.Context) {
		username := c.Param("username")
		talk := c.Param("talk")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"talk":  talk,
		})
	})
// 这个规则既能匹配/user/john/格式也能匹配/user/john/send这种格式
// 若是没有其余路由器匹配/user/john,它将重定向到/user/john/
r.GET("/user/:name/*action", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		message := name + " is " + action
		c.String(http.StatusOK, message)
	})

输入url测试:

2.5参数绑定

为了可以更方便的获取请求相关参数,提升开发效率,咱们能够基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它可以基于请求自动提取JSONform表单QueryString类型的数据,并把值绑定到指定的结构体对象。

// Binding from JSON
type Login struct {
	User     string `form:"user" json:"user" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

func main() {
	router := gin.Default()

	// 绑定JSON的示例 ({"user": "zzz", "password": "123456"})
	router.POST("/loginJSON", func(c *gin.Context) {
		var login Login

		if err := c.ShouldBind(&login); err == nil {
			fmt.Printf("login info:%#v\n", login)
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 绑定form表单示例 (user=zzz&password=123456)
	router.POST("/loginForm", func(c *gin.Context) {
		var login Login
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// 绑定QueryString示例 (/loginQuery?user=zzz&password=123456)
	router.GET("/loginForm", func(c *gin.Context) {
		var login Login
		// ShouldBind()会根据请求的Content-Type自行选择绑定器
		if err := c.ShouldBind(&login); err == nil {
			c.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
	})

	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")
}

注意,这里GET请求和POST请求使用参数绑定有所不一样,GET方法中只会使用 Form 绑定引擎(query)。

而POST方法则会首先检查 content-type 是否为 JSONXML,使用对应的绑定方法进行绑定,若是不是再使用 Formform-data)。

还能够绑定uri,html前端返回的复选框信息等

3.文件上传

 首先定义一个简单的前端文件upload.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>上传文件示例</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="f1">
    <input type="submit" value="上传">
</form>
</body>
</html>

 

 

 

 咱们加载这个html文件,而后写一个GET方法,无需返回值,用于前端渲染

	r.LoadHTMLFiles("./templates/upload.html")
	r.GET("/upload",func(c *gin.Context){
		c.HTML(http.StatusOK,"upload.html",nil)
	})

 使用一个POST方法用于接收浏览器传来的文件数据,

r.POST("/upload", func(c *gin.Context) {
	c.HTML(http.StatusOK,"upload.html",nil)
	file,err:=c.FormFile("f1") //读取html上传的文件f1,f1是在html中定义的文件名
	if err!=nil{
		c.JSON(http.StatusInternalServerError,gin.H{
			"message":err.Error(),
		})
		return
	}
	log.Println(file.Filename)
	dst := fmt.Sprintf("D:/software/%s",file.Filename) //拼接上传到服务器保存的路径
	c.SaveUploadedFile(file,dst)  //上传到指定路径
	c.JSON(http.StatusOK,gin.H{ //返回json信息
		"message":fmt.Sprintf("'%s' upload!",file.Filename),
	})
})

 多文件上传

r.POST("/upload", func(c *gin.Context) {
		// Multipart form
		form, _ := c.MultipartForm()
		files := form.File["file"]

		for index, file := range files {
			log.Println(file.Filename)
			dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
			// 上传文件到指定的目录
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("%d files uploaded!", len(files)),
		})
	})

4.重定向

HTTP重定向到指定的uri中

r.GET("/test", func(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
})

 路由重定向到其余路由

r.GET("/test", func(c *gin.Context) {
    // 指定重定向的URL
    c.Request.URL.Path = "/test2"
    r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"hello": "world"})
})

5.自定义中间件

中间件能够在处理请求的时候,加入本身的处理函数,中间件适合处理一些公共的业务逻辑,好比登陆认证、权限校验、数据分页、记录日志、耗时统计等。

5.1 定义中间件

Gin中的中间件是一个gin.HandlerFunc类型。

给出一个的中间件案例,用于统计执行时间.

func TimeCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()
		// 能够经过c.Set在请求上下文中设置值,后续的处理函数可以取到该值
		c.Set("example", "12345")
		// before request 调用请求的剩余处理程序

		c.Next()

		// after request 不调用请求的剩余处理程序
		cost := time.Since(t)
		log.Print(cost)
// access the status we are sending status := c.Writer.Status() log.Println(status) } }

5.2 注册中间件

全局注册

r.Use(TimeCost())

r.GET("/test", func(c *gin.Context) {
		name := c.MustGet("name").(string) // 能够从上下文取值,获取中间件设置的值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})

 

单独对某个路由注册

r.GET("/test2", TimeCost(), func(c *gin.Context) {
		name := c.MustGet("name").(string) // 从上下文取值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})

为路由组注册中间件

shopGroup := r.Group("/shop", TimeCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}
//或者
shopGroup := r.Group("/shop")
shopGroup.Use(TimeCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

5.3 在中间件中使用goroutine

在中间件或处理程序中启动新的Goroutines时,不该该使用其中的原始上下文,必须使用只读副本(c.Copy()

r.GET("/long_async", func(c *gin.Context) {
		// 建立要在goroutine中使用的副本
		cCp := c.Copy()
		go func() {
			// simulate a long task with time.Sleep(). 5 seconds
			time.Sleep(5 * time.Second)

			// 这里使用你建立的副本
			log.Println("Done! in path " + cCp.Request.URL.Path)
		}()
	})

	r.GET("/long_sync", func(c *gin.Context) {
		// simulate a long task with time.Sleep(). 5 seconds
		time.Sleep(5 * time.Second)

		// 这里没有使用goroutine,因此不用使用副本
		log.Println("Done! in path " + c.Request.URL.Path)
	})

5.4 gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即便配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。若是有panic的话,会写入500响应码。

若是不想使用上面两个默认的中间件,可使用gin.New()新建一个没有任何默认中间件的路由。

5.5 自定义HTTP配置

能够直接使用http.ListenAndServe

func main() {
	router := gin.Default()
	http.ListenAndServe(":8080", router)
}

 也能够进行配置

func main() {
	router := gin.Default()

	s := &http.Server{
		Addr:           ":8080",
		Handler:        router,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	s.ListenAndServe()
}

6.多端口运行

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
)

var (
	g errgroup.Group
)

func router01() http.Handler {
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
		c.JSON(
			http.StatusOK,
			gin.H{
				"code":  http.StatusOK,
				"error": "Welcome server 01",
			},
		)
	})

	return e
}

func router02() http.Handler {
	e := gin.New()
	e.Use(gin.Recovery())
	e.GET("/", func(c *gin.Context) {
		c.JSON(
			http.StatusOK,
			gin.H{
				"code":  http.StatusOK,
				"error": "Welcome server 02",
			},
		)
	})

	return e
}

func main() {
	server01 := &http.Server{
		Addr:         ":8080",
		Handler:      router01(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	server02 := &http.Server{
		Addr:         ":8081",
		Handler:      router02(),
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
   // 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
	g.Go(func() error {
		return server01.ListenAndServe()
	})

	g.Go(func() error {
		return server02.ListenAndServe()
	})

	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}