一、Gin框架介绍
1.1 简介
A. 基于httprouter开发的web框架。 http://github.com/julienschmidt/httprouter
B. 提供Martini风格的API,但比Martini要快40倍
C. 非常轻量级,使用起来非常简洁
1.2 Gin框架的安装与使用
A. 安装: go get -u github.com/gin-gonic/gin
B. import "go get -u github.com/gin-gonic/gin"
实例1:
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() //Default返回一个默认的路由引擎 r.GET("/ping", func(c *gin.Context) { //c这里就是封装了go底层web的r和w c.JSON(200, gin.H{ //200是状态码 "message": "pong", }) //输出json结果给调用方 }) r.Run() // listen and serve on 0.0.0.0:8080 }
执行结果:
浏览器查看:
实例2:
package main import ( "net/http" "github.com/gin-gonic/gin" ) type Result struct { Message string `json:"message"` Code int `json:"code"` } func handleUserInfo(c *gin.Context) { var result Result = Result{ //初始化1个map Message: "success", Code: 0, } c.JSON(http.StatusOK, result) //第二个参数是一个空接口,可以传递任何类型数据进来,这里传递进来的map会自动把我们转成json串 } func main() { r := gin.Default() r.GET("/user/info", handleUserInfo) r.Run(":9090") }
执行结果:
浏览器查看:
1.3 Restful风格的API
A. 把我们设计的API抽象成一个个资源,用URI来标识。
B. 针对每一个资源,使用统一的方法进行操作。
C. 同一的方法有:
a. GET,获取资源内容。
b. POST,创建资源。
c. PUT,更新资源。
d. DELETE,删除资源。
样例:
package main import "github.com/gin-gonic/gin" func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.GET("/ping", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.POST("/ping", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
1.4 实例1 restful风格的API 用户信息接口设计,资源就是 /user/info
样例:
package main import "github.com/gin-gonic/gin" func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.GET("/user/info", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.POST("/user/info", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.PUT("/user/info", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.DELETE("/user/info", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
1.5 实例2 用户信息接口设计,非restful风格的API
样例:
package main import "github.com/gin-gonic/gin" func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.GET("/user/info", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.POST("/user/create", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.POST("/user/delete", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.POST("/user/update", func(c *gin.Context) { //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
二、Gin框架参数传递
2.1 通过querystring传递, 比如: /user/search?username=少林&address=北京
实例1:
package main import "github.com/gin-gonic/gin" func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.GET("/user/search", func(c *gin.Context) { //username := c.DefaultQuery("username", "少林") username := c.Query("username") //通过query来获取用户传递过来的参数 address := c.Query("address") //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", "username": username, "address": address, }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
浏览器端查看:
实例2:
package main import ( "net/http" "github.com/gin-gonic/gin" ) type Result struct { Message string `json:"message"` Code int `json:"code"` } type UserInfo struct { Result UserName string `json:"username"` Passwd string `json:"passwd"` } func handleUserInfo(c *gin.Context) { username := c.Query("username") passwd := c.DefaultQuery("passwd", "dkdkdkdkdkdkdkd") //默认值,如果用户没有传递过来值,则为此处定义默认值 var result UserInfo = UserInfo{ UserName: username, Passwd: passwd, } result.Code = 0 result.Message = "success" c.JSON(http.StatusOK, result) } func main() { r := gin.Default() r.GET("/user/info", handleUserInfo) r.Run(":9090") }
执行结果:
浏览器端查看:
用户不传递参数:
用户传递参数:
2.2 通过路径传递, 比如: /user/search/少林/北京
实例1:
package main import "github.com/gin-gonic/gin" func main() { //Default返回一个默认的路由引擎 r := gin.Default() r.GET("/user/info/:username/:address", func(c *gin.Context) { //username := c.DefaultQuery("username", "少林") username := c.Param("username") //用param方法 address := c.Param("address") //输出json结果给调用方 c.JSON(200, gin.H{ "message": "pong", "username": username, "address": address, }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
执行结果:
浏览器端查看:
实例2:
package main import ( "net/http" "github.com/gin-gonic/gin" ) type Result struct { Message string `json:"message"` Code int `json:"code"` } type UserInfo struct { Result UserName string `json:"username"` Passwd string `json:"passwd"` } func handleUserParams(c *gin.Context) { username := c.Param("username") passwd := c.Param("passwd") var result UserInfo = UserInfo{ UserName: username, Passwd: passwd, } result.Code = 0 result.Message = "success" c.JSON(http.StatusOK, result) } func main() { r := gin.Default() r.GET("/user/info/:username/:passwd", handleUserParams) r.Run(":9090") }
执行结果:
浏览器查看:
2.3 通过表单进行提交, 比如: POST /user/search/
下载postman测试工具,测试api有很多也非常方便。
下载地址: https://www.getpostman.com/apps
代码实例如下:
package main import ( "net/http" "github.com/gin-gonic/gin" ) type Result struct { Message string `json:"message"` Code int `json:"code"` } type UserInfo struct { Result UserName string `json:"username"` Passwd string `json:"passwd"` } func handleUserInfo(c *gin.Context) { username := c.PostForm("username") //使用postform函数获取表单数据 passwd := c.PostForm("passwd") var result UserInfo = UserInfo{ UserName: username, Passwd: passwd, } result.Code = 0 result.Message = "success" c.JSON(http.StatusOK, result) } func main() { r := gin.Default() r.POST("/user/info", handleUserInfo) //选择post方法 r.Run(":9090") }
执行结果:
通过postman测试结果:
三、Gin框架处理文件上传
3.1 单个文件上传
package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) type Result struct { Message string `json:"message"` Code int `json:"code"` } type UserInfo struct { Result UserName string `json:"username"` Passwd string `json:"passwd"` } func handleUpload(c *gin.Context) { file, err := c.FormFile("testfile") //通过formfile方法来获取上传的文件,传入的参数是对上传文件的标识(可以随便起名),也就是要上传的文件的key if err != nil { fmt.Printf("upload file failed") return } filename := fmt.Sprintf("C:/tmp/%s", file.Filename) //文件保存位置 err = c.SaveUploadedFile(file, filename) //调用c将上传文件保存至指定位置 if err != nil { fmt.Printf("save file failed, err:%v ", err) return } c.JSON(http.StatusOK, "file upload success") //http.StatusOK就是返回状态码200 } func main() { r := gin.Default() r.POST("/file/upload", handleUpload) r.Run(":9090") }
执行结果:
postman执行结果:
保存文件查看结果:
3.2 多个文件上传
package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) type Result struct { Message string `json:"message"` Code int `json:"code"` } type UserInfo struct { Result UserName string `json:"username"` Passwd string `json:"passwd"` } func handleMultiUpload(c *gin.Context) { form, err := c.MultipartForm() //多个文件用multipartform方法 if err != nil { fmt.Printf("upload file failed") return } files := form.File["testfile"] //传入文件名的key,其返回值是一个数组 for _, file := range files { //遍历数组,即可把要传入的多个文件都保存起来了 filename := fmt.Sprintf("C:/tmp/%s", file.Filename) err = c.SaveUploadedFile(file, filename) if err != nil { fmt.Printf("save file failed, err:%v ", err) return } } c.JSON(http.StatusOK, "file upload success") } func main() { r := gin.Default() r.POST("/files/upload", handleMultiUpload) r.Run(":9090") }
执行结果:
postman执行结果:
保存文件查看结果:
四、Gin框架路由分组
路由分组做的就是把相同的前缀通过借助group方法实现分组。
实例:
package main import "github.com/gin-gonic/gin" func login(ctx *gin.Context) { ctx.JSON(200, gin.H{ "message": "success", }) } func submit(ctx *gin.Context) { ctx.JSON(200, gin.H{ "message": "success", }) } func main() { //Default返回一个默认的路由引擎 router := gin.Default() // Simple group: v1 v1 := router.Group("/v1") { v1.GET("/login", login) // 等价于/v1/login v1.POST("/submit", submit) } // Simple group: v2 v2 := router.Group("/v2") { v2.POST("/login", login) v2.POST("/submit", submit) //等价于/v2/submit } router.Run(":8080") }
执行结果:
验证测试:
五、Gin框架参数绑定(方便)
为什么要参数绑定,本质上是方便,提高开发效率
A. 通过反射的机制,自动提取querystring、 form表单、 json、 xml等参数到struct中
B. 通过http协议中的context type,识别是json、 xml或者表单
参考网址:
http://www.zhimengzhe.com/bianchengjiaocheng/qitabiancheng/28966.html
注意:
要绑定一个请求body到某个类型, 可以使用model binding。 目前支持JSON, XML 以及标准from格式 (foo=bar&boo=baz)的绑定。
所有你想要绑定的域(field), 需要你设置对应的绑定标识。 例如, 要绑定到JSON, 则这样声明json:"fieldname"。
使用Bind方法时, Gin会尝试通过Content-Type头部来推定绑定的类型(如json还是form)。而如果你明确知道要绑定的类型, 可以使用BindWith方法。
你也可以指定哪些filed需要绑定。 如果某个filed像这样声明: binding:"required", 那么在进行绑定时如果发现是空值(注: 是请求中不存在该field名?), 当前的请求会失败并收到错误提示。
实例1:
package main import ( "net/http" "github.com/gin-gonic/gin" ) type UserInfo struct { UserName string `form:"username" json:"username" binding:"required"` //form表单、json串 Passwd string `form:"passwd" json:"passwd" binding:"required"` Age int `form:"age" json:"age" binding:"required"` Sex string `form:"sex" json:"sex" binding:"required"` } //form func handleUserInfoForm(c *gin.Context) { var userInfo UserInfo err := c.ShouldBind(&userInfo) //直接将结构体的4个参数选项绑定过来,借助ShouldBind if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, userInfo) } } //json func handleUserInfoJson(c *gin.Context) { var userInfo UserInfo err := c.ShouldBindJSON(&userInfo) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, userInfo) } } //querystring func handleUserInfoQuery(c *gin.Context) { var userInfo UserInfo err := c.ShouldBind(&userInfo) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, userInfo) } } func main() { r := gin.Default() // /v1/user/login // /v1/user/login2gi v1Group := r.Group("/v1") v1Group.POST("/user/infoform", handleUserInfoForm) v1Group.POST("/user/infojson", handleUserInfoJson) v1Group.GET("/user/infoquerystring", handleUserInfoQuery) r.Run(":9090") }
执行结果:
测试结果:
form:
json:
Querystring:
六、Gin框架渲染
6.1 渲染json
gin.Context.JSON方法进行渲染
代码实例:
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // gin.H is a shortcut for map[string]interface{} r.GET("/someJSON", func(c *gin.Context) { //第一种方式,自己拼json c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) //gin.H就是一个map的别名,借助gin.H可以去拼接json }) //第二种:通过定义一个结构体,然后将结构体传给c.JSON,之后会将传递过来的结构体打包成支持json协议的字符串返回给客户端 r.GET("/moreJSON", func(c *gin.Context) { // You also can use a struct var msg struct { Name string `json:"user"` Message string Number int } msg.Name = "Lena" msg.Message = "hey" msg.Number = 123 // Note that msg.Name becomes "user" in the JSON c.JSON(http.StatusOK, msg) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
执行结果:
浏览器测试:
6.2 渲染xml
gin.Context.XML方法进行渲染
代码实例1:
package main import ( "github.com/gin-gonic/gin" ) type Result struct { Message string `json:"message"` Code int `json:"code"` } type UserInfo struct { Result UserName string `json:"username"` Passwd string `json:"passwd"` } func handleUserInfo(c *gin.Context) { var userInfo = &UserInfo{ UserName: "skkss", Passwd: "SSSS", } c.XML(200, userInfo) //将结构体传递给c.xml } func main() { r := gin.Default() r.GET("/user/info", handleUserInfo) r.Run(":9090") }
执行结果:
浏览器测试:
实例2:
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/moreXML", func(c *gin.Context) { // You also can use a struct type MessageRecord struct { Name string Message string Number int } var msg MessageRecord msg.Name = "Lena" msg.Message = "hey" msg.Number = 123 c.XML(http.StatusOK, msg) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
6.3 渲染模板
gin.Context.HTML方法进行渲染
代码实例目录结构:
posts.html:
{{ define "post" }} <!-- 用于生命要传递的名字,传递给go源码,相当于模板的key --> <html><h1> {{ . }} </h1> <p>gin.Context.HTML方法进行渲染</p> </html> {{ end }}
main.go:
package main import ( "net/http" "github.com/gin-gonic/gin" ) func handleHtml(c *gin.Context) { c.HTML(http.StatusOK, "post", "ksdkfskfkskfsdkfs") //post是html文件定义的名字 } func main() { r := gin.Default() r.LoadHTMLGlob("./templates/*") //将模板文件加载进来 r.GET("/user/info", handleHtml) r.Run(":9090") }
执行结果:
浏览器测试:
实例2:
import ( "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.LoadHTMLGlob("templates/**/*") //**代表所有子目录 *代表子目录下的所有模板 router.GET("/posts/index", func(c *gin.Context) { c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ "title": "Posts", }) }) router.GET("/users/index", func(c *gin.Context) { c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ "title": "Users", }) }) router.Run(":8080") }
七、 Gin框架中间件&路由原理
7.1 Gin框架中间件
A. Gin框架允许在请求处理过程中,加入用户自己的钩子函数。这个钩子函数就叫中间件
B. 因此,可以使用中间件处理一些公共业务逻辑,比如耗时统计,日志打印,登陆校验(不可能说每个api都写这些公共逻辑,我们可以将公共逻辑放到中间件中)
7.2 自己编写中间件
实例1:
package main import ( "fmt" "net/http" "time" "github.com/gin-gonic/gin" ) func StatCost(c *gin.Context) { //统计耗时函数,这就是一个中间件 start := time.Now() fmt.Printf("start stat cost ") c.Next() ////等中间件中其他函数先执行(也就是说执行完c.Next其他函数已经执行完了) lattancy := time.Since(start) //获取耗时 fmt.Printf("process request cost:%d ms ", lattancy/1000/1000) } func handleUserInfo(c *gin.Context) { //此请求处理函数会在中间件中进行处理,该函数执行完,才会执行c.Next fmt.Printf("request start process ") time.Sleep(3 * time.Second) c.JSON(http.StatusOK, "38333k333") } func main() { r := gin.Default() r.Use(StatCost) //将中间件函数设置(借助use函数)到gin框架,这样gin框架在处理请求时,就会先调用中间件进行处理 r.GET("/user/info", handleUserInfo) r.Run(":8080") }
执行结果:
浏览器测试:
实例2:
package main import ( "log" "net/http" "time" "github.com/gin-gonic/gin" ) func StatCost() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() //可以设置一些公共参数 c.Set("example", "12345") //等其他中间件先执行 c.Next() //获取耗时 latency := time.Since(t) log.Print(latency) } } func main() { r := gin.New() r.Use(StatCost()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) // it would print: "12345" log.Println(example) c.JSON(http.StatusOK, gin.H{ "message": "success", }) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
7.3 Gin框架路由介绍
A. 路由部分用的是: http://github.com/julienschmidt/httprouter
B. 对于所有的路由规则, httprouter会构造一颗前缀树
httprouter官网例子:
package main import "github.com/gin-gonic/gin" func index(ctx *gin.Context) { ctx.JSON(200, gin.H{ "message": "index", }) } func main() { //Default返回一个默认的路由引擎 router := gin.Default() router.POST("/", index) router.POST("/search", index) router.POST("/support", index) router.POST("/blog/:post", index) router.POST("/about", index) router.POST("/contact", index) router.Run(":8080") }
生成的前缀数如下所示: