路由组
- 用来管理有相同的URL的路由
使用示例:
package main import ( "fmt" "github.com/gin-gonic/gin" ) // 路由组 func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // 路由组1 ,处理GET请求 v1 := r.Group("/v1") // {} 是书写规范 { v1.GET("/login", login) v1.GET("submit", submit) } v2 := r.Group("/v2") { v2.POST("/login", login) v2.POST("/submit", submit) } r.Run(":8000") } func login(c *gin.Context) { name := c.DefaultQuery("name", "jack") c.String(200, fmt.Sprintf("hello %s\n", name)) } func submit(c *gin.Context) { name := c.DefaultQuery("name", "lily") c.String(200, fmt.Sprintf("hello %s\n", name)) }
访问失败页面
使用r.NoRoute来设置
例如:
r.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "sorry,天太冷了,页面跑去钻小被窝了") })
结果:
gin路由原理
- 我们知道gin的路由是基于httprouter的,所以httprouter中路由的原理也就是gin路由的原理
- httprouter路由原理请参考:简书-Root_808c的httprouter路由原理
gin数据解析和绑定
Json数据解析和绑定
- 客户端传参,Json格式,服务端解析到结构体
- 使用gin.Context.ShouldBindJSON(&json) err将接收到的json数据解析到结构体中
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"` Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
- 解析中binding中的required该字段表示必须传参,如果不传参则会报错
表单数据解析和绑定
- 使用gin.Context.Bind(&form) err 将接收到的表单数据解析到结构体中
··<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>表单提交数据解析到结构体</title> </head> <body> <form action="http://localhost:8000/loginForm" method="post" enctype="application/x-www-form-urlencoded"> 用户名:<input type="text" name="username"><br> 密 码:<input type="password" name="password"><br> <input type="submit" value="提交"> </form> </body> </html>
package main import ( "github.com/gin-gonic/gin" "net/http" ) type Login struct { // binding:"required"修饰的字段,若接收为空值,则报错,是必须字段 User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"` Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"` } func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // JSON绑定 r.POST("/loginForm", func(c *gin.Context) { // 声明接收的变量 var form Login // Bind()默认解析并绑定form格式 // 根据请求头中content-type自动推断 if err := c.Bind(&form); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 判断用户名密码是否正确 if form.User != "root" || form.Pssword != "admin" { c.JSON(http.StatusBadRequest, gin.H{"status": http.StatusBadRequest}) return } c.JSON(http.StatusOK, gin.H{"status": http.StatusOK,"body":form}) }) r.Run(":8000") }
结果:
URI数据解析和绑定
- 使用gin.Context.ShouldBindUri(&login) err 解析uri数据到结构体中
示例:
func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // JSON绑定 r.GET("/:user/:password", func(c *gin.Context) { // 声明接收的变量 var login Login // Bind()默认解析并绑定form格式 // 根据请求头中content-type自动推断 if err := c.ShouldBindUri(&login); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 判断用户名密码是否正确 if login.User != "root" || login.Pssword != "admin" { c.JSON(http.StatusBadRequest, gin.H{"status": "304"}) return } c.JSON(http.StatusOK, gin.H{"status": "200"}) }) r.Run(":8000") }
结果:
gin 渲染
各种数据格式的响应
- json、结构体、XML、YAML类似于java的properties、ProtoBuf
func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // 1.json r.GET("/someJSON", func(c *gin.Context) { c.JSON(200, gin.H{"message": "someJSON", "status": http.StatusOK}) }) // 2. 结构体响应 r.GET("/someStruct", func(c *gin.Context) { var msg struct { Name string Message string Number int } msg.Name = "root" msg.Message = "message" msg.Number = 123 c.JSON(http.StatusOK, msg) }) // 3.XML r.GET("/someXML", func(c *gin.Context) { c.XML(200, gin.H{"message": "abc"}) }) // 4.YAML响应 r.GET("/someYAML", func(c *gin.Context) { c.YAML(200, gin.H{"name": "zhangsan"}) }) // 5.protobuf格式,谷歌开发的高效存储读取的工具 // 数组?切片?如果自己构建一个传输格式,应该是什么格式? //r.GET("/someProtoBuf", func(c *gin.Context) { // reps := []int64{int64(1), int64(2)} // // 定义数据 // label := "label" // // 传protobuf格式数据 // data := &protoexample.Test{ // Label: &label, // Reps: reps, // } // c.ProtoBuf(200, data) //}) r.Run(":8000") }
XML页面结果:
HTML模板渲染
- gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据,本质上就是字符串替换
- LoadHTMLGlob()方法可以加载模板文件
html文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{.title}}</title> </head> <body> <h1>{{.test}}</h1> </body> </html>
go文件:
package main import ( "github.com/gin-gonic/gin" "net/http" ) // html模板渲染 func main() { r := gin.Default() // 加载模板文件 r.LoadHTMLGlob("static/*") // 如果项目结构不同,也可以是 // r.LoadHTMLGlob("static/**/*") r.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是测试", "test": "这是一个测试文件"}) }) r.Run(":8000") }
结果:
重定向
- 使用 gin.Context.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, "http://www.baidu.com") }) r.Run(":8000") }
同步异步
- 同步:先执行再响应
- 异步:先响应再执行
- go的协程机制(goroutine)可以方便地实现异步处理
- 另外,在启动新的goroutine时,不应该使用原始上下文,必须使用它的只读副本
package main import ( "github.com/gin-gonic/gin" "log" "time" ) // 同步异步 func main() { // 1.创建路由 // 默认使用了2个中间件Logger(), Recovery() r := gin.Default() // 1.异步 r.GET("/long_async", func(c *gin.Context) { // 需要搞一个副本 copyContext := c.Copy() // 异步处理 go func() { time.Sleep(30 * time.Second) log.Println("异步执行:" + copyContext.Request.URL.Path) }() }) // 2.同步 r.GET("/long_sync", func(c *gin.Context) { time.Sleep(30 * time.Second) log.Println("同步执行:" + c.Request.URL.Path) }) r.Run(":8000") }
结果: