前言

Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍。

如果你是性能和高效的追求者, 你会爱上Gin

Go语言里最流行的Web框架,Github上有32K+star。 基于httprouter开发的Web框架。 中文文档齐全,简单易用的轻量级框架。

 

安装

D:\learning-gin>set GOPROXY=https://goproxy.cn
-------------------------------------------------------
D:\learning-gin>go get -u github.com/gin-gonic/gin
go: google.golang.org
/protobuf upgrade => v1.25.0 go: gopkg.in/yaml.v2 upgrade => v2.4.0 go: github.com/golang/protobuf upgrade => v1.4.3 go: github.com/ugorji/go/codec upgrade => v1.2.1 go: golang.org/x/sys upgrade => v0.0.0-20201211090839-8ad439b19e0f go: github.com/json-iterator/go upgrade => v1.1.10 go: github.com/modern-go/reflect2 upgrade => v1.0.1 go: github.com/go-playground/validator/v10 upgrade => v10.4.1 go: github.com/modern-go/concurrent upgrade => v0.0.0-20180306012644-bacd9c7ef1dd go: golang.org/x/crypto upgrade => v0.0.0-20201208171446-5f87f3452ae9

 

Gin简单示例

package main

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

func index(c *gin.Context) {
    //返回json类型的数据,h=type H map[string]interface{}
    c.JSON(200, gin.H{"msg": "您好呀!"},
    )
}

func main() {
    //定义1个默认路由(基于httprouter的)
    router := gin.Default()
    //增加url
    router.GET("/index", index)
    //server段开始linsten运行
    router.Run("127.0.0.1:8000")

}

 

request&response Header 

vue设置请求头中的token

this.$http.defaults.headers.common.X-token = 'sidhlmajldhbd-vue'

Gin获取请求头中的Token

token := c.Request.Header.Get("X-token")

Gin响应头设置Token

c.Header("X-token","sidhlmajldhbd-gin")

 

Gin request

我们可以通过gin的context获取到客户端请求携带的url参数、form表单、json数据、文件等。

package main

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


type user struct {
    Name string `json:"name"`
    City string `json:"city"`
}
var person=&user{}
//从url获取参数
func urlData(c *gin.Context) {
    //name:=c.Query("name") //获取url参数,获取不到获取空字符串
     name:=c.DefaultQuery("name","zhanggen") ////获取url参数,获取不到获取默认!
    city:=c.DefaultQuery("city","bj")
    person.Name=name
    person.City=city
    c.JSON(200,person)
}

//从form表单中获取数据
func formData(c *gin.Context) {
    c.PostForm("name")
    person.Name=c.DefaultPostForm("name","Martin")
    person.City=c.DefaultPostForm("city","London")
    c.JSON(200,person)
}

//获取url地址参数
func pathData(c *gin.Context){
    person.City=c.Param("city")
    person.Name=c.Param("name")
    c.JSON(200,*person)
}
//获取json数据
func jsonData(c *gin.Context){
    c.Bind(person)
    fmt.Println("-----------------",*person)
    c.JSON(200,person)
}


func main()  {
    r:=gin.Default()
    //http://127.0.0.1:8001/user?name=zhanggen&city=beijing
    r.GET("/user",urlData)
    r.POST("/user",formData)
    //http://127.0.0.1:8001/user/bj/zhanggen
    r.GET("/user/:city/:name",pathData)
    r.POST("/user/json/",jsonData)
    r.Run(":8001")
}

 

Gin shouldBind

默认情况下,我们需要根据客户端请求的content-type,在后端使用不同的方式,获取客户端请求参数。

获取个请求参还需要c.Query、c.PostForm、c.Bind、C.Param,这也太麻烦了~

shouldBind可帮助我们根据客户端request的content-type,自动获取请求参数,并赋值给后端struct的字段。

package main

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

//0.contentType对应ShouldBind对应的结构体
type UserInfo struct {
	Username string `form:"username" json:"username"`
	Password string `form:"password" json:"password"`
}

func index(c *gin.Context) {
	requestMethod := c.Request.Method
	//1.声明1个值类型uerinfo类型的变量u
	var user UserInfo
	//2.把客户端request请求的参数和后端的结合体字段进行绑定
	err := c.ShouldBind(&user)
	if err != nil {
		c.JSON(400, gin.H{"err": err.Error()})
		return
	}
	//3.可以通过反射的方式,根据客户端request的contentType自动获取数据了
	if requestMethod == "GET" {
		fmt.Println(user)
		c.HTML(200, "index.html", gin.H{})
	}
	if requestMethod == "POST" {
		fmt.Println(user)
		c.JSON(200, gin.H{"data": "postOkay"})
	}

}

func main() {
	router := gin.Default()
	router.Static("/static", "./static")
	router.LoadHTMLGlob("templates/*")
	router.GET("/user", index)
	router.POST("/user", index)
	router.Run(":8002")
}

  

Gin response

我们web开发过程中,大型项目会采用MVVM(前后端分离)的架构,小型项目会采用MTV(模板渲染)的架构。

疏通同归其目的都是完成数据驱动视图,不同的是数据驱动视图的地方不一样。

貌似web开发玩得就是这6个字,数据----》 驱动-----》视图。空谈误国,怎么才能更好的驱动视图才是关键。

MTV模式(模板渲染):后端使用模板语法也就是字符串替换的方式,在后端直接完成数据和HTML的渲染,直接返回给客户端。

MVVM(前后端分离架构):后端返回json数据,前端使用axios/ajax的方式获取到数据,使用vue等前端框架完成数据到HTML的渲染。

 

1.RESTful API

只要API程序遵循了REST风格,那就可以称其为RESTful API。

其实核心思想是1个资源对应1个URL,客户端对这1资源操作时(不同的request.get/post/put/delete方法)对应后端的增/删/改/查操作。

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作。

我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

请求方法 URL 含义
GET /book 查询书籍信息
POST /create_book 创建书籍记录
POST /update_book 更新书籍信息
POST /delete_book 删除书籍信息

我们按照RESTful API设计如下:

请求方法 URL 含义
GET /book 查询书籍信息
POST /book 创建书籍记录
PUT /book 更新书籍信息
DELETE /book 删除书籍信息

 

 

c.JSON响应json数据

package main

import "github.com/gin-gonic/gin"
//结构体
type user struct {
    Name string `json:"name"`
    Age int     `json:"age"`
}
//视图函数
func perosn(c *gin.Context)  {
    var userInfor=user{Name: "张根",Age:18}
    c.JSON(200,userInfor)
}
func main(){
    r:=gin.Default()
    r.GET("/person/",perosn)
    r.Run(":8002")
}

 

2.MVC模板渲染

如果是小型项目、历史原因、SEO优化我们使用模板渲染,Gin也是支持MTV模式的。

package main

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

func index(c *gin.Context) {
    //3.gin 模板渲染
    c.HTML(200, "index.html", gin.H{"title": "首页", "body": "hello"})

}

func main() {
    //1.创建1个默认的路由引擎
    router := gin.Default()
    router.GET("/", index)
    //2.gin模板解析
    router.LoadHTMLGlob("templates/*")    //正则匹配templates/所有文件
    router.LoadHTMLGlob("templates/**/*") //正则匹配template/目录/所有文件
    err := router.Run(":9001")
    if err != nil {
        fmt.Println("gin启动失败", err)
    }
}

 

3.文件上传

http请求也可以传输文件,有时候我们可以使用gin搭建1个ftp服务器。

单个文件上传

package main

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

func handleFile(c *gin.Context) {
	method := c.Request.Method
	if method == "GET" {
		c.HTML(200, "file.html", gin.H{})
	}

	if method == "POST" {
		//1.从客户端请求中获取文件
		fileObj, err := c.FormFile("localFile")
		if err != nil {
			c.JSON(400, gin.H{"err": err.Error()})
			return
		}
		//2.保存到服务端
		fileStorePath := path.Join("./upload/", fileObj.Filename)
		err = c.SaveUploadedFile(fileObj, fileStorePath)
		if err != nil {
			errMsg := fmt.Sprintf("文件保存失败:%s\n", err.Error())
			c.JSON(200, gin.H{"err": errMsg})
		}
		c.JSON(200, gin.H{"data": "上传成功"})
	}
}

func main() {
	router := gin.Default()
	router.Static("/static", "./static")
	router.LoadHTMLGlob("templates/*")
	router.GET("/file/", handleFile)
	router.POST("/file/", handleFile)
	err := router.Run(":8002")
	if err != nil {
		fmt.Println("gin启动失败", err)
		return
	}
}

 

多个文件上传

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

  

 

Gin模板渲染 

现在大部分都是前后端分离的架构,除了seo优化我们基本不会使用gin做模板渲染。

 

1.扩展gin模板函数

package main

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

func index(c *gin.Context) {
    //3.gin 模板渲染
    c.HTML(200, "index.html", gin.H{"title": "首页", "name": "Martin", "age": "hello", "url": `<a href="https://www.cnblogs.com/sss4/">主页</a>`})

}

func main() {
    //1.创建1个默认的路由引擎
    router := gin.Default()
    router.GET("/", index)
    //1.5 gin框架模板自定义模板函数
    router.SetFuncMap(template.FuncMap{
        "safe": func(safeString string) template.HTML {
            return template.HTML(safeString)
        },
    })
    //2.gin模板解析
    //router.LoadHTMLGlob("templates/*")    //正则匹配templates/所有文件
    router.LoadHTMLGlob("templates/**/*") //正则匹配template/目录/所有文件

    err := router.Run(":9001")
    if err != nil {
        fmt.Println("gin启动失败", err)
    }
}

模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.title}}</title>

</head>
<body>
<ul>
    <li>{{.name}}</li>
    <li>{{.age}}</li>
    <li>{{.url | safe}}</li>
</ul>
</body>
</html>

 

2.加载静态文件路径

package main

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

func index(c *gin.Context) {
	//5.gin 模板渲染
	c.HTML(200, "index.html", gin.H{"title": "首页", "name": "Martin", "age": "hello", "url": `<a href="https://www.cnblogs.com/sss4/">主页</a>`})

}

func main() {
	//1.创建1个默认的路由引擎
	router := gin.Default()
	router.GET("/", index)
	//2.加载静态文件路径 .css
	router.Static("/static","./static")
	//3. 扩展gin框架模板自定义模板函数
	router.SetFuncMap(template.FuncMap{
		"safe": func(safeString string) template.HTML {
			return template.HTML(safeString)
		},
	})
	//4.gin模板解析
	//router.LoadHTMLGlob("templates/*")    //正则匹配templates/所有文件
	router.LoadHTMLGlob("templates/**/*") //正则匹配template/目录/所有文件


	err := router.Run(":9001")
	if err != nil {
		fmt.Println("gin启动失败", err)
	}
}

 模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.title}}</title>
    <link rel="stylesheet" href="/static/dist/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <script src="/static/dist/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <table class="table table-hover">
        <theader>
            <tr>
                <td>姓名</td>
                <td>年龄</td>
                <td>主页</td>
            </tr>
        </theader>
        <tbody>
        <tr>
            <td>{{.name}}</td>
            <td>{{.age}}</td>
            <td>{{.url|safe}}</td>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

 

3.Gin模板继承

html/template实现了模板的嵌套和继承,但是gin不包含此功能。但是我们使用第第三方包。github.com/gin-contrib/multitemplate

 

Gin 路由组

URL路由太多了就需要分组管理,类似Flask的蓝图、Django里面的include URL。这些都是基于反射实现的。

但是Gin框架中的路由使用的是httprouter这个库,其基本原理就是构造一个路由地址的前缀树

1.单支路由

//所有请求方式都汇聚到handleBook
router.Any("/book/", handleBook)
//处理404错误
router.NoRoute(handle404)

 

2.路由组

    //cmdb路由组
    cmdbRouter := router.Group("/cmdb")
    {
        cmdbRouter.GET("/list/")
        cmdbRouter.GET("/hosts/")
        cmdbRouter.GET("/option/")
    }
    //工单路由组
    workOrder := router.Group("/works")
    {
        workOrder.GET("/daily/")
        cmdbRouter.GET("/momthly")
        cmdbRouter.GET("/quarterly")
    }

 

3.路由嵌套

虽然gin的路由支持嵌套,但是出于对查询性能的考虑我们一般都会不会嵌套很多层路由。

//cmdb路由组
    cmdbRouter := router.Group("/cmdb")
    {
        cmdbRouter.GET("/list/")
        cmdbRouter.GET("/hosts/")
        //1.cmdb的主机
        hostRouter := cmdbRouter.Group("/host")
        {
            //1.1主机的cpu
            hostRouter.GET("/cup/")
            //1.2主机的内存
            hostRouter.GET("/memory/")
            //1.3主机的硬盘
            hostRouter.GET("/disks/")
            //1.4主机运行的服务
            hostRouter.GET("/process/")
            //1.5网络流量
            hostRouter.GET("/networks/")

        }

        cmdbRouter.GET("/option/")
    }
    //2.工单路由组
    workOrder := router.Group("/works")
    {
        workOrder.GET("/daily/")
        cmdbRouter.GET("/momthly")
        cmdbRouter.GET("/quarterly")
    }

 

