You are on page 1of 80

gin框架

1. gin框架
简介

gin 路由

gin 数据解析和绑定

gin 渲染

gin 中间件

会话控制

其他

本文档使用 书栈网 · BookStack.CN 构建 - 908 -


简介

1. 简介

1.1. 介绍
Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容
错方便等特点

对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的 net/http 足够
简单,性能也非常不错

借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范

1.2. 安装
要安装Gin软件包,您需要安装Go并首先设置Go工作区。

1.首先需要安装Go(需要1.10+版本),然后可以使用下面的Go命令安装Gin。

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

2.将其导入您的代码中:

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

3.(可选)导入net/http。例如,如果使用常量,则需要这样做http.StatusOK。

import "net/http"

1.3. hello word

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. func main() {
10. // 1.创建路由

本文档使用 书栈网 · BookStack.CN 构建 - 909 -


简介

11. r := gin.Default()
12. // 2.绑定路由规则,执行的函数
13. // gin.Context,封装了request和response
14. r.GET("/", func(c *gin.Context) {
15. c.String(http.StatusOK, "hello World!")
16. })
17. // 3.监听端口,默认在8080
18. // Run("里面不指定端口号默认为8080")
19. r.Run(":8000")
20. }

本文档使用 书栈网 · BookStack.CN 构建 - 910 -


gin路由

1. gin路由
基本路由

Restful风格的API

API参数

URL参数

表单参数

上传单个文件

上传多个文件

routes group

路由原理

路由拆分与注册

本文档使用 书栈网 · BookStack.CN 构建 - 911 -


基本路由

1. 基本路由
gin 框架中采用的路由库是基于httprouter做的

地址为:https://github.com/julienschmidt/httprouter

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. func main() {
10. r := gin.Default()
11. r.GET("/", func(c *gin.Context) {
12. c.String(http.StatusOK, "hello word")
13. })
14. r.POST("/xxxpost",getting)
15. r.PUT("/xxxput")
16. //监听端口默认为8080
17. r.Run(":8000")
18. }

本文档使用 书栈网 · BookStack.CN 构建 - 912 -


Restful风格的API

1. Restful风格的API
gin支持Restful风格的API

即Representational State Transfer的缩写。直接翻译的意思是"表现层状态转化",是一


种互联网应用程序的API设计理念:URL定位资源,用HTTP描述操作

1.获取文章 /blog/getXxx Get blog/Xxx

2.添加 /blog/addXxx POST blog/Xxx

3.修改 /blog/updateXxx PUT blog/Xxx

4.删除 /blog/delXxxx DELETE blog/Xxx

本文档使用 书栈网 · BookStack.CN 构建 - 913 -


API参数

1. API参数
可以通过Context的Param方法来获取API参数

localhost:8000/xxx/zhangsan

1. package main
2.
3. import (
4. "net/http"
5. "strings"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. func main() {
11. r := gin.Default()
12. r.GET("/user/:name/*action", func(c *gin.Context) {
13. name := c.Param("name")
14. action := c.Param("action")
15. //截取/
16. action = strings.Trim(action, "/")
17. c.String(http.StatusOK, name+" is "+action)
18. })
19. //默认为监听8080端口
20. r.Run(":8000")
21. }

输出结果:

本文档使用 书栈网 · BookStack.CN 构建 - 914 -


URL参数

1. URL参数
URL参数可以通过DefaultQuery()或Query()方法获取
DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串
API ? name=zs

1. package main
2.
3. import (
4. "fmt"
5. "net/http"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. func main() {
11. r := gin.Default()
12. r.GET("/user", func(c *gin.Context) {
13. //指定默认值
14. //http://localhost:8080/user 才会打印出来默认的值
15. name := c.DefaultQuery("name", "枯藤")
16. c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
17. })
18. r.Run()
19. }

不传递参数输出的结果:

传递参数输出的结果:

本文档使用 书栈网 · BookStack.CN 构建 - 915 -


URL参数

本文档使用 书栈网 · BookStack.CN 构建 - 916 -


表单参数

1. 表单参数
表单传输为post请求,http常见的传输格式为四种:
application/json
application/x-www-form-urlencoded
application/xml
multipart/form-data
表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或
from-data格式的参数

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
7. <title>Document</title>
8. </head>
9. <body>
<form action="http://localhost:8080/form" method="post"
10. action="application/x-www-form-urlencoded">
用户名:<input type="text" name="username" placeholder="请输入你的用户名">
11. <br>
密&nbsp;&nbsp;&nbsp;码:<input type="password" name="userpassword"
12. placeholder="请输入你的密码"> <br>
13. <input type="submit" value="提交">
14. </form>
15. </body>
16. </html>

1. package main
2.
3. //
4. import (
5. "fmt"
6. "net/http"
7.
8. "github.com/gin-gonic/gin"
9. )
10.

本文档使用 书栈网 · BookStack.CN 构建 - 917 -


表单参数

11. func main() {


12. r := gin.Default()
13. r.POST("/form", func(c *gin.Context) {
14. types := c.DefaultPostForm("type", "post")
15. username := c.PostForm("username")
16. password := c.PostForm("userpassword")
// c.String(http.StatusOK,
17. fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))
c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s",
18. username, password, types))
19. })
20. r.Run()
21. }

输出结果:

本文档使用 书栈网 · BookStack.CN 构建 - 918 -


上传单个文件

1. 上传单个文件
multipart/form-data格式用于文件上传

gin文件上传与原生的net/http方法类似,不同在于gin把原生的request封装到c.Request

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
7. <title>Document</title>
8. </head>
9. <body>
<form action="http://localhost:8080/upload" method="post"
10. enctype="multipart/form-data">
11. 上传文件:<input type="file" name="file" >
12. <input type="submit" value="提交">
13. </form>
14. </body>
15. </html>

1. package main
2.
3. import (
4. "github.com/gin-gonic/gin"
5. )
6.
7. func main() {
8. r := gin.Default()
9. //限制上传最大尺寸
10. r.MaxMultipartMemory = 8 << 20
11. r.POST("/upload", func(c *gin.Context) {
12. file, err := c.FormFile("file")
13. if err != nil {
14. c.String(500, "上传图片出错")
15. }
16. // c.JSON(200, gin.H{"message": file.Header.Context})
17. c.SaveUploadedFile(file, file.Filename)

本文档使用 书栈网 · BookStack.CN 构建 - 919 -


上传单个文件

18. c.String(http.StatusOK, file.Filename)


19. })
20. r.Run()
21. }

效果演示:

1.1.1. 上传特定文件
有的用户上传文件需要限制上传文件的类型以及上传文件的大小,但是gin框架暂时没有这些函数(也有
可能是我没找到),因此基于原生的函数写法自己写了一个可以限制大小以及文件类型的上传函数

1. package main
2.
3. import (
4. "fmt"
5. "log"
6. "net/http"
7.
8. "github.com/gin-gonic/gin"
9. )
10.
11. func main() {

本文档使用 书栈网 · BookStack.CN 构建 - 920 -


上传单个文件

12. r := gin.Default()
13. r.POST("/upload", func(c *gin.Context) {
14. _, headers, err := c.Request.FormFile("file")
15. if err != nil {
16. log.Printf("Error when try to get file: %v", err)
17. }
18. //headers.Size 获取文件大小
19. if headers.Size > 1024*1024*2 {
20. fmt.Println("文件太大了")
21. return
22. }
23. //headers.Header.Get("Content-Type")获取上传文件的类型
24. if headers.Header.Get("Content-Type") != "image/png" {
25. fmt.Println("只允许上传png图片")
26. return
27. }
28. c.SaveUploadedFile(headers, "./video/"+headers.Filename)
29. c.String(http.StatusOK, headers.Filename)
30. })
31. r.Run()
32. }

本文档使用 书栈网 · BookStack.CN 构建 - 921 -


上传多个文件

1. 上传多个文件

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
7. <title>Document</title>
8. </head>
9. <body>
<form action="http://localhost:8000/upload" method="post"
10. enctype="multipart/form-data">
11. 上传文件:<input type="file" name="files" multiple>
12. <input type="submit" value="提交">
13. </form>
14. </body>
15. </html>

1. package main
2.
3. import (
4. "github.com/gin-gonic/gin"
5. "net/http"
6. "fmt"
7. )
8.
9. // gin的helloWorld
10.
11. func main() {
12. // 1.创建路由
13. // 默认使用了2个中间件Logger(), Recovery()
14. r := gin.Default()
15. // 限制表单上传大小 8MB,默认为32MB
16. r.MaxMultipartMemory = 8 << 20
17. r.POST("/upload", func(c *gin.Context) {
18. form, err := c.MultipartForm()
19. if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get err %s",
20. err.Error()))

本文档使用 书栈网 · BookStack.CN 构建 - 922 -


上传多个文件

21. }
22. // 获取所有图片
23. files := form.File["files"]
24. // 遍历所有图片
25. for _, file := range files {
26. // 逐个存
27. if err := c.SaveUploadedFile(file, file.Filename); err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s",
28. err.Error()))
29. return
30. }
31. }
32. c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
33. })
34. //默认端口号是8080
35. r.Run(":8000")
36. }

效果演示:

本文档使用 书栈网 · BookStack.CN 构建 - 923 -


routes group

1. routes group
routes group是为了管理一些相同的URL

1. package main
2.
3. import (
4. "github.com/gin-gonic/gin"
5. "fmt"
6. )
7.
8. // gin的helloWorld
9.
10. func main() {
11. // 1.创建路由
12. // 默认使用了2个中间件Logger(), Recovery()
13. r := gin.Default()
14. // 路由组1 ,处理GET请求
15. v1 := r.Group("/v1")
16. // {} 是书写规范
17. {
18. v1.GET("/login", login)
19. v1.GET("submit", submit)
20. }
21. v2 := r.Group("/v2")
22. {
23. v2.POST("/login", login)
24. v2.POST("/submit", submit)
25. }
26. r.Run(":8000")
27. }
28.
29. func login(c *gin.Context) {
30. name := c.DefaultQuery("name", "jack")
31. c.String(200, fmt.Sprintf("hello %s\n", name))
32. }
33.
34. func submit(c *gin.Context) {
35. name := c.DefaultQuery("name", "lily")
36. c.String(200, fmt.Sprintf("hello %s\n", name))
37. }

本文档使用 书栈网 · BookStack.CN 构建 - 924 -


routes group

效果演示:

本文档使用 书栈网 · BookStack.CN 构建 - 925 -


路由原理

1. 路由原理
httproter会将所有路由规则构造一颗前缀树

例如有 root and as at cn com

本文档使用 书栈网 · BookStack.CN 构建 - 926 -


路由拆分与注册

1. 路由拆分与注册

1.1.1. 基本的路由注册
下面最基础的gin路由注册方式,适用于路由条目比较少的简单项目或者项目demo。

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. func helloHandler(c *gin.Context) {
10. c.JSON(http.StatusOK, gin.H{
11. "message": "Hello www.topgoer.com!",
12. })
13. }
14.
15. func main() {
16. r := gin.Default()
17. r.GET("/topgoer", helloHandler)
18. if err := r.Run(); err != nil {
19. fmt.Println("startup service failed, err:%v\n", err)
20. }
21. }

1.1.2. 路由拆分成单独文件或包
当项目的规模增大后就不太适合继续在项目的main.go文件中去实现路由注册相关逻辑了,我们会倾向
于把路由部分的代码都拆分出来,形成一个单独的文件或包:

我们在routers.go文件中定义并注册路由信息:

1. package main
2.
3. import (
4. "net/http"
5.

本文档使用 书栈网 · BookStack.CN 构建 - 927 -


路由拆分与注册

6. "github.com/gin-gonic/gin"
7. )
8.
9. func helloHandler(c *gin.Context) {
10. c.JSON(http.StatusOK, gin.H{
11. "message": "Hello www.topgoer.com!",
12. })
13. }
14.
15. func setupRouter() *gin.Engine {
16. r := gin.Default()
17. r.GET("/topgoer", helloHandler)
18. return r
19. }

此时main.go中调用上面定义好的setupRouter函数:

1. func main() {
2. r := setupRouter()
3. if err := r.Run(); err != nil {
4. fmt.Println("startup service failed, err:%v\n", err)
5. }
6. }

此时的目录结构:

1. gin_demo
2. ├── go.mod
3. ├── go.sum
4. ├── main.go
5. └── routers.go

把路由部分的代码单独拆分成包的话也是可以的,拆分后的目录结构如下:

1. gin_demo
2. ├── go.mod
3. ├── go.sum
4. ├── main.go
5. └── routers
6. └── routers.go

本文档使用 书栈网 · BookStack.CN 构建 - 928 -


路由拆分与注册

routers/routers.go需要注意此时setupRouter需要改成首字母大写:

1. package routers
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. func helloHandler(c *gin.Context) {
10. c.JSON(http.StatusOK, gin.H{
11. "message": "Hello www.topgoer.com",
12. })
13. }
14.
15. // SetupRouter 配置路由信息
16. func SetupRouter() *gin.Engine {
17. r := gin.Default()
18. r.GET("/topgoer", helloHandler)
19. return r
20. }

main.go文件内容如下:

1. package main
2.
3. import (
4. "fmt"
5. "gin_demo/routers"
6. )
7.
8. func main() {
9. r := routers.SetupRouter()
10. if err := r.Run(); err != nil {
11. fmt.Println("startup service failed, err:%v\n", err)
12. }
13. }

1.1.3. 路由拆分成多个文件
当我们的业务规模继续膨胀,单独的一个routers文件或包已经满足不了我们的需求了,

本文档使用 书栈网 · BookStack.CN 构建 - 929 -


路由拆分与注册

1. func SetupRouter() *gin.Engine {


2. r := gin.Default()
3. r.GET("/topgoer", helloHandler)
4. r.GET("/xx1", xxHandler1)
5. ...
6. r.GET("/xx30", xxHandler30)
7. return r
8. }

因为我们把所有的路由注册都写在一个SetupRouter函数中的话就会太复杂了。

我们可以分开定义多个路由文件,例如:

1. gin_demo
2. ├── go.mod
3. ├── go.sum
4. ├── main.go
5. └── routers
6. ├── blog.go
7. └── shop.go

routers/shop.go中添加一个LoadShop的函数,将shop相关的路由注册到指定的路由器:

1. func LoadShop(e *gin.Engine) {


2. e.GET("/hello", helloHandler)
3. e.GET("/goods", goodsHandler)
4. e.GET("/checkout", checkoutHandler)
5. ...
6. }

routers/blog.go中添加一个LoadBlog的函数,将blog相关的路由注册到指定的路由器:

1. func LoadBlog(e *gin.Engine) {


2. e.GET("/post", postHandler)
3. e.GET("/comment", commentHandler)
4. ...
5. }

在main函数中实现最终的注册逻辑如下:

1. func main() {

本文档使用 书栈网 · BookStack.CN 构建 - 930 -


路由拆分与注册

2. r := gin.Default()
3. routers.LoadBlog(r)
4. routers.LoadShop(r)
5. if err := r.Run(); err != nil {
6. fmt.Println("startup service failed, err:%v\n", err)
7. }
8. }

1.1.4. 路由拆分到不同的APP
有时候项目规模实在太大,那么我们就更倾向于把业务拆分的更详细一些,例如把不同的业务代码拆分
成不同的APP。

因此我们在项目目录下单独定义一个app目录,用来存放我们不同业务线的代码文件,这样就很容易进
行横向扩展。大致目录结构如下:

