首页 > 其他分享 >[Go语言Web02]Gin框架Web开发

[Go语言Web02]Gin框架Web开发

时间:2023-03-11 12:11:26浏览次数:43  
标签:Web http func index Web02 Context Go gin main

1. Gin框架初识

1.1 Gin的安装

直接参考官方的文档

1.1.1 安装过程

  1. 下载并安装 gin:
$ go get -u github.com/gin-gonic/gin
  1. 将 gin 引入到代码中:
import "github.com/gin-gonic/gin"

1.1.2 解决安装失败

设置代理:https://goproxy.cn

失败的原因大概率是因为没有设置代理(GOPROXY)。两种方式设置:

① 创建项目时设置

GIN02.png

② 在设置里修改

GIN03.png

1.2 Gin的简单使用

package main

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

func main() {
	r := gin.Default() // 返回默认的路由引擎
	r.GET("/hello", sayHello)
	r.Run() // 默认8080端口,可以自行修改
}
func sayHello(c *gin.Context) {
	c.JSON(200, gin.H{
		"massage": "hello golang!",
	})
}

确实看起来简单。

1.3 RESTful API

1.3.1 浅说一下

一种http编程的代码风格,比较好的东西,之后可以了解一下,其中有提到的一点:

GET 来查询记录

POST 来创建记录

PUT 来更新记录

DELETE 来删除记录

1.3.2 测试一下

package main

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

func main() {
	r := gin.Default() // 返回默认的路由引擎
	r.GET("/hello", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "GET",
		})
	})
	r.POST("/hello", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "POST",
		})
	})
	r.PUT("/hello", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "PUT",
		})
	})
	r.DELETE("/hello", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "DELETE",
		})
	})
	r.Run() // 默认8080,可自行修改
}

启动之后,通过postman发送请求,得到的结果如下:

Postman_a6eDP9hZej.png

Postman_PB2qTEM0At.png

2. Gin框架模板渲染

2.1 单个模板

有了之前用标准库的经验,用gin也更好理解了:

package main

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

func main() {
	r := gin.Default()
	// 模板解析
	r.LoadHTMLFiles("./templates/index.tmpl")

	// 模板渲染
	r.GET("/index", func(c *gin.Context) {
		// http请求
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "firecar",
		})
	})

	r.Run(":8080")
}

2.2 多个模板

goland64_UfsqOpjNly.png

package main

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

func main() {
	r := gin.Default()
	// 模板解析
	//r.LoadHTMLFiles("./templates/index.tmpl")
	r.LoadHTMLGlob("templates/**/*")
	// 模板渲染
	r.GET("posts/index", func(c *gin.Context) {
		// http请求
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
			"title": "posts",
		})
	})
	r.GET("users/index", func(c *gin.Context) {
		// http请求
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
			"title": "users",
		})
	})
	r.Run(":8080")
}
  1. 模板解析的时候()内是要解析tmpl文件的相对路径

  2. r.GET()的第一个参数,代表的是需要访问的url相对路径

  3. c.HTML()的第二个参数是要渲染的模板的文件名(有重复的需要再模板内用{{define xxx}} {{end}}来重命名)

    2.3 自定义模板函数

    使用:

    r.SetFuncMap(template.FuncMap{
        "xxx" : func(xx)xx {
            return xx
        }
    })
    

    例如:

    package main
    
    import (
       "github.com/gin-gonic/gin"
       "html/template"
       "net/http"
    )
    
    func main() {
       r := gin.Default()
       // gin框架中给模板添加自定义函数
       r.SetFuncMap(template.FuncMap{
          "safe": func(str string) template.HTML {
             return template.HTML(str)
          },
       })
       // 模板解析
       r.LoadHTMLGlob("templates/**/*")
       // 模板渲染
       r.GET("posts/index", func(c *gin.Context) {
          // http请求
          c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
             "title": "<h1>wooowowowow</h1>",
          })
       })
       r.GET("users/index", func(c *gin.Context) {
          // http请求
          c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
             "title": "users",
          })
       })
       r.Run(":8080")
    }
    
    {{define "posts/index.tmpl"}}
    <!DOCTYPE html>
    <html>
    <body>
        {{safe .title}}  posts
    </body>
    </html>
    {{end}}
    

    Postman_jpWSlfyKSp.png

2.4 静态文件

html页面上用到的样式文件.css js文件 图片等

goland64_cy3OAzwzie.png

把这些静态文件存在了statics里面。

在main函数里渲染模板之前添加上r.Static("/statics", "./statics")就可以了,第一个参数是之后调用时用的相对位置(可以是不和文件目录相同),第二个参数是文件所在的目录。

goland64_grQWVr9CoL.png

① 为什么.css不调用

我在这么运行的时候发现,网页上是没有使用.css的:

body {
    background-color: cadetblue;
}

在终端上看一下发现确实没有调用这个.css的信息。

goland64_nPkLkbIubX.png

OK发现原来是我他妈的href写成了herf……标记的这么明显了我竟然没发现

修改之后发现就调用成功了。

2.5 前端模板使用

直接在网上扒了一个单页前端模板(不然html太多了太麻烦了)

goland64_CAgPw2S0x3.png

2.6 返回json

2.6.1 通过map传数据

package main

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

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

	r.GET("/json", func(c *gin.Context) {
		data := map[string]any{
			"name":    "firecar",
			"message": "hello golang",
			"age":     18,
		}
		c.JSON(http.StatusOK, data)
	})

	r.Run()
}

网页上显示{"age":18,"message":"hello golang","name":"firecar"}

gin的作者考虑到大伙喜欢用map[string] interface{}所以直接做了一个快捷调用gin.H代表的就是这个类型。因此可以写成下面的样子:

c.JSON(200, gin.H{
    "name":    "firecar",
    "message": "hello golang",
    "age":     18,    
})

实际上的效果是一样的。

2.6.2 通过结构体传数据

3. Gin框架获取参数

3.1 获取querystring参数

querystring就是URL中后面的参数,比如下面的链接:

chrome_48C49nITKZ.png

?前面是请求的URL,后面是通过GET查询的信息参数。

package main

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

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

	r.GET("/web", func(c *gin.Context) {
		// 获取浏览器携带的querystring参数
		name := c.Query("query")
		c.JSON(http.StatusOK, gin.H{
			"name": name,
		})
	})
	r.Run()
}

3.1.1 Query

直接通过c.Query()来查询,后面跟着的就是参数的名字。

Postman_kM8ZPayN6s.png

3.1.2 DefaultQuery

这个就是添加了一个默认值,如果找不到这个名字的参数,就返回默认值:name := c.DefaultQuery("query","hello")

如果搜索的时候?后面没有关键词query那么name最后得到的参数就是hello

3.1.3 GetQuery

name,ok := c.GetQuery("query")

这个就是能顺便带个判断是否有关键词query。没有ok就是false呗,有就是true,和上面也没啥特别的。

3.1.4 多个参数

其实和取一个也没啥差别,就是在访问的时候记得用&分隔开就好了。

package main

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

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

	r.GET("/web", func(c *gin.Context) {
		// 获取浏览器携带的querystring参数
		name := c.Query("query")
		age := c.Query("age")
		c.JSON(http.StatusOK, gin.H{
			"name": name,
			"age" : age,
		})
	})
	r.Run()
}

Postman_f9RiapYVXz.png

3.2 获取form参数

请求的数据通过form表单来提交。

3.2.1 同一个URL不同返回页面

写了一个html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<form action="/login" method="post" novalidate autocomplete="off">
    <div>
        <label for="username">username:</label>
        <input type="text" name="username" id="username">
    </div>
    <div>
        <label for="password">username:</label>
        <input type="text" name="password" id="password">
    </div>
    <div>
        <input type="submit" value="登录">
    </div>
</form>
</body>
</html>

点击submit之后会向/login发送post请求。

package main

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

func main() {
	r := gin.Default()
    // 一定要记得解析html!!否则打不开
	r.LoadHTMLFiles("./login.html")

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

	r.Run()
}

但是我这里只写了获得GET请求的处理方法,没有写POST请求的处理方法。

Postman_VQNQBLRFbR.png

Postman_A7cOq6546o.png

当你填写完表单之后点击提交,就会POST请求/login,无法响应请求导致了404,这也就是为什么同一个URL会有不同的结果,关键在于请求的方式不同。

一次请求对应一次响应!一次请求对应一次响应!一次请求对应一次响应!

3.2.2 调用form参数

package main

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

func main() {
   r := gin.Default()
   r.LoadHTMLFiles("./login.html", "./index.html")

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

   r.POST("/login", func(c *gin.Context) {
      username := c.PostForm("username")
      password := c.PostForm("password")
      c.HTML(http.StatusOK, "index.html", gin.H{
         "name":     username,
         "password": password,
      })
   })

   r.Run()
}

可以看到通过c.PostForm()来获取的元素内的信息,()里面的是元素的name标签而不是id标签。

goland64_cBEGQbkjiJ.png

下面是post请求后的网页。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEX</title>
</head>
<body>
<h1>Hello, {{ .name }}</h1>
<p>你的密码是{{.password}}</p>
</body>
</html>

3.2.3 DefaultPostForm

带默认值的……类似于之前说过的。但是大概率是没填(默认“”)而不是不存在。

username := c.DefaultPostForm("username","somebody")

同理GetPostForm也是如此,多了一个判断存不存在的。

3.3 获取path参数

path参数也可以称为URI参数

统一资源标志符(英語:Uniform Resource Identifier,縮寫:URI)

请求的参数通过URL路径传递,例如user/search/火焰车/大炮

package main

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

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

	r.GET("/:name/:age", func(c *gin.Context) {
		// 获取path参数
		name := c.Param("name")
		age := c.Param("age")
		c.JSON(200, gin.H{
			"name": name,
			"age":  age,
		})
	})

	r.Run()
}

GET标记的地方用/:aaa/:bbb来表示要获取参数的一个标识,之后通过c.Param()来获取参数值。

3.3.1 解决冲突

多用于博客的一些信息汇总的页面上,如下面的代码:

r.GET("/blog/:year/:month", func(c *gin.Context) {
    year := c.Param("year")
    month := c.Param("month")
    c.JSON(200, gin.H{
        "year":  year,
        "month": month,
    })
})

但是!但是!但是!注意一个问题,如果同时还存在一个GET(/:name/:age)就会产生问题,他会同样把/blog/:year/:month给匹配了。

所以,在设置这样的匹配时,如果有多个,需要前面有唯一的标识来区分,比如把/:name/:age改成/userGET/:name/:age就可以解决冲突了。

Postman_hveGMHjVpX.png

Postman_q9anHrQLn3.png

3.4 参数绑定

如果每次按照以下方式来调用参数会,在参数量很大的时候会显得很麻烦:

package main

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

type UserInfo struct {
	username string
	password string
}

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

	r.GET("user", func(c *gin.Context) {
		username := c.Query("username")
		password := c.Query("password")
		u := UserInfo{
			username: username,
			password: password,
		}
		fmt.Printf("%#v\n", u)
		c.JSON(200, gin.H{
			"message": "ok",
		})
	})

	r.Run()
}

3.4.1 如何绑定

关键的代码是err := c.ShouldBind(u) ,这里绑定的是一个地址,因为结构体是值传递,因此我在声明结构体的时候直接声明的是一个指针u := new(UserInfo)(通过new获得的是指针),不然的话需要加&来取地址。

第二点,结构体里面的成员首字母大写否则无法访问到。

第三点,因为绑定的时候肯定不会对应,所以说要添加反射,也就是在成员后加tag标签,如form:"password"。可以通过这个标签来找到该成员。

package main

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

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

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

	r.GET("user", func(c *gin.Context) {
		u := new(UserInfo)
		err := c.ShouldBind(u) // 传入的是地址
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"status":   "ok",
				"username": u.Username,
				"password": u.Password,
			})
		}
	})

	r.Run()
}

3.4.2 测试一下POST请求

package main

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

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

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

	r.POST("/form", func(c *gin.Context) {
		u := new(UserInfo)
		err := c.ShouldBind(u) // 传入的是地址
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"status":   "ok",
				"username": u.Username,
				"password": u.Password,
			})
		}
	})

	r.Run()
}

Postman_ddf0C4yOpd.png

4. Gin框架文件上传

4.1 单个文件上传

还是,遇到问题就先写注释来理清思路,前端的内容不多说,术业有专攻大体了解一下就好了。当我们收到了一份上传文件的请求,首先我们要从请求中获取文件(二进制之类的数据),之后再将数据保存到本地(即服务器端)。

package main

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

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("./index.html")
	r.GET("/index", func(c *gin.Context) {
		c.HTML(200, "index.html", nil)
	})
	r.POST("upload", func(c *gin.Context) {
		// 从请求中读取获得的文件
		f, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err,
			})
		} else {
			// 将获取的文件保存在本地(服务器端)
			dst := fmt.Sprintf("./%s", f.Filename)
			//dst := path.Join("./",f.Filename)
			_ = c.SaveUploadedFile(f, dst)
		}
		c.JSON(200, gin.H{
			"status": "ok",
		})
	})
	r.Run()
}

其中index.html如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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>

就是一个很简单的文件上传表单,请求方式是POST,enctype一定要设置为multipart/form-data以上传图片、音频之类的文件。

注意以下几点:

  1. 不要忘记解析html文件r.LoadHTMLFiles("./index.html")
  2. 从请求中获取文件的方法就是f, err := c.FormFile("f1")
dst := fmt.Sprintf("./%s", f.Filename)
//dst := path.Join("./",f.Filename)
_ = c.SaveUploadedFile(f, dst)

两种方式都可以组合成文件的名称(用来存放接收到的文件的地址)。第一种方式的Sprintf是将后面格式的内容做为string类型的返回值给dst。

4.2 多个文件上传

在前端html界面上也需要修改一点,在上传文件的input后面要加上multiple="multiple"

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="f1" multiple="multiple">
    <input type="submit" value="上传">
</form>

之后就是最主要的代码了:

package main

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

func main() {
	r := gin.Default()
	r.LoadHTMLFiles("./index.html")
	r.GET("/index", func(c *gin.Context) {
		c.HTML(200, "index.html", nil)
	})
	// 默认为 32MiB
	// r.MaxMultipartMemory = 8 << 20 // 8MiB
	r.POST("upload", func(c *gin.Context) {
		// 从请求中读取获得的文件
		form, _ := c.MultipartForm()
		files := form.File["f1"]
		for _, file := range files {
			dst := path.Join("./" + file.Filename)
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(200, gin.H{
			"status": "ok",
		})
	})
	r.Run()
}

其实就是从上传单个文件的一个form变成了很多个form,因此先声明了一个c.MultipartForm,之后再靠文件的name="f1"找到每一个文件form.File["f1"]。之后就跟之前一样保存文件就可以了。

5. Gin框架请求重定向

5.1 HTTP重定向

很简单,只使用一个方法即可func (c *Context) Redirect(code int, location string)

Redirect + 状态码 + 重定向的地址

package main

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

func main() {
	r := gin.Default()
	r.GET("index", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "https://www.bilibili.com")
	})
	r.Run()
}

5.2 路由重定向

package main

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

func main() {
	r := gin.Default()
    
	r.GET("/a", func(c *gin.Context) {
		// 跳转到 /b 对应的路由处理函数
		c.Request.URL.Path = "/b"
		r.HandleContext(c)
	})
	r.GET("/b", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "b",
		})
	})
	r.Run()
}

此时访问/a,会出现{"message":"b"},但是网页仍然是/a,请求是先运行的/b再运行/a

通过修改c.Request.URL.Path = "/b"然后再处理请求r.HandleContext(c)

常见的就是访问网站未登录点击某些页面会跳转到登录页面,登陆之后会跳转回刚才访问的界面。

6. Gin框架路由和路由组

6.1 普通路由

就是我们一直用的那些方法,常见的:

r.GET("/home",func(c *gin.Context{...}))
r.POST("/home",func(c *gin.Context{...}))
r.PUT("/home",func(c *gin.Context{...}))
r.DELETE("/home",func(c *gin.Context{...}))

同时还有一个可以匹配所有方法的Any方法:

r.Any("/home",func(c *gin.Context{...}))

里面包含了常用的方法。

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

r.NoRoute(func(c *gin.Context) {
   c.HTML(http.StatusOK,"404.html",nil)
})

别忘了要解析html。

6.2 路由组

我们可以将拥有共同URL前缀的路由划分为一个路由组。

习惯性{}包裹同路组的路由,只是为了增加可读性,实际上是否包裹对功能没有影响。

package main

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

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

	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/index",
			})
		})
		videoGroup.GET("/oo", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"msg": "/video/index",
			})
		})
	}
	shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"msg": "/shop/index",
			})
		})
	}

	r.Run()
}

7. Gin框架中间件

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

7.1 定义中间件

如果每次在路由时添加识别、认证或者其他功能会导致代码冗余,那么就可以在经过路由前就进行特定的操作,因为夹在中间,所以也被称为中间件。

