前言
Gin是一款基于Go语言开发的轻量级、高性能WebHTTP框架,API风格兼容Martini框架,但性能实现跨越式提升。
Gin底层采用httprouter路由库,路由执行速度相较传统框架提升近40倍,是Go生态中追求高性能、高并发、高效率的首选Web框架。
作为目前 Go 语言社区最流行、使用最广泛的Web 框架。
Gin GitHub 上收获32K+ Star,生态成熟、中文文档完善、学习成本极低,是构建微服务、API 服务、后台管理系统的主流选择。
Gin依赖包下载
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") }
请求头
// 获取请求头 Origin c.GetHeader("X-Token") //设置请求头 c.Request.Header.Set("X-Token", "新值")
响应头
// 获取响应头 c.Writer.Header().Get("Access-Control-Allow-Origin") // 设置响应头 c.Writer.Header().Set("Access-Control-Allow-Origin", "...")
请求体
我们可以通过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,把requestBody中数据自动绑定到后台Struct的字段,但不支持重复绑定。
BindXXX:会自动返回信息,输入无效时,在header写入状态码=400。
ShouldBindXXX:返回消息,输入无效时,不会在header写入400状态码,这时候可以自定义返回信息,在使用上相对来说更加灵活。
ShouldBindWith:
在gin 1.4 之前,重复使用ShouldBind绑定会报错EOF。
gin 1.4 之后官方提供了一个 ShouldBindBodyWith 的方法,可以支持重复绑定,原理就是将body的数据缓存了下来,但是二次取数据的时候还是得用 ShouldBindBodyWith 才行,直接用 ShouldBind 还是会报错的。
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")
}
自定义验证的错误信息
对前端传入的参数进行校验,当验证不通过时,会给出错误的信息。
package main import ( "fmt" "reflect" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" ) type LoginDto struct { Username string `json:"username" binding:"required" msg:"用户名不能为空"` Password string `json:"password" binding:"min=3,max=6" msg:"密码长度不能小于3大于6"` Email string `json:"email" binding:"email" msg:"邮箱地址格式不正确"` } func GetValidMsg(err error, obj interface{}) string { fmt.Println("sherlock") getObj := reflect.TypeOf(obj) if errs, ok := err.(validator.ValidationErrors); ok { for _, e := range errs { if f, exist := getObj.Elem().FieldByName(e.Field()); exist { return f.Tag.Get("msg") } } } return err.Error() } func main() { r := gin.Default() r.POST("/login", func(c *gin.Context) { userDto := &LoginDto{} if err := c.ShouldBindJSON(userDto); err != nil { c.JSON(200, gin.H{ "status": "err", "message": GetValidMsg(err, userDto), }) } else { c.JSON(200, gin.H{ "status": "change", "message": userDto, }) } }) r.Run(":8080") }
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中间件
中间件就像写字楼里的闸机,刷卡进入------>工作------>出去也得刷卡。
当请求进入服务时,中间件第一时间生成唯一的 TraceID,将其注入请求上下文、日志、响应头。
让整条请求链路拥有统一标识,实现浏览器请求跨域、全链路追踪、问题排查、流量染色等功能。
中间件执行流程

执行流程

调用context.Next(),继续调用下一个视图函数进行压栈。(子弹装满弹匣)
调用context.Abort() 阻止继续调用后续的函数,执行完当前栈针(函数)之后出栈。(1发子弹就够了)
调用context.Abort() + return,当前位置返回,当前位置之后的代码都不需要不执行了。(1发哑弹)
2.全局和局部中间间
全局中间件(全国宪法)
在根路由直接通过 router.Use() 注册,作用于项目所有接口、所有路由分组。
无论后续划分多少子路由组,请求都会优先执行全局中间件。
局部中间件(地方政策)
局部中间件在子路由组通过 group.Use() 注册,仅当前路由组下的接口生效。
不会影响其他独立路由组,但依然会先走全局中间件,再执行当前组的局部中间件。

浙公网安备 33010602011771号