gin框架学习(二) | 青训营

293 阅读8分钟

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 world!",
		})
	})
	// 启动HTTP服务,默认在0.0.0.0:8080启动服务
	r.Run()
}

gin.Default():func gin.Default() *gin.Engine

Default returns an Engine instance with the Logger and Recovery middleware already attached

r.GET():func (*gin.RouterGroup).GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes

GET is a shortcut for router.Handle("GET", path, handlers).

r.run():func (*gin.Engine).Run(addr ...string) (err error)

Run attaches the router to a http.Server and starts listening and serving HTTP requests. It is a shortcut for http.ListenAndServe(addr, router) Note: this method will block the calling goroutine indefinitely unless an error happens.

RESTful API

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

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

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。
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(200, gin.H{
			"message": "PUT",
		})
	})

	r.DELETE("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "DELETE",
		})
	})
}

html响应

package main

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

func main() {
	r := gin.Default()             // 默认路由
	r.Static("css/", "./html/css") // 把路径中的"css/"替换成"./html/css"
	r.Static("images/", "./html/images")
	r.LoadHTMLGlob("tmpletes/*") // 加载html
	r.GET("", func(ctx *gin.Context) {
		ctx.HTML(200, "index.html", nil)
	})
	r.Run(":9090") // 运行端口
}

r.Static()函数用于将静态资源(例如样式表、脚本文件、图片等)与HTTP路由进行映射。这样可以让客户端通过相应的URL路径访问这些静态资源

r.LoadHTMLGlob()函数用于加载HTML模板文件,并告诉Gin框架在哪个目录下寻找模板文件。该函数会根据提供的模板路径,加载所有匹配该路径模式的HTML模板文件。

只要提前预处理好这两个文件,大部分的html都可以实现响应了

以下内容取自其他博客:

返回html

先要使用 LoadHTMLGlob()或者LoadHTMLFiles()方法来加载模板文件

//加载模板
router.LoadHTMLGlob("gin框架/templates/*")
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
//定义路由
router.GET("/tem", func(c *gin.Context) {
	//根据完整文件名渲染模板,并传递参数
	c.HTML(http.StatusOK, "index.html", gin.H{
		"title": "Main website",
	})
})

在模板中使用这个title,需要使用{{ .title }}

不同文件夹下模板名字可以相同,此时需要 LoadHTMLGlob() 加载两层模板路径。

router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
        "title": "Posts",
    })
    c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
        "title": "Users",
    })

})

文件响应:

// 在golang总,没有相对文件的路径,它只有相对项目的路径
// 网页请求这个静态目录的前缀, 第二个参数是一个目录,注意,前缀不要重复
router.StaticFS("/static", http.Dir("static/static"))
// 配置单个文件, 网页请求的路由,文件的路径
router.StaticFile("/titian.png", "static/titian.png")

重定向:

router.GET("/redirect", func(c *gin.Context) {
    //支持内部和外部的重定向
    c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
})

json 响应

router.GET("/json", func(c *gin.Context) {
 	c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
// 结构体转json
router.GET("/moreJSON", func(c *gin.Context) {
	// You also can use a struct
	type Msg struct {
		Name    string `json:"user"`
		Message string
		Number  int
	}
	msg := Msg{"fengfeng", "hey", 21}
	// 注意 msg.Name 变成了 "user" 字段
	// 以下方式都会输出 :   {"user": "hanru", "Message": "hey", "Number": 123}
	c.JSON(http.StatusOK, msg)
})

请求

获取querystring参数

querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.GET("/user/search", func(c *gin.Context) {
		username := c.DefaultQuery("username", "小王子")
		//username := c.Query("username")
		address := c.Query("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	r.Run()
}
func _query(c *gin.Context) {
	fmt.Println(c.Query("user"))
	fmt.Println(c.GetQuery("user"))
	fmt.Println(c.QueryArray("user")) // 拿到多个相同的查询参数
	fmt.Println(c.DefaultQuery("addr", "四川省"))
}
r.GET("/test", func(ctx *gin.Context) {
	// 获取浏览器发送的请求
	//两种获取请求的方式
	a := ctx.DefaultQuery("name", "sb") // 取不到就用指定默认值
	b := ctx.Query("age")               // 获取含有age请求的参数
	ctx.JSON(200, gin.H{
		"name": a,
		"age":  b,
	})
	// 例如 /test?age=18&name=whopxx
})

表单 PostForm

获取form参数

当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下:

func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.POST("/user/search", func(c *gin.Context) {
		// DefaultPostForm取不到值时会返回指定的默认值
		//username := c.DefaultPostForm("username", "小王子")
		username := c.PostForm("username")
		address := c.PostForm("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})
	r.Run(":8080")
}

可以接收 multipart/form-data; 和application/x-www-form-urlencoded

func _form(c *gin.Context) {
	fmt.Println(c.PostForm("name"))
	fmt.Println(c.PostFormArray("name"))
	fmt.Println(c.DefaultPostForm("addr", "四川省")) // 如果用户没传,就使用默认值
	forms, err := c.MultipartForm()               // 接收所有的form参数,包括文件
	fmt.Println(forms, err)
}

示例:

package main

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

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("./index.html", "./home.html")
	r.GET("/index", func(c *gin.Context) {
		c.HTML(200, "index.html", nil)
	})
	r.POST("/index", func(c *gin.Context) {
		username := c.PostForm("username")
		password := c.PostForm("password")
		c.HTML(200, "home.html", gin.H{
			"Name":     username,
			"Password": password,
		})
	})
	// 启动HTTP服务,默认在0.0.0.0:8080启动服务
	r.Run()
}

url获取path参数

请求的参数通过URL路径传递,例如:/user/search/小王子/沙河。 获取请求URL路径中的参数的方式如下。

func main() {
	//Default返回一个默认的路由引擎
	r := gin.Default()
	r.GET("/user/search/:username/:address", func(c *gin.Context) {
		username := c.Param("username")
		address := c.Param("address")
		//输出json结果给调用方
		c.JSON(http.StatusOK, gin.H{
			"message":  "ok",
			"username": username,
			"address":  address,
		})
	})

	r.Run(":8080")
}

参数绑定

.ShouldBind():它能够基于请求自动提取JSON、form表单和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": "q1mi", "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=q1mi&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=q1mi&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")
}

ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

  • 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  • 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)。
package main

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

type UserInfo struct {
  Name string `form:"name"`
  Age  int    `form:"age"`
  Sex  string `form:"sex"`
}

func main() {
  router := gin.Default()
  
  router.POST("/form", func(c *gin.Context) {
    var userInfo UserInfo
    err := c.ShouldBind(&userInfo)
    if err != nil {
      fmt.Println(err)
      c.JSON(200, gin.H{"msg": "你错了"})
      return
    }
    c.JSON(200, userInfo)
  })

  router.Run(":8080")
}

文件上传

文件上传前端页面代码:

<!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>

后端gin框架部分代码:

func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// 单个文件
		file, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": err.Error(),
			})
			return
		}

		log.Println(file.Filename)
		dst := fmt.Sprintf("C:/tmp/%s", file.Filename)
		// 上传文件到指定的目录
		c.SaveUploadedFile(file, dst)
		c.JSON(http.StatusOK, gin.H{
			"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
		})
	})
	router.Run()
}

多个文件上传:

func main() {
	router := gin.Default()
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.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)),
		})
	})
	router.Run()
}

路由组

感觉像是一种封装,也许就是方便管理吧

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

func main() {
	r := gin.Default()
	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) {...})
	}
	r.Run()
}

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

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) {...})
	}

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

将一系列的路由放到一个组下,统一管理

例如,以下的路由前面统一加上api的前缀