chrome_HWMiXeQr1c.png

写一个计时的中间件:

package main

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

// handlerFunc 下面也可称为中间件
func indexHandler(c *gin.Context) {
	fmt.Println("index in ...")
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
	fmt.Println("index out ...")
}

// 定义一个中间件m1
func m1(c *gin.Context) {
	// 计时
	fmt.Println("m1 in ...")
	start := time.Now()
	c.Next() // 调用后续的处理函数(代码中的indexHandler)
	// c.Abort() // 阻止调用后续的函数
	cost := time.Since(start)
	fmt.Printf("cost : %v\n", cost)
	fmt.Println("m1 out ...")
}
func main() {
	r := gin.Default()
	r.GET("/home", m1, indexHandler)
	r.GET("/shop", m1, func(c *gin.Context) {
		c.JSON(http.StatusOK,gin.H{
			"msg": "shop",
		})
	})
	r.Run()
}


/*
m1 in ...
index in ...
index out ...
程序运行耗时: 0s
m1 out ...
*/

看一下标准库func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc)实际上这个GET方法是个变长的方法(可以放多个HandlerFunc)。

他是按照顺序来执行的,那么我在真正意义上的/index(indexHandler)出现之前先运行另一个Handler

Func就是一个中间件(m1),并且因为不再使用匿名函数来定义HandlerFunc,可以很多个路由都使用这个中间件,实现了代码的复用。

7.2 全局注册中间件函数

我所有的网页都要用的话就可以搞一个全局注册,这样就不需要每次都再额外加上这个HandlerFunc的标识了(极致的简约)。

就提前加一句r.Use()就可以了,参数是HandlerFunc类型。

package main

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

// handlerFunc 下面也可称为中间件
func indexHandler(c *gin.Context) {
	fmt.Println("index in ...")
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
	fmt.Println("index out ...")
}

// 定义一个中间件m1
func m1(c *gin.Context) {
	// 计时
	fmt.Println("m1 in ...")
	start := time.Now()
	c.Next() // 调用后续的处理函数(代码中的indexHandler)
	// c.Abort() // 阻止调用后续的函数
	cost := time.Since(start)
	fmt.Printf("cost : %v\n", cost)
	fmt.Println("m1 out ...")
}
func main() {
	r := gin.Default()
	r.Use(m1) // 全局注册中间件函数m1
	r.GET("/home", indexHandler)
	r.GET("/shop", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "shop",
		})
	})
	r.Run()
}

这里的(上一个代码也有)c.Next指的是调用后续的HandlerFunc()从输出顺序上来看就容易知道,先开始运行m1,然后运行index,然后index结束,最后m1结束。

如果不使用c.Next就会顺序执行,即运行完m1之后再运行index,在这里因为想要给index计时所以要用c.Next

7.3 多个中间件

package main

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

// handlerFunc 下面也可称为中间件
func indexHandler(c *gin.Context) {
	fmt.Println("index in ...")
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
	fmt.Println("index out ...")
}

// 定义一个中间件m1
func m1(c *gin.Context) {
	// 计时
	fmt.Println("m1 in ...")
	start := time.Now()
	c.Next() // 调用后续的处理函数(代码中的indexHandler)
	cost := time.Since(start)
	fmt.Printf("cost : %v\n", cost)
	fmt.Println("m1 out ...")
}

func m2(c *gin.Context) {
	fmt.Println("m2 in ...")
	c.Next()
	fmt.Println("m2 out ...")
}
func main() {
	r := gin.Default()
	r.Use(m1, m2) // 全局注册中间件函数m1
	r.GET("/home", indexHandler)
	r.GET("/shop", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "shop",
		})
	})
	r.Run()
}
/*
m1 in ...
m2 in ...
index in ...
index out ...
m2 out ...
cost : 0s
m1 out ...
*/

上面就属于有点嵌套的意思了。

想想一个登陆状态查询的中间件如何来写:

func authMiddleware(c *gin.Context) {
    // 如果登陆c.Next,如果没有登陆c.Abort.
}

实际运用中可能要把这些中间件做成闭包的形式:

func authMiddleware() gin.HandlerFunc{
    // 连接数据库……
    // 其他的准备工作……
    return func(c *gin.Context) {
        // 如果登陆c.Next,如果没有登陆c.Abort.
    }
}

