Gin框架使用

Gin

Gin框架安装与使用

下载安装Gin:

go get -u github.com/gin-gonic/gin

使用示例:

package main

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

func main() {
	//创建一个默认的路由引擎
	r := gin.Default()
	//GET:请求方式,/hello:请求路径
	//当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
	r.GET("/hello", func(c *gin.Context) {
		// c.JSON:返回JSON格式的数据
		c.JSON(200, gin.H{
			"message": "hello gin",
		})
	})
	//启动服务,默认端口是8080
	r.Run(":9090")
}

启动服务,浏览器访问:

Gin框架使用_第1张图片

RESTful API

REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。

Gin框架使用_第2张图片

Gin框架支持开发RESTful API的开发。

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	// 创建默认引擎
	r := gin.Default()
	//请求编写
	//查询
	r.GET("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "GET",
		})
	})
	//创建
	r.POST("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "POST",
		})
	})
	//修改
	r.PUT("/book", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "PUT",
		})
	})
	//删除
	r.DELETE("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "DELETE",
		})
	})
	//启动HTTP服务
	r.Run()
}

模板引擎标准库 http/template

Go语言的模板引擎

Go语言内置了文本模板引擎text/template和用于HTML文档的html/template。它们的作用机制可以简单归纳如下:

  1. 模板文件通常定义为.tmpl.tpl为后缀(也可以使用其他的后缀),必须使用UTF8编码。
  2. 模板文件中使用{{}}包裹和标识需要传入的数据。
  3. 传给模板这样的数据就可以通过点号(.)来访问,如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。
  4. {{}}包裹的内容外,其他内容均不做修改原样输出。

模板引擎的使用

Go语言模板引擎的使用可以分为三部分:定义模板文件、解析模板文件和模板渲染.

1、定义模板文件

2、解析模板文件

上面定义好了模板文件之后,可以使用下面的常用方法去解析模板文件,得到模板对象:

func (t *Template) Parse(src string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)

也可以使用func New(name string) *Template函数创建一个名为name的模板,然后对其调用上面的方法去解析模板字符串或模板文件。

3、模板渲染

渲染模板简单来说就是使用数据去填充模板,当然实际上可能会复杂很多。

func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error

JSON渲染

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	r.GET("json", func(c *gin.Context) {
		// 方法一:使用map
		//data := map[string]interface{}{
		//	"name":    "Weiyuxin",
		//	"message": "hello gin",
		//	"age":     18,
		//}
		// gin.H预定义成map[string]interface{}
		data := gin.H{
			"name":    "Weiyuxin",
			"message": "hello gin",
			"age":     18,
		}

		c.JSON(http.StatusOK, data)
	})
	r.GET("/json1", func(c *gin.Context) {
		//方法2:结构体
		type msg struct {
			Name    string `json:"name"` //不能小写,否则无法访问,要想返回的json是小写,需要设置
			Message string
			Age     int8
		}
		data := msg{"weiyuexin", "hello gin json", 90}
		c.JSON(http.StatusOK, data) //json序列化使用反射
	})
	r.Run(":9090")
}

获取参数

获取querystring参数(常用于GET请求)

