Go Web开发框架 Gin

Go Web开发框架 Gin

10.1 Gin

现在的开发模式是后端只提供一份数据,然后不管是移动端还是网页端,如果想要展现出不同的效果,可以自己根据这份数据个性化构建展示效果

下载Gin package

GOPROXY=https://goproxy.cn
go get -u github.com/gin-gonic/gin

引入使用即可

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

//使用Gin框架

func main() {

	r := gin.Default() //返回默认的路由引擎

	//访问路径以及返回
	r.GET("/hello", sayhello)

	//启动
	//封装了http.ListenAndServe
	r.Run(":8080")
}

func sayhello(c *gin.Context) {
	c.JSON(200, gin.H{
		"message": "Hello Golang",
	})
}

10.2 RESTful架构

REST是客户端和服务器之间进行交互的时候,使用HTTP协议中的四个请求方法代表不同的动作,更多的是一种规范

GET获取资源
POST新建资源
PUT更新资源
DELETE删除资源
r.GET("/book",...)
r.POST("/book",...)
r.PUT("/book",...)
r.DELETE("/book",...)

浏览器默认只能发送GET和POST请求,如果没有通过AJAX的话,那用postman作为测试工具

10.3 Gin框架模版创建、解析和渲染

我们仍然先按照三步走操作

第一步,定义模版文件index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>posts/index</title>
</head>
<body>
{
   
   {.title}} //在模版中使用哈希表的键来传参
</body>
</html>

第二步和第三步,解析模版和渲染模版

func main() {
	//创建一个gin模版引擎路由
	r := gin.Default()

	//解析模版
	r.LoadHTMLFiles("./templates/index.tmpl")

	//创建并处理GET请求
	r.GET("/index", func(c *gin.Context) {//指定路径和handler
		c.HTML(http.StatusOK, "index.tmpl", gin.H{ //模版渲染,向模版中传入参数
			"title": "shiyivei.com",
		})
	})

	r.Run(":8080") //启动服务
}

渲染多个模版

可以通过define先重命名多个名字一样的模版文件,然后再解析

{
   
   {define "posts/index.tmpl"}} //重命名
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>posts/index</title>
</head>
<body>
{
   
   {.title}}
</body>
</html>
{
   
   {end}} //不要忘了end

解析

//解析模版
	r.LoadHTMLFiles("./templates/index.tmpl","templates/users/index.tmpl")

但是,当文件过多时,可以通过指定文件夹的方式加载所有tmpl文件

//解析模版
	//r.LoadHTMLFiles("./templates/index.tmpl", "templates/users/index.tmpl")
	r.LoadHTMLGlob("templates/**/*")

同样我们也需要多个path和handler来处理

func main() {
	//创建一个gin模版引擎路由
	r := gin.Default()

	//解析模版
	//r.LoadHTMLFiles("./templates/index.tmpl", "templates/users/index.tmpl")
	r.LoadHTMLGlob("templates/**/*")

	//创建并处理GET请求
	r.GET("/posts/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ //模版渲染
			"title": "posts/index.tmpl",
		})
	})
	//创建并处理GET请求
	r.GET("/users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ //模版渲染
			"title": "users/index.tmpl",
		})
	})

	r.Run(":8080") //启动服务
}

10.4 自定义函数

使用Gin引擎也是可以自定义函数,同样的,在解析模版之前

r.SetFuncMap(template.FuncMap{
		"safe": func(str string) template.HTML { //定义匿名函数
			return template.HTML(str)
		},
	})

然后直接常规调用就行

//创建并处理GET请求
	r.GET("/users/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ //模版渲染
			"title": "<a href='liwenzhou.com'>liwenzhou的博客</a>",
		})
	})

	r.Run(":8080") //启动服务

10.5 静态文件处理

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

使用/xxx路径加载文件

//如果有静态文件的话需要先加载
	r.Static("/xxx", "./statics")

在tmpl文件添加链接

{
   
   {define "users/index.tmpl"}}
<!doctype html>
<html lang="en">
<head>
    <link rel="stylesheet" href="/xxx/index.css"> //添加css静态文件
    <title>users/index</title>
</head>
<body>
{
   
   {.title | safe}}
<script src="/xxx/index.js"></script> //添加js静态文件
</body>
</html>
{
   
   {end}}

另外,Gin框架不支持使用block

使用网页上随意前端页面的一个例子

//返回从网上下载的模版
	r.GET("/home", func(c *gin.Context) {
		c.HTML(http.StatusOK, "home.html", nil)
	})

	r.Run(":8080") //启动服务

10.6 返回json

返回json格式数据的两种方式

  1. 前端直接请求服务器,服务器返回完整的web页面
  2. 前端框架拿到json格式的数据,自己进行渲染

那如果使用gin框架进行json格式文件的渲染?有两种方式,可以把map和struct返回成json格式数据如下:

func main() {
	//使用gin框架的步骤
	//1.引入
	r := gin.Default()

	//2.写访问路径和handler
	//方法一,使用map
	r.GET("/json", func(c *gin.Context) {

		//定义一个map,填入数据
		//data := map[string]interface{}{
		//	"name":    "小王子",
		//	"message": "hello world",
		//	"age":     18,
		//}

		//or使用内置的map进行填写
		data := gin.H{"name": "小王子", "message": "hello world", "age": 18}
		c.JSON(http.StatusOK, data)
	})

	//方法二,使用struct
	//type msg struct {
	//	Name    string
	//	Message string
	//	Age     int
	//}

	//使用tag标签可以把能导出的字段设置为自定义的格式,如把Name 显示成 name
	type msg struct {
		Name    string `json:"name"`
		Message string `json:"message"`
		Age     int    `json:"age"`
	}
	//2.写访问路径和handler
	r.GET("/another_json", func(c *gin.Context) {

		data := msg{
			Name:    "大王子",
			Message: "Hello golang",
			Age:     19,
		}
		c.JSON(http.StatusOK, data)
	})
	//3.启动,写上端口号
	r.Run(":8080")
	//输出结果: {"name":"大王子","message":"Hello golang","age":19}
}

浏览器呈现结果

{"age":18,"message":"hello world","name":"小王子"}

10.7 gin获取querystring参数

在以上操作中,我们访问的都是路径,然后让浏览器返回该路径所对应的页面。现在我们想通过在浏览器中输入特定参数实现查询功能。首先我们需要先捕获这个输入参数

实际上这个参数对于后端来说就是一个个Map的key,当输入的参数和key所匹配时,就返回对应的value

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

	r.GET("/web", func(c *gin.Context) {

		//c.JSON(http.StatusOK, "ok")
		//第一种方式
		//name := c.Query("query")
		//第二种方式
		//name := c.DefaultQuery("query", "somebody")//如果制定了key就返回key对应的指定内容,否则返回默认值
		//第三种方式
		name, ok := c.GetQuery("query") //和上述一致的意思
		if !ok {
			name = "somebody"
		}
		//返回
		c.JSON(http.StatusOK, gin.H{
			"name": name,
		})
	})
	r.Run(":8080")
}

10.8 获取form参数

form表单通过POST请求来提交

编写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">password:</label>
        <input type="password" name="password" id="password">
    </div>

    <div>
        <input type="submit" value="登录">
    </div>
</form>

</body>
</html>

通过表单提交请求,此时不能成功,因为使用了GET请求

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

	r.LoadHTMLFiles("./login.html")

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

	r.Run(":8080")
}

再写一个POST请求

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,
		})
	})

此时当访问http://127.0.0.1:8080/login 时,实际上触发的GET请求,当提交表单时,表单本身会触发POST请求,对应的会返回相应的内容

10.9 获取path参数

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

	r.GET("/:name/:age", func(c *gin.Context) {

		//获取路径参数
		name := c.Param("name")
		age := c.Param("age")

		c.JSON(http.StatusOK, gin.H{
			"name": name,
			"age":  age,
		})
	})
	r.Run(":8080")
}

访问路径:http://127.0.0.1:8080/小王子/28

{"age":"28","name":"小王子"}