1. gin_demo
2. ├── app
3. │ ├── blog
4. │ │ ├── handler.go
5. │ │ └── router.go
6. │ └── shop
7. │ ├── handler.go
8. │ └── router.go
9. ├── go.mod
10. ├── go.sum
11. ├── main.go
12. └── routers
13. └── routers.go

其中app/blog/router.go用来定义post相关路由信息,具体内容如下:

1. func Routers(e *gin.Engine) {


2. e.GET("/post", postHandler)
3. e.GET("/comment", commentHandler)
4. }

app/shop/router.go用来定义shop相关路由信息,具体内容如下:

1. func Routers(e *gin.Engine) {


2. e.GET("/goods", goodsHandler)
3. e.GET("/checkout", checkoutHandler)

本文档使用 书栈网 · BookStack.CN 构建 - 931 -


路由拆分与注册

4. }

routers/routers.go中根据需要定义Include函数用来注册子app中定义的路由,Init函数用来进
行路由的初始化操作:

1. type Option func(*gin.Engine)


2.
3. var options = []Option{}
4.
5. // 注册app的路由配置
6. func Include(opts ...Option) {
7. options = append(options, opts...)
8. }
9.
10. // 初始化
11. func Init() *gin.Engine {
12. r := gin.New()
13. for _, opt := range options {
14. opt(r)
15. }
16. return r
17. }

main.go中按如下方式先注册子app中的路由,然后再进行路由的初始化:

1. func main() {
2. // 加载多个APP的路由配置
3. routers.Include(shop.Routers, blog.Routers)
4. // 初始化路由
5. r := routers.Init()
6. if err := r.Run(); err != nil {
7. fmt.Println("startup service failed, err:%v\n", err)
8. }
9. }

转自:https://www.liwenzhou.com/posts/Go/gin_routes_registry/

本文档使用 书栈网 · BookStack.CN 构建 - 932 -


gin 数据解析和绑定

1. gin 数据解析和绑定
Json 数据解析和绑定

表单数据解析和绑定

URI数据解析和绑定

本文档使用 书栈网 · BookStack.CN 构建 - 933 -


Json 数据解析和绑定

1. Json 数据解析和绑定
客户端传参,后端接收并解析到结构体

1. package main
2.
3. import (
4. "github.com/gin-gonic/gin"
5. "net/http"
6. )
7.
8. // 定义接收数据的结构体
9. type Login struct {
10. // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
User string `form:"username" json:"user" uri:"user" xml:"user"
11. binding:"required"`
Pssword string `form:"password" json:"password" uri:"password"
12. xml:"password" binding:"required"`
13. }
14.
15. func main() {
16. // 1.创建路由
17. // 默认使用了2个中间件Logger(), Recovery()
18. r := gin.Default()
19. // JSON绑定
20. r.POST("loginJSON", func(c *gin.Context) {
21. // 声明接收的变量
22. var json Login
23. // 将request的body中的数据,自动按照json格式解析到结构体
24. if err := c.ShouldBindJSON(&json); err != nil {
25. // 返回错误信息
26. // gin.H封装了生成json数据的工具
27. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
28. return
29. }
30. // 判断用户名密码是否正确
31. if json.User != "root" || json.Pssword != "admin" {
32. c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
33. return
34. }
35. c.JSON(http.StatusOK, gin.H{"status": "200"})

本文档使用 书栈网 · BookStack.CN 构建 - 934 -


Json 数据解析和绑定

36. })
37. r.Run(":8000")
38. }

效果演示:

本文档使用 书栈网 · BookStack.CN 构建 - 935 -


表单数据解析和绑定

1. 表单数据解析和绑定

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
7. <title>Document</title>
8. </head>
9. <body>
<form action="http://localhost:8000/loginForm" method="post"
10. enctype="application/x-www-form-urlencoded">
11. 用户名<input type="text" name="username"><br>
12. 密码<input type="password" name="password">
13. <input type="submit" value="提交">
14. </form>
15. </body>
16. </html>

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. // 定义接收数据的结构体
10. type Login struct {
11. // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
User string `form:"username" json:"user" uri:"user" xml:"user"
12. binding:"required"`
Pssword string `form:"password" json:"password" uri:"password"
13. xml:"password" binding:"required"`
14. }
15.
16. func main() {
17. // 1.创建路由
18. // 默认使用了2个中间件Logger(), Recovery()

本文档使用 书栈网 · BookStack.CN 构建 - 936 -


表单数据解析和绑定

19. r := gin.Default()
20. // JSON绑定
21. r.POST("/loginForm", func(c *gin.Context) {
22. // 声明接收的变量
23. var form Login
24. // Bind()默认解析并绑定form格式
25. // 根据请求头中content-type自动推断
26. if err := c.Bind(&form); err != nil {
27. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
28. return
29. }
30. // 判断用户名密码是否正确
31. if form.User != "root" || form.Pssword != "admin" {
32. c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
33. return
34. }
35. c.JSON(http.StatusOK, gin.H{"status": "200"})
36. })
37. r.Run(":8000")
38. }

效果展示:

本文档使用 书栈网 · BookStack.CN 构建 - 937 -


URI数据解析和绑定

1. URI数据解析和绑定

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. // 定义接收数据的结构体
10. type Login struct {
11. // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
User string `form:"username" json:"user" uri:"user" xml:"user"
12. binding:"required"`
Pssword string `form:"password" json:"password" uri:"password"
13. xml:"password" binding:"required"`
14. }
15.
16. func main() {
17. // 1.创建路由
18. // 默认使用了2个中间件Logger(), Recovery()
19. r := gin.Default()
20. // JSON绑定
21. r.GET("/:user/:password", func(c *gin.Context) {
22. // 声明接收的变量
23. var login Login
24. // Bind()默认解析并绑定form格式
25. // 根据请求头中content-type自动推断
26. if err := c.ShouldBindUri(&login); err != nil {
27. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
28. return
29. }
30. // 判断用户名密码是否正确
31. if login.User != "root" || login.Pssword != "admin" {
32. c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
33. return
34. }
35. c.JSON(http.StatusOK, gin.H{"status": "200"})
36. })
37. r.Run(":8000")

本文档使用 书栈网 · BookStack.CN 构建 - 938 -


URI数据解析和绑定

38. }

效果演示:

本文档使用 书栈网 · BookStack.CN 构建 - 939 -


gin 渲染

1. gin 渲染
各种数据格式的响应

HTML模板渲染

重定向

同步异步

本文档使用 书栈网 · BookStack.CN 构建 - 940 -


各种数据格式的响应

1. 各种数据格式的响应
json、结构体、XML、YAML类似于java的properties、ProtoBuf

1. package main
2.
3. import (
4. "github.com/gin-gonic/gin"
5. "github.com/gin-gonic/gin/testdata/protoexample"
6. )
7.
8. // 多种响应方式
9. func main() {
10. // 1.创建路由
11. // 默认使用了2个中间件Logger(), Recovery()
12. r := gin.Default()
13. // 1.json
14. r.GET("/someJSON", func(c *gin.Context) {
15. c.JSON(200, gin.H{"message": "someJSON", "status": 200})
16. })
17. // 2. 结构体响应
18. r.GET("/someStruct", func(c *gin.Context) {
19. var msg struct {
20. Name string
21. Message string
22. Number int
23. }
24. msg.Name = "root"
25. msg.Message = "message"
26. msg.Number = 123
27. c.JSON(200, msg)
28. })
29. // 3.XML
30. r.GET("/someXML", func(c *gin.Context) {
31. c.XML(200, gin.H{"message": "abc"})
32. })
33. // 4.YAML响应
34. r.GET("/someYAML", func(c *gin.Context) {
35. c.YAML(200, gin.H{"name": "zhangsan"})
36. })
37. // 5.protobuf格式,谷歌开发的高效存储读取的工具

本文档使用 书栈网 · BookStack.CN 构建 - 941 -


各种数据格式的响应

38. // 数组?切片?如果自己构建一个传输格式,应该是什么格式?
39. r.GET("/someProtoBuf", func(c *gin.Context) {
40. reps := []int64{int64(1), int64(2)}
41. // 定义数据
42. label := "label"
43. // 传protobuf格式数据
44. data := &protoexample.Test{
45. Label: &label,
46. Reps: reps,
47. }
48. c.ProtoBuf(200, data)
49. })
50.
51. r.Run(":8000")
52. }

本文档使用 书栈网 · BookStack.CN 构建 - 942 -


HTML模板渲染

1. HTML模板渲染
gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据,本质上就是字符串替换
LoadHTMLGlob()方法可以加载模板文件

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. func main() {
10. r := gin.Default()
11. r.LoadHTMLGlob("tem/*")
12. r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是测试", "ce":
13. "123456"})
14. })
15. r.Run()
16. }

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
7. <title>{{.title}}</title>
8. </head>
9. <body>
10. fgkjdskjdsh{{.ce}}
11. </body>
12. </html>

目录结构:

本文档使用 书栈网 · BookStack.CN 构建 - 943 -


HTML模板渲染

如果你的目录结构是下面的情况

代码如下:

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. func main() {
10. r := gin.Default()
11. r.LoadHTMLGlob("tem/**/*")
12. r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "user/index.html", gin.H{"title": "我是测试",
13. "address": "www.5lmh.com"})
14. })
15. r.Run()
16. }

1. {{ define "user/index.html" }}
2. <!DOCTYPE html>
3. <html lang="en">
4. <head>
5. <meta charset="UTF-8">
6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
8. <title>{{.title}}</title>

本文档使用 书栈网 · BookStack.CN 构建 - 944 -


HTML模板渲染

9. </head>
10. <body>
11. fgkjdskjdsh{{.address}}
12. </body>
13. </html>
14. {{ end }}

如果你想进行头尾分离就是下面这种写法了:

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. func main() {
10. r := gin.Default()
11. r.LoadHTMLGlob("tem/**/*")
12. r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "user/index.html", gin.H{"title": "我是测试",
13. "address": "www.5lmh.com"})
14. })
15. r.Run()
16. }

user/index.html 文件代码:

1. {{ define "user/index.html" }}
2. {{template "public/header" .}}
3. fgkjdskjdsh{{.address}}
4. {{template "public/footer" .}}
5. {{ end }}

本文档使用 书栈网 · BookStack.CN 构建 - 945 -


HTML模板渲染

public/header.html 文件代码:

1. {{define "public/header"}}
2. <!DOCTYPE html>
3. <html lang="en">
4. <head>
5. <meta charset="UTF-8">
6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
8. <title>{{.title}}</title>
9. </head>
10. <body>
11.
12. {{end}}

public/footer.html 文件代码:

1. {{define "public/footer"}}
2. </body>
3. </html>
4. {{ end }}

如果你需要引入静态文件需要定义一个静态文件目录

1. r.Static("/assets", "./assets")

本文档使用 书栈网 · BookStack.CN 构建 - 946 -


重定向

1. 重定向

1. package main
2.
3. import (
4. "net/http"
5.
6. "github.com/gin-gonic/gin"
7. )
8.
9. func main() {
10. r := gin.Default()
11. r.GET("/index", func(c *gin.Context) {
12. c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")
13. })
14. r.Run()
15. }

本文档使用 书栈网 · BookStack.CN 构建 - 947 -


同步异步

1. 同步异步
goroutine机制可以方便地实现异步处理
另外,在启动新的goroutine时,不应该使用原始上下文,必须使用它的只读副本

1. package main
2.
3. import (
4. "log"
5. "time"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. func main() {
11. // 1.创建路由
12. // 默认使用了2个中间件Logger(), Recovery()
13. r := gin.Default()
14. // 1.异步
15. r.GET("/long_async", func(c *gin.Context) {
16. // 需要搞一个副本
17. copyContext := c.Copy()
18. // 异步处理
19. go func() {
20. time.Sleep(3 * time.Second)
21. log.Println("异步执行:" + copyContext.Request.URL.Path)
22. }()
23. })
24. // 2.同步
25. r.GET("/long_sync", func(c *gin.Context) {
26. time.Sleep(3 * time.Second)
27. log.Println("同步执行:" + c.Request.URL.Path)
28. })
29.
30. r.Run(":8000")
31. }

本文档使用 书栈网 · BookStack.CN 构建 - 948 -


gin 中间件

1. gin 中间件
全局中间件

Next()方法

局部中间件

中间件练习

本文档使用 书栈网 · BookStack.CN 构建 - 949 -


全局中间件

1. 全局中间件
所有请求都经过此中间件

1. package main
2.
3. import (
4. "fmt"
5. "time"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. // 定义中间
11. func MiddleWare() gin.HandlerFunc {
12. return func(c *gin.Context) {
13. t := time.Now()
14. fmt.Println("中间件开始执行了")
15. // 设置变量到Context的key中,可以通过Get()取
16. c.Set("request", "中间件")
17. status := c.Writer.Status()
18. fmt.Println("中间件执行完毕", status)
19. t2 := time.Since(t)
20. fmt.Println("time:", t2)
21. }
22. }
23.
24. func main() {
25. // 1.创建路由
26. // 默认使用了2个中间件Logger(), Recovery()
27. r := gin.Default()
28. // 注册中间件
29. r.Use(MiddleWare())
30. // {}为了代码规范
31. {
32. r.GET("/ce", func(c *gin.Context) {
33. // 取值
34. req, _ := c.Get("request")
35. fmt.Println("request:", req)
36. // 页面接收
37. c.JSON(200, gin.H{"request": req})

本文档使用 书栈网 · BookStack.CN 构建 - 950 -


全局中间件

38. })
39.
40. }
41. r.Run()
42. }

输出结果:

注意: 请注意黑色的数据里面有一步算时间差没有执行(需要学习Next就懂了)

本文档使用 书栈网 · BookStack.CN 构建 - 951 -


Next(/)方法

1. Next()方法

1. package main
2.
3. import (
4. "fmt"
5. "time"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. // 定义中间
11. func MiddleWare() gin.HandlerFunc {
12. return func(c *gin.Context) {
13. t := time.Now()
14. fmt.Println("中间件开始执行了")
15. // 设置变量到Context的key中,可以通过Get()取
16. c.Set("request", "中间件")
17. // 执行函数
18. c.Next()
19. // 中间件执行完后续的一些事情
20. status := c.Writer.Status()
21. fmt.Println("中间件执行完毕", status)
22. t2 := time.Since(t)
23. fmt.Println("time:", t2)
24. }
25. }
26.
27. func main() {
28. // 1.创建路由
29. // 默认使用了2个中间件Logger(), Recovery()
30. r := gin.Default()
31. // 注册中间件
32. r.Use(MiddleWare())
33. // {}为了代码规范
34. {
35. r.GET("/ce", func(c *gin.Context) {
36. // 取值
37. req, _ := c.Get("request")
38. fmt.Println("request:", req)

本文档使用 书栈网 · BookStack.CN 构建 - 952 -


Next(/)方法

39. // 页面接收
40. c.JSON(200, gin.H{"request": req})
41. })
42.
43. }
44. r.Run()
45. }

输出结果:

本文档使用 书栈网 · BookStack.CN 构建 - 953 -


局部中间件

1. 局部中间件

1. package main
2.
3. import (
4. "fmt"
5. "time"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. // 定义中间
11. func MiddleWare() gin.HandlerFunc {
12. return func(c *gin.Context) {
13. t := time.Now()
14. fmt.Println("中间件开始执行了")
15. // 设置变量到Context的key中,可以通过Get()取
16. c.Set("request", "中间件")
17. // 执行函数
18. c.Next()
19. // 中间件执行完后续的一些事情
20. status := c.Writer.Status()
21. fmt.Println("中间件执行完毕", status)
22. t2 := time.Since(t)
23. fmt.Println("time:", t2)
24. }
25. }
26.
27. func main() {
28. // 1.创建路由
29. // 默认使用了2个中间件Logger(), Recovery()
30. r := gin.Default()
31. //局部中间键使用
32. r.GET("/ce", MiddleWare(), func(c *gin.Context) {
33. // 取值
34. req, _ := c.Get("request")
35. fmt.Println("request:", req)
36. // 页面接收
37. c.JSON(200, gin.H{"request": req})
38. })

本文档使用 书栈网 · BookStack.CN 构建 - 954 -


局部中间件

39. r.Run()
40. }

效果演示:

本文档使用 书栈网 · BookStack.CN 构建 - 955 -


中间件练习

1. 中间件练习
定义程序计时中间件,然后定义2个路由,执行函数后应该打印统计的执行时间,如下:

1. package main
2.
3. import (
4. "fmt"
5. "time"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. // 定义中间
11. func myTime(c *gin.Context) {
12. start := time.Now()
13. c.Next()
14. // 统计时间
15. since := time.Since(start)
16. fmt.Println("程序用时:", since)
17. }
18.
19. func main() {
20. // 1.创建路由
21. // 默认使用了2个中间件Logger(), Recovery()
22. r := gin.Default()
23. // 注册中间件
24. r.Use(myTime)
25. // {}为了代码规范
26. shoppingGroup := r.Group("/shopping")
27. {
28. shoppingGroup.GET("/index", shopIndexHandler)
29. shoppingGroup.GET("/home", shopHomeHandler)
30. }
31. r.Run(":8000")
32. }
33.
34. func shopIndexHandler(c *gin.Context) {
35. time.Sleep(5 * time.Second)
36. }
37.

本文档使用 书栈网 · BookStack.CN 构建 - 956 -


中间件练习

38. func shopHomeHandler(c *gin.Context) {


39. time.Sleep(3 * time.Second)
40. }

效果演示:

本文档使用 书栈网 · BookStack.CN 构建 - 957 -


会话控制

1. 会话控制
Cookie介绍

Cookie的使用

Cookie练习

Cookie的缺点

Sessions

本文档使用 书栈网 · BookStack.CN 构建 - 958 -


Cookie介绍

1. Cookie介绍
HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否
由同一个客户端发出
Cookie就是解决HTTP协议无状态的方案之一,中文是小甜饼的意思
Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器
发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求
Cookie由服务器创建,并发送给浏览器,最终由浏览器保存

1.1.1. Cookie的用途
测试服务端发送cookie给客户端,客户端请求时携带cookie

本文档使用 书栈网 · BookStack.CN 构建 - 959 -


Cookie的使用

1. Cookie的使用
测试服务端发送cookie给客户端,客户端请求时携带cookie

1. package main
2.
3. import (
4. "github.com/gin-gonic/gin"
5. "fmt"
6. )
7.
8. func main() {
9. // 1.创建路由
10. // 默认使用了2个中间件Logger(), Recovery()
11. r := gin.Default()
12. // 服务端要给客户端cookie
13. r.GET("cookie", func(c *gin.Context) {
14. // 获取客户端是否携带cookie
15. cookie, err := c.Cookie("key_cookie")
16. if err != nil {
17. cookie = "NotSet"
18. // 给客户端设置cookie
19. // maxAge int, 单位为秒
20. // path,cookie所在目录
21. // domain string,域名
22. // secure 是否智能通过https访问
23. // httpOnly bool 是否允许别人通过js获取自己的cookie
24. c.SetCookie("key_cookie", "value_cookie", 60, "/",
25. "localhost", false, true)
26. }
27. fmt.Printf("cookie的值是: %s\n", cookie)
28. })
29. r.Run(":8000")
30. }

本文档使用 书栈网 · BookStack.CN 构建 - 960 -


Cookie练习

1. Cookie练习
模拟实现权限验证中间件

有2个路由,login和home
login用于设置cookie
home是访问查看信息的请求
在请求home之前,先跑中间件代码,检验是否存在cookie
访问home,会显示错误,因为权限校验未通过

然后访问登录的请求,登录并设置cookie

再次访问home,访问成功

1. package main
2.
3. import (
4. "github.com/gin-gonic/gin"
5. "net/http"
6. )
7.

本文档使用 书栈网 · BookStack.CN 构建 - 961 -


Cookie练习

8. func AuthMiddleWare() gin.HandlerFunc {


9. return func(c *gin.Context) {
10. // 获取客户端cookie并校验
11. if cookie, err := c.Cookie("abc"); err == nil {
12. if cookie == "123" {
13. c.Next()
14. return
15. }
16. }
17. // 返回错误
18. c.JSON(http.StatusUnauthorized, gin.H{"error": "err"})
19. // 若验证不通过,不再调用后续的函数处理
20. c.Abort()
21. return
22. }
23. }
24.
25. func main() {
26. // 1.创建路由
27. r := gin.Default()
28. r.GET("/login", func(c *gin.Context) {
29. // 设置cookie
30. c.SetCookie("abc", "123", 60, "/",
31. "localhost", false, true)
32. // 返回信息
33. c.String(200, "Login success!")
34. })
35. r.GET("/home", AuthMiddleWare(), func(c *gin.Context) {
36. c.JSON(200, gin.H{"data": "home"})
37. })
38. r.Run(":8000")
39. }

访问 /home 和 /login进行测试

本文档使用 书栈网 · BookStack.CN 构建 - 962 -


Cookie的缺点

1. Cookie的缺点
不安全,明文
增加带宽消耗
可以被禁用
cookie有上限

本文档使用 书栈网 · BookStack.CN 构建 - 963 -


Sessions

1. Sessions
gorilla/sessions为自定义session后端提供cookie和文件系统session以及基础结构。

主要功能是:

简单的API:将其用作设置签名(以及可选的加密)cookie的简便方法。
内置的后端可将session存储在cookie或文件系统中。
Flash消息:一直持续读取的session值。
切换session持久性(又称“记住我”)和设置其他属性的便捷方法。
旋转身份验证和加密密钥的机制。
每个请求有多个session,即使使用不同的后端也是如此。
自定义session后端的接口和基础结构:可以使用通用API检索并批量保存来自不同商店的
session。

代码:

1. package main
2.
3. import (
4. "fmt"
5. "net/http"
6.
7. "github.com/gorilla/sessions"
8. )
9.
10. // 初始化一个cookie存储对象
11. // something-very-secret应该是一个你自己的密匙,只要不被别人知道就行
12. var store = sessions.NewCookieStore([]byte("something-very-secret"))
13.
14. func main() {
15. http.HandleFunc("/save", SaveSession)
16. http.HandleFunc("/get", GetSession)
17. err := http.ListenAndServe(":8080", nil)
18. if err != nil {
19. fmt.Println("HTTP server failed,err:", err)
20. return
21. }
22. }
23.
24. func SaveSession(w http.ResponseWriter, r *http.Request) {

本文档使用 书栈网 · BookStack.CN 构建 - 964 -


Sessions

25. // Get a session. We're ignoring the error resulted from decoding an
26. // existing session: Get() always returns a session, even if empty.
27.
28. // 获取一个session对象,session-name是session的名字
29. session, err := store.Get(r, "session-name")
30. if err != nil {
31. http.Error(w, err.Error(), http.StatusInternalServerError)
32. return
33. }
34.
35. // 在session中存储值
36. session.Values["foo"] = "bar"
37. session.Values[42] = 43
38. // 保存更改
39. session.Save(r, w)
40. }
41. func GetSession(w http.ResponseWriter, r *http.Request) {
42. session, err := store.Get(r, "session-name")
43. if err != nil {
44. http.Error(w, err.Error(), http.StatusInternalServerError)
45. return
46. }
47. foo := session.Values["foo"]
48. fmt.Println(foo)
49. }

删除session的值:

1. // 删除
2. // 将session的最大存储时间设置为小于零的数即为删除
3. session.Options.MaxAge = -1
4. session.Save(r, w)

官网地址:http://www.gorillatoolkit.org/pkg/sessions

本文档使用 书栈网 · BookStack.CN 构建 - 965 -


参数验证

1. 参数验证
结构体验证

自定义验证

多语言翻译验证

本文档使用 书栈网 · BookStack.CN 构建 - 966 -


结构体验证

1. 结构体验证
用gin框架的数据验证,可以不用解析数据,减少if else,会简洁许多。

1. package main
2.
3. import (
4. "fmt"
5. "time"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. //Person ..
11. type Person struct {
12. //不能为空并且大于10
13. Age int `form:"age" binding:"required,gt=10"`
14. Name string `form:"name" binding:"required"`
15. Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
16. }
17.
18. func main() {
19. r := gin.Default()
20. r.GET("/5lmh", func(c *gin.Context) {
21. var person Person
22. if err := c.ShouldBind(&person); err != nil {
23. c.String(500, fmt.Sprint(err))
24. return
25. }
26. c.String(200, fmt.Sprintf("%#v", person))
27. })
28. r.Run()
29. }

演示地址:

http://localhost:8080/5lmh?age=11&name=枯藤&birthday=2006-01-02

本文档使用 书栈网 · BookStack.CN 构建 - 967 -


自定义验证

1. 自定义验证
都在代码里自己看吧

1. package main
2.
3. import (
4. "net/http"
5. "reflect"
6. "github.com/gin-gonic/gin"
7. "github.com/gin-gonic/gin/binding"
8. "gopkg.in/go-playground/validator.v8"
9. )
10.
11. /*
12. 对绑定解析到结构体上的参数,自定义验证功能
比如我们要对 name 字段做校验,要不能为空,并且不等于 admin ,类似这种需求,就无法
13. binding 现成的方法
需要我们自己验证方法才能实现 官网示例(https://godoc.org/gopkg.in/go-
14. playground/validator.v8#hdr-Custom_Functions)
15. 这里需要下载引入下 gopkg.in/go-playground/validator.v8
16. */
17. type Person struct {
18. Age int `form:"age" binding:"required,gt=10"`
19. // 2、在参数 binding 上使用自定义的校验方法函数注册时候的名称
20. Name string `form:"name" binding:"NotNullAndAdmin"`
21. Address string `form:"address" binding:"required"`
22. }
23.
24. // 1、自定义的校验方法
func nameNotNullAndAdmin(v *validator.Validate, topStruct reflect.Value,
currentStructOrField reflect.Value, field reflect.Value, fieldType
25. reflect.Type, fieldKind reflect.Kind, param string) bool {
26.
27. if value, ok := field.Interface().(string); ok {
28. // 字段不能为空,并且不等于 admin
29. return value != "" && !("5lmh" == value)
30. }
31.
32. return true
33. }

本文档使用 书栈网 · BookStack.CN 构建 - 968 -


自定义验证

34.
35. func main() {
36. r := gin.Default()
37.
38. // 3、将我们自定义的校验方法注册到 validator中
39. if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
40. // 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
41. v.RegisterValidation("NotNullAndAdmin", nameNotNullAndAdmin)
42. }
43.
44. /*
curl -X GET "http://127.0.0.1:8080/testing?
45. name=&age=12&address=beijing"
curl -X GET "http://127.0.0.1:8080/testing?
46. name=lmh&age=12&address=beijing"
curl -X GET "http://127.0.0.1:8080/testing?
47. name=adz&age=12&address=beijing"
48. */
49. r.GET("/5lmh", func(c *gin.Context) {
50. var person Person
51. if e := c.ShouldBind(&person); e == nil {
52. c.String(http.StatusOK, "%v", person)
53. } else {
54. c.String(http.StatusOK, "person bind err:%v", e.Error())
55. }
56. })
57. r.Run()
58. }

示例2:

1. package main
2.
3. import (
4. "net/http"
5. "reflect"
6. "time"
7.
8. "github.com/gin-gonic/gin"
9. "github.com/gin-gonic/gin/binding"
10. "gopkg.in/go-playground/validator.v8"
11. )

本文档使用 书栈网 · BookStack.CN 构建 - 969 -


自定义验证

12.
13. // Booking contains binded and validated data.
14. type Booking struct {
15. //定义一个预约的时间大于今天的时间
CheckIn time.Time `form:"check_in" binding:"required,bookabledate"
16. time_format:"2006-01-02"`
17. //gtfield=CheckIn退出的时间大于预约的时间
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn"
18. time_format:"2006-01-02"`
19. }
20.
21. func bookableDate(
v *validator.Validate, topStruct reflect.Value, currentStructOrField
22. reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param
23. string,
24. ) bool {
25. //field.Interface().(time.Time)获取参数值并且转换为时间格式
26. if date, ok := field.Interface().(time.Time); ok {
27. today := time.Now()
28. if today.Unix() > date.Unix() {
29. return false
30. }
31. }
32. return true
33. }
34.
35. func main() {
36. route := gin.Default()
37. //注册验证
38. if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
39. //绑定第一个参数是验证的函数第二个参数是自定义的验证函数
40. v.RegisterValidation("bookabledate", bookableDate)
41. }
42.
43. route.GET("/5lmh", getBookable)
44. route.Run()
45. }
46.
47. func getBookable(c *gin.Context) {
48. var b Booking
49. if err := c.ShouldBindWith(&b, binding.Query); err == nil {
50. c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})

本文档使用 书栈网 · BookStack.CN 构建 - 970 -


自定义验证

51. } else {
52. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
53. }
54. }
55.
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-11-07&check_out=2019-
56. 11-20"
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-09-07&check_out=2019-
57. 11-20"
// curl -X GET "http://localhost:8080/5lmh?check_in=2019-11-07&check_out=2019-
58. 11-01"

本文档使用 书栈网 · BookStack.CN 构建 - 971 -


多语言翻译验证

1. 多语言翻译验证
当业务系统对验证信息有特殊需求时,例如:返回信息需要自定义,手机端返回的信息需要是中文而pc
端发挥返回的信息需要时英文,如何做到请求一个接口满足上述三种情况。

1. package main
2.
3. import (
4. "fmt"
5.
6. "github.com/gin-gonic/gin"
7. "github.com/go-playground/locales/en"
8. "github.com/go-playground/locales/zh"
9. "github.com/go-playground/locales/zh_Hant_TW"
10. ut "github.com/go-playground/universal-translator"
11. "gopkg.in/go-playground/validator.v9"
12. en_translations "gopkg.in/go-playground/validator.v9/translations/en"
13. zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
14. zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
15. )
16.
17. var (
18. Uni *ut.UniversalTranslator
19. Validate *validator.Validate
20. )
21.
22. type User struct {
23. Username string `form:"user_name" validate:"required"`
24. Tagline string `form:"tag_line" validate:"required,lt=10"`
25. Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
26. }
27.
28. func main() {
29. en := en.New()
30. zh := zh.New()
31. zh_tw := zh_Hant_TW.New()
32. Uni = ut.New(en, zh, zh_tw)
33. Validate = validator.New()
34.
35. route := gin.Default()
36. route.GET("/5lmh", startPage)

本文档使用 书栈网 · BookStack.CN 构建 - 972 -


多语言翻译验证

37. route.POST("/5lmh", startPage)


38. route.Run(":8080")
39. }
40.
41. func startPage(c *gin.Context) {
42. //这部分应放到中间件中
43. locale := c.DefaultQuery("locale", "zh")
44. trans, _ := Uni.GetTranslator(locale)
45. switch locale {
46. case "zh":
47. zh_translations.RegisterDefaultTranslations(Validate, trans)
48. break
49. case "en":
50. en_translations.RegisterDefaultTranslations(Validate, trans)
51. break
52. case "zh_tw":
53. zh_tw_translations.RegisterDefaultTranslations(Validate, trans)
54. break
55. default:
56. zh_translations.RegisterDefaultTranslations(Validate, trans)
57. break
58. }
59.
60. //自定义错误内容
Validate.RegisterTranslation("required", trans, func(ut ut.Translator)
61. error {
return ut.Add("required", "{0} must have a value!", true) // see
62. universal-translator for details
63. }, func(ut ut.Translator, fe validator.FieldError) string {
64. t, _ := ut.T("required", fe.Field())
65. return t
66. })
67.
68. //这块应该放到公共验证方法中
69. user := User{}
70. c.ShouldBind(&user)
71. fmt.Println(user)
72. err := Validate.Struct(user)
73. if err != nil {
74. errs := err.(validator.ValidationErrors)
75. sliceErrs := []string{}
76. for _, e := range errs {
77. sliceErrs = append(sliceErrs, e.Translate(trans))

本文档使用 书栈网 · BookStack.CN 构建 - 973 -


多语言翻译验证

78. }
79. c.String(200, fmt.Sprintf("%#v", sliceErrs))
80. }
81. c.String(200, fmt.Sprintf("%#v", "user"))
82. }

正确的链接:http://localhost:8080/testing?user_name=枯藤
&tag_line=9&tag_line2=33&locale=zh

http://localhost:8080/testing?user_name=枯藤
&tag_line=9&tag_line2=3&locale=en 返回英文的验证信息

http://localhost:8080/testing?user_name=枯藤
&tag_line=9&tag_line2=3&locale=zh 返回中文的验证信息

查看更多的功能可以查看官网 gopkg.in/go-playground/validator.v9

本文档使用 书栈网 · BookStack.CN 构建 - 974 -


其他

1. 其他
日志文件

Air实时加载

gin验证码

生成解析token

权限管理

本文档使用 书栈网 · BookStack.CN 构建 - 975 -


日志文件

1. 日志文件

1. package main
2.
3. import (
4. "io"
5. "os"
6.
7. "github.com/gin-gonic/gin"
8. )
9.
10. func main() {
11. gin.DisableConsoleColor()
12.
13. // Logging to a file.
14. f, _ := os.Create("gin.log")
15. gin.DefaultWriter = io.MultiWriter(f)
16.
17. // 如果需要同时将日志写入文件和控制台,请使用以下代码。
18. // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
19. r := gin.Default()
20. r.GET("/ping", func(c *gin.Context) {
21. c.String(200, "pong")
22. })
23. r.Run()
24. }

效果演示:

本文档使用 书栈网 · BookStack.CN 构建 - 976 -


Air实时加载

1. Air实时加载
本章我们要介绍一个神器——Air能够实时监听项目的代码文件,在代码发生变更之后自动重新编译并执
行,大大提高gin框架项目的开发效率。

1.1.1. 为什么需要实时加载?
之前使用Python编写Web项目的时候,常见的Flask或Django框架都是支持实时加载的,你修改了项
目代码之后,程序能够自动重新加载并执行(live-reload),这在日常的开发阶段是十分方便的。

在使用Go语言的gin框架在本地做开发调试的时候,经常需要在变更代码之后频繁的按下Ctrl+C停止
程序并重新编译再执行,这样就不是很方便。

1.1.2. Air介绍
怎样才能在基于gin框架开发时实现实时加载功能呢?像这种烦恼肯定不会只是你一个人的烦恼,所以
我报着肯定有现成轮子的心态开始了全网大搜索。果不其然就在Github上找到了一个工具:Air[1]。
它支持以下特性:

彩色日志输出
自定义构建或二进制命令
支持忽略子目录
启动后支持监听新目录
更好的构建过程

1.1.3. 安装Air

Go

这也是最经典的安装方式:

1. go get -u github.com/cosmtrek/air

MacOS

1. curl -fLo air https://git.io/darwin_air

Linux

1. curl -fLo air https://git.io/linux_air

本文档使用 书栈网 · BookStack.CN 构建 - 977 -


Air实时加载

Windows

1. curl -fLo air.exe https://git.io/windows_air

Dcoker

1. docker run -it --rm \


2. -w "<PROJECT>" \
3. -e "air_wd=<PROJECT>" \
4. -v $(pwd):<PROJECT> \
5. -p <PORT>:<APP SERVER PORT> \
6. cosmtrek/air
7. -c <CONF>

然后按照下面的方式在docker中运行你的项目:

1. docker run -it --rm \


2. -w "/go/src/github.com/cosmtrek/hub" \
3. -v $(pwd):/go/src/github.com/cosmtrek/hub \
4. -p 9090:9090 \
5. cosmtrek/air

1.1.4. 使用Air
为了敲命令更简单更方便,你应该把 alias air='~/.air' 加到你的 .bashrc 或 .zshrc 中。

首先进入你的项目目录:

1. cd /path/to/your_project

最简单的用法就是直接执行下面的命令:

1. # 首先在当前目录下查找 `.air.conf`配置文件,如果找不到就使用默认的
2. air -c .air.conf

推荐的使用方法是:

1. # 1. 在当前目录创建一个新的配置文件.air.conf
2. touch .air.conf
3.
4. # 2. 复制 `air.conf.example` 中的内容到这个文件,然后根据你的需要去修改它

本文档使用 书栈网 · BookStack.CN 构建 - 978 -


Air实时加载

5.
6. # 3. 使用你的配置运行 air, 如果文件名是 `.air.conf`,只需要执行 `air`。
7. air

air_example.conf示例

完整的air_example.conf示例配置如下,可以根据自己的需要修改。

1. # [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件


2.
3. # 工作目录
4. # 使用 . 或绝对路径,请注意 `tmp_dir` 目录必须在 `root` 目录下
5. root = "."
6. tmp_dir = "tmp"
7.
8. [build]
9. # 只需要写你平常编译使用的shell命令。你也可以使用 `make`
10. cmd = "go build -o ./tmp/main ."
11. # 由`cmd`命令得到的二进制文件名
12. bin = "tmp/main"
13. # 自定义的二进制,可以添加额外的编译标识例如添加 GIN_MODE=release
14. full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
15. # 监听以下文件扩展名的文件.
16. include_ext = ["go", "tpl", "tmpl", "html"]
17. # 忽略这些文件扩展名或目录
18. exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
19. # 监听以下指定目录的文件
20. include_dir = []
21. # 排除以下文件
22. exclude_file = []
23. # 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间
24. delay = 1000 # ms
25. # 发生构建错误时,停止运行旧的二进制文件。
26. stop_on_error = true
27. # air的日志文件名,该日志文件放置在你的`tmp_dir`中
28. log = "air_errors.log"
29.
30. [log]
31. # 显示日志时间
32. time = true
33.
34. [color]

本文档使用 书栈网 · BookStack.CN 构建 - 979 -


Air实时加载

35. # 自定义每个部分显示的颜色。如果找不到颜色,使用原始的应用程序日志。
36. main = "magenta"
37. watcher = "cyan"
38. build = "yellow"
39. runner = "green"
40.
41. [misc]
42. # 退出时删除tmp目录
43. clean_on_exit = true

本文转自:https://mp.weixin.qq.com/s/QgL3jx8Au7YbRjlfsIFgJg

本文档使用 书栈网 · BookStack.CN 构建 - 980 -


gin验证码

1. gin验证码
在开发的过程中,我们有些接口为了防止被恶意调用,我们会采用加验证码的方式,例如:发送短信的
接口,为了防止短信接口被频繁调用造成损失;注册的接口,为了防止恶意注册。在这里为大家推荐一
个验证码的类库,方便大家学习使用。

1. github.com/dchest/captcha

web端是怎么实现验证码的功能呢?

提供一个路由,先在session里写入键值对(k->v),把值写在图片上,然后生成图片,显示在
浏览器上面
前端将图片中的内容发送给后后端,后端根据session中的k取得v,比对校验。如果通过继续下
一步的逻辑,失败给出错误提示

API接口验证码实现方式类似,可以把键值对存储在起来,验证的时候把键值对传输过来一起校验。这
里我只给出了web端的方法,爱动手的小伙伴可以自己尝试一下。

后端

1. package main
2.
3. import (
4. "bytes"
5. "github.com/dchest/captcha"
6. "github.com/gin-contrib/sessions"
7. "github.com/gin-contrib/sessions/cookie"
8. "github.com/gin-gonic/gin"
9. "net/http"
10. "time"
11. )
12.
13. // 中间件,处理session
14. func Session(keyPairs string) gin.HandlerFunc {
15. store := SessionConfig()
16. return sessions.Sessions(keyPairs, store)
17. }
18. func SessionConfig() sessions.Store {
19. sessionMaxAge := 3600
20. sessionSecret := "topgoer"
21. var store sessions.Store

本文档使用 书栈网 · BookStack.CN 构建 - 981 -


gin验证码

22. store = cookie.NewStore([]byte(sessionSecret))


23. store.Options(sessions.Options{
24. MaxAge: sessionMaxAge, //seconds
25. Path: "/",
26. })
27. return store
28. }
29.
30. func Captcha(c *gin.Context, length ...int) {
31. l := captcha.DefaultLen
32. w, h := 107, 36
33. if len(length) == 1 {
34. l = length[0]
35. }
36. if len(length) == 2 {
37. w = length[1]
38. }
39. if len(length) == 3 {
40. h = length[2]
41. }
42. captchaId := captcha.NewLen(l)
43. session := sessions.Default(c)
44. session.Set("captcha", captchaId)
45. _ = session.Save()
46. _ = Serve(c.Writer, c.Request, captchaId, ".png", "zh", false, w, h)
47. }
48. func CaptchaVerify(c *gin.Context, code string) bool {
49. session := sessions.Default(c)
50. if captchaId := session.Get("captcha"); captchaId != nil {
51. session.Delete("captcha")
52. _ = session.Save()
53. if captcha.VerifyString(captchaId.(string), code) {
54. return true
55. } else {
56. return false
57. }
58. } else {
59. return false
60. }
61. }
func Serve(w http.ResponseWriter, r *http.Request, id, ext, lang string,
62. download bool, width, height int) error {

本文档使用 书栈网 · BookStack.CN 构建 - 982 -


gin验证码

63. w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")


64. w.Header().Set("Pragma", "no-cache")
65. w.Header().Set("Expires", "0")
66.
67. var content bytes.Buffer
68. switch ext {
69. case ".png":
70. w.Header().Set("Content-Type", "image/png")
71. _ = captcha.WriteImage(&content, id, width, height)
72. case ".wav":
73. w.Header().Set("Content-Type", "audio/x-wav")
74. _ = captcha.WriteAudio(&content, id, lang)
75. default:
76. return captcha.ErrNotFound
77. }
78.
79. if download {
80. w.Header().Set("Content-Type", "application/octet-stream")
81. }
http.ServeContent(w, r, id+ext, time.Time{},
82. bytes.NewReader(content.Bytes()))
83. return nil
84. }
85.
86. func main() {
87. router := gin.Default()
88. router.LoadHTMLGlob("./*.html")
89. router.Use(Session("topgoer"))
90. router.GET("/captcha", func(c *gin.Context) {
91. Captcha(c, 4)
92. })
93. router.GET("/", func(c *gin.Context) {
94. c.HTML(http.StatusOK, "index.html", nil)
95. })
96. router.GET("/captcha/verify/:value", func(c *gin.Context) {
97. value := c.Param("value")
98. if CaptchaVerify(c, value) {
99. c.JSON(http.StatusOK, gin.H{"status": 0, "msg": "success"})
100. } else {
101. c.JSON(http.StatusOK, gin.H{"status": 1, "msg": "failed"})
102. }
103. })

本文档使用 书栈网 · BookStack.CN 构建 - 983 -


gin验证码

104. router.Run(":8080")
105. }

前端页面

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <title>www.topgoer.com验证码</title>
6. </head>
7. <body>
8. <img src="/captcha" onclick="this.src='/captcha?v='+Math.random()">
9. </body>
10. </html>

浏览器访问http://127.0.0.1:8080

访问http://127.0.0.1:8080/captcha/verify/5721 进行验证

1. {
2. "msg": "failed",
3. "status": 1
4. }

转自:https://www.jianshu.com/p/5ee3c2a71453

本文档使用 书栈网 · BookStack.CN 构建 - 984 -


生成解析token

1. 生成解析token
如今有很多将身份验证内置到API中的方法 -JSON Web令牌只是其中之一。JSON Web令牌(JWT)作
为令牌系统而不是在每次请求时都发送用户名和密码,因此比其他方法(如基本身份验证)具有固有的
优势。要了解更多信息,请直接进入jwt.io上的介绍,然后再直接学习。

以下是JWT的实际应用示例。主要有两个部分:提供用户名和密码以获取令牌;并根据请求检查该令
牌。

在此示例中,我们使用了两个库,即Go中的JWT实现以及将其用作中间件的方式。

最后,在使用此代码之前,您需要将APP_KEY常量更改为机密(理想情况下,该常量将存储在代码库外
部),并改进用户名/密码检查中的内容,TokenHandler以检查不仅仅是myusername/
mypassword组合。

下面的代码是gin框架对jwt的封装

1. package main
2.
3. import (
4. "fmt"
5. "net/http"
6. "time"
7.
8. "github.com/dgrijalva/jwt-go"
9. "github.com/gin-gonic/gin"
10. )
11.
12. //自定义一个字符串
13. var jwtkey = []byte("www.topgoer.com")
14. var str string
15.
16. type Claims struct {
17. UserId uint
18. jwt.StandardClaims
19. }
20.
21. func main() {
22. r := gin.Default()
23. r.GET("/set", setting)
24. r.GET("/get", getting)

本文档使用 书栈网 · BookStack.CN 构建 - 985 -


生成解析token

25. //监听端口默认为8080
26. r.Run(":8080")
27. }
28.
29. //颁发token
30. func setting(ctx *gin.Context) {
31. expireTime := time.Now().Add(7 * 24 * time.Hour)
32. claims := &Claims{
33. UserId: 2,
34. StandardClaims: jwt.StandardClaims{
35. ExpiresAt: expireTime.Unix(), //过期时间
36. IssuedAt: time.Now().Unix(),
37. Issuer: "127.0.0.1", // 签名颁发者
38. Subject: "user token", //签名主题
39. },
40. }
41. token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
42. // fmt.Println(token)
43. tokenString, err := token.SignedString(jwtkey)
44. if err != nil {
45. fmt.Println(err)
46. }
47. str = tokenString
48. ctx.JSON(200, gin.H{"token": tokenString})
49. }
50.
51. //解析token
52. func getting(ctx *gin.Context) {
53. tokenString := ctx.GetHeader("Authorization")
54. //vcalidate token formate
55. if tokenString == "" {
56. ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
57. ctx.Abort()
58. return
59. }
60.
61. token, claims, err := ParseToken(tokenString)
62. if err != nil || !token.Valid {
63. ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
64. ctx.Abort()
65. return
66. }

本文档使用 书栈网 · BookStack.CN 构建 - 986 -


生成解析token

67. fmt.Println(111)
68. fmt.Println(claims.UserId)
69. }
70.
71. func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
72. Claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, Claims, func(token
73. *jwt.Token) (i interface{}, err error) {
74. return jwtkey, nil
75. })
76. return token, Claims, err
77. }

本文档使用 书栈网 · BookStack.CN 构建 - 987 -

You might also like