Gin中间件

我们可以在不修改视图函数的前提下,利用Web框架中携带的钩子函数也就是中间件 做权限控制、登录认证、权限校验、数据分页、记录日志、耗时统计.........

注意我们的中间件不仅可以设置1个,也根据我们的业务逻辑设置N个,相当于对用户请求增加了多层过滤。

就像Python里面的多层装饰器。

 

1.中间件执行流程

 

Golang的web框架之Gin

 

由于http请求包含request、response 2个动作所以中间件是双行线,中间件的执行流程就像1个递归函数的执行过程

压栈: 用户---------> 认证中间件---------> 用户权限中间件---------> 错误处理中间件---------> 视图函数执行

出栈: 视图函数执行完毕---------> 错误处理中间件---------> 用户权限中间件---------> 认证中间件---------> 用户

 

2.控制中间件执行流程

所为的控制流程我感觉就是设计中间件这个栈里面包含的层层栈针。

我们在弹匣里装了什么样的子弹,扣动扳机时就会发射出什么子弹,这样想会更简单一些否则很容易被绕进去。

在中间件执行的过程中我们可以控制进栈和出栈流程。

 Golang的web框架之Gin

 

以上代码执行结果:

m1 in
m2 in
m1 out

 

调用context.Next(),继续调用下一个视图函数进行压栈。(子弹装满弹匣)

调用context.Abort() 阻止继续调用后续的函数,执行完当前栈针(函数)之后出栈。(1发子弹就够了)

调用context.Abort() + return,当前位置返回,当前位置之后的代码都不需要不执行了。(1发哑弹)

 

package main

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

//中间件1
func middleWare1(c *gin.Context) {
	fmt.Println("middleWare1开始----------")
	c.Next() //调用后续的处理函数
	fmt.Println("middleWare1结束----------")

}

//中间件2
func middleWare2(c *gin.Context) {
	fmt.Println("middleWare2开始========")
	c.Abort()//终止后续处理函数的调用,执行完本函数返回
	return  //更极端一些 到这就结束!(本函数也不需要执行完毕了)。
	fmt.Println("middleWare2结束========")
}

//index视图函数
func index(c *gin.Context) {
	fmt.Println("index开始+++++++++")
	c.JSON(200, gin.H{"data": "ok"})
	fmt.Println("index结束+++++++++")
}

func main() {
	router := gin.Default()
	//全局注册中间件:middleWare1, middleWare2
	router.Use(middleWare1, middleWare2)
	router.GET("/index/", index)
	err := router.Run(":9001")
	if err != nil {
		fmt.Println("Gin启动失败", err)
	}

}

  

3.给单个路由(url)设置中间件

当我们需要对特定的视图函数增加新功能时,可以给它增加1个中间件。

package main

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

//中间件1
func middleWare1(c *gin.Context) {
    fmt.Println("--------------I`m going through middleWare1----------")
    start := time.Now()
    c.Next() //调用后续的处理函数
    cost := time.Since(start)
    fmt.Printf("耗时----------%v\n", cost)
    c.Abort() //终止请求
}

//index handlerfunc类型的函数
func index(c *gin.Context) {
    fmt.Println("--------------I`m going through handlerfunc----------")
    c.JSON(200, gin.H{"data": "ok"})
    c.Next()

}

//中间件2
func middleWare2(c *gin.Context) {
    fmt.Println("--------------I`m going through middleWare2----------")
    c.Next()
    c.Abort() //请求终止
}

func main() {
    router := gin.Default()
    //设置中间件流程:middleWare1-----》index----》middleWare2
    router.GET("/index/", middleWare1, index, middleWare2)
    err := router.Run(":9001")
    if err != nil {
        fmt.Println("Gin启动失败", err)
    }

}

 

4.全局注册中间件

如果我们需要每个视图函数都设置1个中间件,把这一中间件写到每个视图函数前面会非常不方便,我们可以使用use进行全局注册。

package main

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

//中间件1
func middleWare1(c *gin.Context) {
    fmt.Println("middleWare1开始----------")
    c.Next() //调用后续的处理函数
    fmt.Println("middleWare1结束----------")
    //c.Abort() //终止请求
}