10.10 参数绑定

现在我们的需求是将用户传进来的参数保存起来,我们定义一个结构体来存储

ShouldBind函数可以把query/form/json不同形式提交的数据绑定到定义的结构体

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

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

	r.GET("/user", func(c *gin.Context) {
	//方法1:一个一个获取
		获取的赋值给变量
		//username := c.Query("username")
		//password := c.Query("password")
		//一个一个存储
		把变量存起来
		//user := UserInfo{username: username, password: password}
		//fmt.Printf("%#v\n", user)
		
		//方式2:使用绑定函数
		var user UserInfo
		err := c.ShouldBind(&user) //使用指针才能改变原来的值
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		} else {
			//返回一个状态
			fmt.Printf("%#v\n", user)
			c.JSON(http.StatusOK, gin.H{
				"status": "ok",
			})
		}
		
		//同样的可以用POST请求
		r.POST("/form", func(c *gin.Context) {
		var user UserInfo
		err := c.ShouldBind(&user) //使用指针才能改变原来的值
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		} else {
			//返回一个状态
			fmt.Printf("%#v\n", user)
			c.JSON(http.StatusOK, gin.H{
				"status": "ok",
			})
		}
	})
	
	r.Run(":8080")
}

10.11 gin文件上传

文件上传分为单个文件和多个文件,但是原理是一样的,即为:上传-读取-保存

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="f1">
    <input type="submit" name="上传">
</form>
</body>
</html>
func main() {
	r := gin.Default()
	
	//先写一个用来上传文件的html页面
	r.LoadHTMLFiles("./index.html")

	//使用Get请求访问页面并让用户上传文件
	r.GET("/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", nil)
	})
	//用户提交之后使用post的请求将文件处理
	r.POST("/upload", func(c *gin.Context) {
		//从请求中读取文件
		f, err := c.FormFile("f1")
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			//filePath := fmt.Sprintf("./%s",f.Filename)
			filePath := path.Join("./", f.Filename) //写到本文件夹下
			err = c.SaveUploadedFile(f, filePath)
			if err != nil {
				fmt.Printf("save uploadfile failed, err:%v", err)
			}
			c.JSON(http.StatusOK, gin.H{
				"status": "ok",
			})
		}
	})
	
	r.Run(":8080")
}

10.12 gin请求重定向

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

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

	r.GET("/index", func(c *gin.Context) {
		//c.JSON(http.StatusOK, gin.H{
		//	"status": "ok",
		//})
		//跳转百度
		c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
	})

	//内部重定向,转换处理请求的函数
	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": "bbb",
		})
	})

	r.Run(":8080")
}

10.13 gin路由和路由组

我们可以为每一个路径设置路由

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

	//一个请求的路径和一个请求的方法对应一个handler
	//常见的有四个请求方法
	//获取信息
	//r.GET("/index", func(c *gin.Context) {
	//	c.JSON(http.StatusOK, gin.H{
	//		"method": "GET",
	//	})
	//})
	//
	创建信息,如注册成为会员
	//r.POST("/index", func(c *gin.Context) {
	//	c.JSON(http.StatusOK, gin.H{
	//		"method": "POST",
	//	})
	//})
	//
	删除数据
	//r.DELETE("/index", func(c *gin.Context) {
	//	c.JSON(http.StatusOK, gin.H{
	//		"method": "DELETE",
	//	})
	//})
	//
	修改部分数据
	//r.PUT("/index", func(c *gin.Context) {
	//	c.JSON(http.StatusOK, gin.H{
	//		"method": "PUT",
	//	})
	//})

	//一种方式汇总所有请求方式
	r.Any("/index", func(c *gin.Context) {
		switch c.Request.Method {
		case "GET":
			c.JSON(http.StatusOK, gin.H{"method": "GET"})
		case "POST":
			c.JSON(http.StatusOK, gin.H{"method": "POST"})
		case "PUT":
			c.JSON(http.StatusOK, gin.H{"method": "PUT"})
		case "DELETE":
			c.JSON(http.StatusOK, gin.H{"method": "DELETE"})
		}
	})
	err := r.Run(":8000")
	if err != nil {
		fmt.Printf("run server failed,err:%v", err)
	}
}