7.4 路由组注册中间件

两种方法,效果是相同的。

shopGroup := r.Group("/shop",m1) 
{
    shopGroup.GET("XX",func(c *gin.Context){...})
    shopGroup.GET("oo",func(c *gin.Context){...})
    ...
}

或者

shopGroup := r.Group("/shop")
shopGroup.Use(m1)
{
    shopGroup.GET("XX",func(c *gin.Context){...})
    shopGroup.GET("oo",func(c *gin.Context){...})
    ...
}

7.5 跨中间件取参数

使用c.Set()来设置,之后用的时候通过c.GET或者c.MustGet来获取(后者没有error)。

package main

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

// handlerFunc 下面也可称为中间件
func indexHandler(c *gin.Context) {
	fmt.Println("index in ...")
	name, _ := c.Get("name")
	c.JSON(http.StatusOK, gin.H{
		"msg": name,
	})
	fmt.Println("index out ...")
}

// 定义一个中间件m1
func m1(c *gin.Context) {
	fmt.Println("m1 in ...")
	c.Next() // 调用后续的处理函数(代码中的indexHandler)
	fmt.Println("m1 out ...")
}

// 定义一个中间件m2
func m2(c *gin.Context) {
	fmt.Println("m2 in ...")
	c.Set("name", "Firecar")
	c.Next()
	fmt.Println("m2 out ...")
}
func main() {
	r := gin.Default()
	r.Use(m1, m2)
	r.GET("/home", indexHandler)
	r.Run()
}

7.6 注意事项

7.6.1 gin默认中间件

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

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

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

7.6.2 gin中间件中使用goroutine

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

8. 参考链接

标签:Web,http,func,index,Web02,Context,Go,gin,main
From: https://www.cnblogs.com/F0und/p/17205615.html

相关文章

  • go 安装go环境
    目录go安装go环境mac安装go环境安装包下载配置修改go安装go环境mac安装go环境安装包下载https://golang.google.cn下载后傻瓜式安装,一直点下一步,直至安装结束配置......
  • 【质因数分解算法详解】C/Java/Go/Python/JS/Dart/Swift/Rust等不同语言实现
    关于质因数分解算法的不同语言实现,通过实例来看不同语言的差异什么是质因数算法?即任意一个合数可以分解为多个质数相乘。例如:20=2*2*545=3*3*5210=2*......
  • webview注入js
    js中与webview通信functionsetStyle(){console.log('1111')returnfunction(){console.log('2222')try{console.log('3333')//隐藏图......
  • 0002-ALGO1006 拿金币
    试题算法训练拿金币动态规划问题,选取上一步最优的结果加上此步可以获取的金币数量。importjava.util.Scanner;/***@authorHuaWang135608*@date2023.03.11......
  • WebApi问题与跨域和返回json
    1、编写接口时,发现访问不到指定接口注释掉[Authorize]特性给方法设置访问方式,已经指定路由方法如下2、出现一下跨域问题在web.config里面配置2、返回类型指定接口......
  • 使用Go语言创建WebSocket服务器和客户端
    WebSocket是一种新型的网络通信协议,可以在Web应用程序中实现双向通信。在这篇文章中,我们将介绍如何使用Go语言编写一个简单的WebSocket服务器。首先,我们需要使用G......
  • go的day4
    面向对象特征方法假设有两个方法,一个方法的接收者是指针类型,一个方法的接收者是值类型,那么:+对于值类型的变量和指针类型的变量,这两个方法有什么区别?+如果这两个方法是......
  • 如何从SPFx Solution连接old API,为Viva Connections构建web部件、扩展、团队应用程序
    Yammer现在更名为VivaEngage,在本文中,我将介绍如何从SPFxSolution连接oldAPI,为VivaConnections构建web部件、扩展、团队应用程序等。关于授权VivaEngage权限,请参照上一......
  • how to write a webpack plugin by using webpack 5 and TypeScript All In One
    howtowriteawebpackpluginbyusingwebpack5andTypeScriptAllInOne如何使用webpack5和TypeScript编写一个webpack插件(......
  • how to write a webpack loader by using webpack 5 and TypeScript All In One
    howtowriteawebpackloaderbyusingwebpack5andTypeScriptAllInOne如何使用webpack5和TypeScript编写一个webpack加载器demoshttps://github.co......