//中间件2
func middleWare2(c *gin.Context) {
    fmt.Println("middleWare2开始========")
    c.Next()
    fmt.Println("middleWare2结束========")
}

//index handlerfunc类型的函数
func index(c *gin.Context) {
    fmt.Println("index开始+++++++++")
    c.JSON(200, gin.H{"data": "ok"})
    fmt.Println("index结束+++++++++")
}

func main() {
    router := gin.Default()
    //全局注册中间件:middleWare1, middleWare2
    router.Use(middleWare1, middleWare2)
    router.GET("/index/", index)
    err := router.Run(":9001")
    if err != nil {
        fmt.Println("Gin启动失败", err)
    }

}

输出:

验证web框架里中间件设计思想是的递归思想。

middleWare1开始----------
middleWare2开始========
index开始+++++++++
index结束+++++++++
middleWare2结束========
middleWare1结束----------

 

5.路由组注册中间件

给路由组注册中间件有2种写法

写法1:

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

 

写法2:

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

 

6.闭包的中间件

以上我们得知:Gin的中间件是以1种gin.HandlerFunc类型存在,在路由和路由组里进行注册。

router.GET("/index/", authMiddleWare(false), index)

那我们可以使用闭包将1个开关参数和这个handlerFunc一起包起来。实现对中间进行开关控制比较灵活。

package main

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

//使用闭包函数返回,gin.HandlerFunc。可以实现对中间进行开关控制,比较灵活。
func authMiddleWare(work bool) gin.HandlerFunc {
	if work{
		//连接数据库
		//其他准备工作
		dbDB := "Martin"
		return func(c *gin.Context) {
			username := c.Query("username")
			if username == dbDB {
				c.Next()
			} else {
				c.Abort()
				c.JSON(403, gin.H{"data": "没有访问权限"})
			}

		}
	}
	return func(context *gin.Context) {
	}
}

//index视图函数
func index(c *gin.Context) {
	fmt.Println("index视图函数开始")
	c.JSON(200, gin.H{"data": "ok"})
	fmt.Println("index视图函数结束")

}

func main() {
	router := gin.Default()
	router.GET("/index/", authMiddleWare(false), index)
	err := router.Run(":9001")
	if err != nil {
		fmt.Println("Gin启动失败", err)
	}

}

  

7.夸中间件进行传值

中间件可以有多层,假如我们上游的中间得出的值,如何传递到下游中间件呢?。通过上下文content。

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。以保证我们传递的值是一致的。

c.Set("username", username)
currentUser, ok := c.Get("username")

 

8.gin默认中间件

gin.Defaut生成的路由引擎,默认使用了Logger(), Recovery()的中间件。

//生成的路由引擎,默认使用了Logger(), Recovery()的中间件
 gin.Default()
router :
= gin.New()

Logger:用于记录日志

Recovery:用于保证在gin 发生错误时进程不会终止。

package main

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

//使用闭包函数返回,gin.HandlerFunc。可以实现对中间进行开关控制,比较灵活。
func authMiddleWare(work bool) gin.HandlerFunc {
    if work {
        //连接数据库
        //其他准备工作
        dbDB := "Martin"
        return func(c *gin.Context) {
            username := c.Query("username")
            if username == dbDB {
                //1.在中间中设置值进行传递
                c.Set("username", username)
                fmt.Println("-----------", username)
                c.Next()
            } else {
                c.Abort()
                c.JSON(403, gin.H{"data": "没有访问权限"})
            }

        }
    }
    return func(context *gin.Context) {
    }
}

//index视图函数
func index(c *gin.Context) {
    //2.获取上流传递的值
    currentUser, ok := c.Get("username")
    if !ok {
        currentUser = "anonymous"
    }
    fmt.Println("index视图函数开始")
    c.JSON(200, gin.H{"data": currentUser})
    fmt.Println("index视图函数结束")

}

func main() {
    //生成的路由引擎,默认使用了Logger(), Recovery()的中间件
    gin.Default()
    //router := gin.Default()
    router := gin.New()
    router.GET("/index/", authMiddleWare(true), index)
    err := router.Run(":9001")
    if err != nil {
        fmt.Println("Gin启动失败", err)
    }

}
代码

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-10-09
  • 2021-07-25
  • 2021-12-15
  • 2022-01-24
  • 2021-12-21
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2021-06-27
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案