针对那些还需未穷尽的路径,我们统一处理,如:

r.NoRoute(func(c *gin.Context) {
		c.JSON(http.StatusNotFound, gin.H{"msg": "baidu.com"})
	})

路由组

可以把以某段路径开头的组织为一组,公用前缀提取

//提取公共前缀
	videoGroup := r.Group("/video")

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

		})
		videoGroup.GET("/xxx", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"msg": "/video/xxx"})

		})
		videoGroup.GET("/yyy", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"msg": "/video/yyy"})

		})
	}

并且路由是支持嵌套的

10.14 Gin中间件

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

钩子函数其实也是一个handler

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

	r.Use(m1, m2) //全局注册中间件m1,m2

	//请求先走m1再走中间件
	r.GET("/index", indexHandler)
	r.GET("/shop", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "shop",
		})
	})
	r.GET("/user", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "user",
		})
	})

	r.Run(":8082")
}

//把HandlerFunc提取出来

func indexHandler(c *gin.Context) {
	fmt.Println("index")
	c.JSON(http.StatusOK, gin.H{
		"message": "index",
	})
}

//定义一个中间件

func m1(c *gin.Context) {
	//fmt.Println("m1")
	//c.JSON(http.StatusOK, gin.H{
	//	"message": "m1",
	//})
	fmt.Println("m1 in...")
	start := time.Now()
	c.Next() //调用后续处理的函数
	cost := time.Since(start)
	fmt.Println(cost)
	fmt.Println("m1 out...")
}

func m2(c *gin.Context) {
	fmt.Println("m2 in...")
	//c.Next()  //调用后续处理的函数
	c.Abort() //阻止调用后续处理的函数
	fmt.Println("m2 out...")
}

浏览器输出结果,请求先经过m1,再到indexHandler

{"message":"m1"}{"message":"index"}

如果多个请求都用同一个中间件,则可以将其定义全局变量

r.Use(m1, m2) //全局注册中间件m1,m2

中间件的更多形式

//一个常见的中间件模版
//func authMiddleware(x *gin.Context)  {
//是否登录判断
//if是登录用户
//c.next()
//else
//c.Abort()
//}

//但是更常写成闭包的形式
func authMiddleware(doCheck bool) gin.HandlerFunc {
	//连接数据库
	//或者一些其他的准备工作
	return func(c *gin.Context) {
		if doCheck {
			//是否登录判断
			//if是登录用户
			//c.next()
			//else
			//c.Abort()
		} else {
			c.Abort()
		}
	}
}

还可以针对路由组定义中间件

	//路由组1
	xx1Group := r.Group("/xx1",authMiddleware(true))
	{
		xx1Group.GET("/index", func(c *gin.Context) {
			c.JSON(http.StatusOK,gin.H{
				"msg":"xx1Group",
			})
		})
	}

	//路由组2
	xx2Group := r.Group("/xx2")
	xx2Group.Use(authMiddleware(true))
	{
		xx2Group.GET("/index", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{
				"msg": "xx2Group",
			})
		})
	}

如果在中间件中判断成功,怎么传递结果其它

func m2(c *gin.Context) {
	fmt.Println("m2 in...")
	
	//在中间件中先调用set方法
	c.Set("name","shiyivei")
	//c.Next()  //调用后续处理的函数
	c.Abort() //阻止调用后续处理的函数
	fmt.Println("m2 out...")
}
func indexHandler(c *gin.Context) {
	fmt.Println("index")
	//在处理函数中使用Get方法获取
	name, ok := c.Get("name")
	if !ok {
		name = "匿名用户"
	}
	c.JSON(http.StatusOK, gin.H{
		"message": name,
	})
}

另外,在中间件中使用goroutine时,不能使用原始上下文中的(c *gin.Context).必须使用其只读副本(c.Copy())

go func(c.Copy())

猜你喜欢

转载自blog.csdn.net/weixin_51487151/article/details/124830173