querystring指的是URL中?后面携带的参数,例如:`/user/search?username=小王子&address=沙河

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

//获取参数
func main() {
	r := gin.Default()
	//GET请求URL 的?后面的是query string参数
	// 通过key-value格式,多个key-value通过 & 连接
	r.GET("/query", func(c *gin.Context) {
		//获取浏览器发送过来的请求携带的 query string
		name := c.Query("name") //通过Query获取请求中携带的querystring参数
		age := c.Query("age")   //通过Query获取请求中携带的querystring参数
		//name := c.DefaultQuery("name", "wyx") //查到的话就用查到的值,查不到就用默认值
		//name, ok := c.GetQuery("name") //返回取到的值和是否取到
		//if !ok {
		//	c.JSON(http.StatusOK, gin.H{
		//		"ok": "请输入参数",
		//	})
		//	return
		//}
		c.JSON(http.StatusOK, gin.H{
			"ok": "hello," + name + ",age=" + age,
		})
	})
	r.Run(":9090")
}

运行结果:

Gin框架使用_第3张图片

获取form参数(常用POST请求)

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

//获取form表单提交的参数
func main() {
	r := gin.Default()
	r.POST("/login", func(c *gin.Context) {
		//username:=http.PostForm("username") //获取form表单提交的数据
		//username := c.DefaultPostForm("username", "wyx")//获取不到的话就使用默认值
		username, ok1 := c.GetPostForm("username") //获取数据和获取结果
		password, ok2 := c.GetPostForm("password")
		msg := "登录成功"
		data := make(map[string]string, 2)
		status := 200
		if !ok1 {
			msg = "请输入用户名"
			status = 500
		}
		if !ok2 {
			msg = "请输入密码"
			status = 500
		}

		data["username"] = username
		data["password"] = password
		
		c.JSON(http.StatusOK, gin.H{
			"code": status,
			"msg":  msg,
			"data": data,
		})
	})

	r.Run(":9090")
}

运行结果:

Gin框架使用_第4张图片

获取path参数

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

//获取URL路径参数
func main() {
	r := gin.Default()

	r.GET("/user/:userId/:username", func(c *gin.Context) {
		//获取路径参数
		userId := c.Param("userId")
		username := c.Param("username")
		c.JSON(http.StatusOK, gin.H{
			"userId":   userId,
			"username": username,
		})
	})

	r.Run(":9090")
}

运行结果:

Gin框架使用_第5张图片

获取json参数

package main

import (
	"encoding/json"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	r.POST("/json", func(c *gin.Context) {
		data, _ := c.GetRawData()
		//定义map或结构体
		var m map[string]interface{}
		//反序列化
		_ = json.Unmarshal(data, &m)
		c.JSON(http.StatusOK, m)
	})
	r.Run(":9090")
}

运行结果:

Gin框架使用_第6张图片

绑定参数

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

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

type User struct {
	Username string `form:"username" json:"username" `
	Password string `form:"password" json:"password" `
}

//绑定参数
func main() {
	r := gin.Default()
	//绑定query-string参数
	r.GET("/user", func(c *gin.Context) {
		var user User              //声明一个User类型的变量user
		err := c.ShouldBind(&user) //绑定参数到结构体,这里传的一定得是地址
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"msg":  "hello",
				"data": user,
			})
		}
	})
	//绑定form参数
	r.POST("/form", func(c *gin.Context) {
		var user User              //声明一个User类型的变量user
		err := c.ShouldBind(&user) //绑定参数到结构体,这里传的一定得是地址
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"msg":  "hello",
				"data": user,
			})
		}
	})
	//绑定json数据
	r.POST("/json", func(c *gin.Context) {
		var user User
		err := c.ShouldBind(&user)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"msg":  "hello",
				"data": user,
			})
		}
	})
	r.Run(":9090")
}

运行结果:

Gin框架使用_第7张图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AVYMTpiu-1690859834109)(https://cdn.jsdelivr.net/gh/weiyuexin/blogimg@latest/img/2023/08/01/20230801111636.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QPkI9rcV-1690859834110)(./assets/image-20230315105106348.png)]

文件上传

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

//文件上传
func main() {
	r := gin.Default()
	//上传单个文件
	r.POST("/upload", func(c *gin.Context) {
		//从请求中读取文件
		file, err := c.FormFile("file")
		if err != nil { //读取失败
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			//将读取到的文件保存到服务器,dst是保存的位置
			dst := fmt.Sprintf("C:\\Users\\30224\\Desktop\\GoLearn\\gin\\gin_06_upload_file\\%s", file.Filename)
			c.SaveUploadedFile(file, dst)
			c.JSON(http.StatusOK, gin.H{
				"status":   "ok",
				"msg":      "upload success",
				"filePath": dst,
			})
		}
	})
	//上传多个文件
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	r.POST("/uploads", func(c *gin.Context) {
		form, _ := c.MultipartForm()
		files := form.File["file"]
		for index, file := range files {
			log.Println(file.Filename)
			dst := fmt.Sprintf("C:\\Users\\30224\\Desktop\\GoLearn\\gin\\gin_06_upload_file\\%d_%s", index, file.Filename)
			//上传文件到指定目录
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{
			"msg": fmt.Sprintf("%d files upload!", len(files)),
		})
	})
	r.Run(":9090")
}

重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

//http重定向
r.GET("/index", func(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
})

运行结果:直接跳转到新页面

路由重定向

路由重定向,使用HandleContext

//路由重定向
r.GET("/a", func(c *gin.Context) {
	//跳转到 /b 对应的路由函数
	c.Request.URL.Path = "/b" //把请求的URI修改cheng /b
	r.HandleContext(c)        //继续后续的处理
	c.JSON(http.StatusOK, gin.H{
		"msg": "a",
	})
})
r.GET("/b", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"msg": "b",
	})
})

运行结果:浏览器显示的地址没有变,先执行 /b 然后再执行 /a 的后续操作

Gin框架使用_第8张图片

路由和路由组

普通路由

r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
//请求方法集合,匹配所以请求方法
r.Any("/user", func(c *gin.Context) {
	switch c.Request.Method {
	case "GET":
		c.JSON(http.StatusOK, gin.H{"method": "GET"})
	case http.MethodPost:
		c.JSON(http.StatusOK, gin.H{"method": "POST"})
	case http.MethodDelete:
		c.JSON(http.StatusOK, gin.H{"method": "DELETE"})
	}
})

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

r.NoRoute(func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"msg": "404 not found"})
})

路由组

我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。

videoGroup := r.Group("/video")
{
	videoGroup.GET("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"msg": "/video/index"})
	})
	videoGroup.GET("/xx", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"msg": "/video/xx"})
	})
	videoGroup.GET("/oo", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"msg": "/video/oo"})
	})
}

路由组也是支持嵌套的,例如:

shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"msg": "/shop/index"})
		})
		xx := shopGroup.Group("/xx")
		{
			xx.GET("/oo", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"msg": "/shop/xx/oo"})
			})
		}
	}

Gin中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

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

// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
		// 调用该请求的剩余处理程序
		c.Next()
		// 不调用该请求的剩余处理程序
		// c.Abort()
		// 计算耗时
		cost := time.Since(start)
		log.Println(cost)
	}
}
//记录响应体的中间件
type bodyLogWriter struct {
	gin.ResponseWriter               // 嵌入gin框架ResponseWriter
	body               *bytes.Buffer // 我们记录用的response
}

// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)                  // 我们记录一份
	return w.ResponseWriter.Write(b) // 真正写入响应
}

// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {
	blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}
	c.Writer = blw // 使用我们自定义的类型替换默认的

	c.Next() // 执行业务逻辑

	fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}

注册中间件

在gin框架中,我们可以为每个路由添加任意数量的中间件。

为全局路由注册
func main() {
	// 新建一个没有任何默认中间件的路由
	r := gin.New()
	// 注册一个全局中间件
	r.Use(StatCost())
	
	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.Run()
}
为某个路由单独注册
// 给/test2路由单独注册中间件(可注册多个)
r.GET("/test2", StatCost(), 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", StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}
//方法二
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

中间件使用事项

Gin默认中间件

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

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

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

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

你可能感兴趣的:(gin)