package main

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

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

	r := router.Group("/api")
	r.GET("/index", func(c *gin.Context) {
		c.String(200, "index")
	})
	r.GET("/home", func(c *gin.Context) {
		c.String(200, "home")
	})

	router.Run(":8080")
}

Gin中间件

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

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

单独注册中间件

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
)
func indexHandler(c *gin.Context) {
	fmt.Println("index.....")
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
}

//定义一个中间件
func m1(c *gin.Context) {
  	fmt.Println("m1 in.........")
}
func main() {
	r := gin.Default()
	//m1处于indexHandler函数的前面,请求来之后,先走m1,再走index
	r.GET("/index", m1, indexHandler)

	_ = r.Run()
}

多个中间件

router.GET,后面可以跟很多HandlerFunc方法,这些方法其实都可以叫中间件

package main

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

func m1(c *gin.Context) {
  	fmt.Println("m1 ...in")
}
func m2(c *gin.Context) {
  	fmt.Println("m2 ...in")
}

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

	router.GET("/", m1, func(c *gin.Context) {
		fmt.Println("index ...")
		c.JSON(200, gin.H{"msg": "响应数据"})
	}, m2)

	router.Run(":8080")
}

/*
m1  ...in
index ...
m2  ...in
*/

中间件拦截响应

c.Abort()拦截,后续的HandlerFunc就不会执行了

package main

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

func m1(c *gin.Context) {
	fmt.Println("m1 ...in")
	c.JSON(200, gin.H{"msg": "第一个中间件拦截了"})
	c.Abort()
}
func m2(c *gin.Context) {
  	fmt.Println("m2 ...in")
}

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

	router.GET("/", m1, func(c *gin.Context) {
		fmt.Println("index ...")
		c.JSON(200, gin.H{"msg": "响应数据"})
	}, m2)

	router.Run(":8080")
}


### 中间件放行

c.Next(),Next前后形成了其他语言中的请求中间件和响应中间件

```go
package main

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

func m1(c *gin.Context) {
	fmt.Println("m1 ...in")
	c.Next()
	fmt.Println("m1 ...out")
}
func m2(c *gin.Context) {
	fmt.Println("m2 ...in")
	c.Next()
	fmt.Println("m2 ...out")
}

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

	router.GET("/", m1, func(c *gin.Context) {
		fmt.Println("index ...in")
		c.JSON(200, gin.H{"msg": "响应数据"})
		c.Next()
		fmt.Println("index ...out")
	}, m2)

	router.Run(":8080")
}

/*
m1 ...in
index ...in
m2 ...in   
m2 ...out  
index ...out
m1 ...out
*/
CopyErrorOK!

如果其中一个中间件响应了c.Abort(),后续中间件将不再执行,直接按照顺序走完所有的响应中间件

全局注册中间件

package main

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

func m10(c *gin.Context) {
	fmt.Println("m1 ...in")
	c.Next()
	fmt.Println("m1 ...out")
}

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

	router.Use(m10)
	router.GET("/", func(c *gin.Context) {
		fmt.Println("index ...in")
		c.JSON(200, gin.H{"msg": "index"})
		c.Next()
		fmt.Println("index ...out")
  })

  	router.Run(":8080")

}

使用Use去注册全局中间件,Use接收的参数也是多个HandlerFunc

中间件传递数据

使用Set设置一个key-value,

在后续中间件中使用Get接收数据

package main

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

func m10(c *gin.Context) {
	fmt.Println("m1 ...in")
	c.Set("name", "fengfeng")
}

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

	router.Use(m10)
	router.GET("/", func(c *gin.Context) {
		fmt.Println("index ...in")
		name, _ := c.Get("name")
		fmt.Println(name)
		
		c.JSON(200, gin.H{"msg": "index"})
	})

	router.Run(":8080")

}

引用:

  1. 枫枫知道
  2. Gin框架介绍及使用