Gin是一个用Go编写的HTTPweb框架。它是一个类似于martini但拥有更好性能的API框架, 优于httprouter,速度提高了近 40 倍。 点击此处访问Gin官方中文文档。
go get -u github.com/gin-gonic/gin
import "github.com/gin-gonic/gin"
新建文件main.go,内容如下:
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // 1.创建路由 r := gin.Default() // 2.绑定路由规则,执行的函数 // gin.Context,封装了request和response r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "hello World!") }) // 3.监听端口,默认在8080 // Run("")里面不指定端口号默认为8080 r.Run(":8000") }
运行后访问: http://localhost:8000/
1、渲染html模板
Gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据,本质上就是字符串替换。LoadHTMLGlob()方法可以加载模板文件(参数为待渲染的html模板文件,如果参数为相对路径则为运行路径的相对路径)。
a、 渲染单个文件
r.LoadHTMLGlob("web/index.html")
b、 渲染文件夹下的所有文件
r.LoadHTMLGlob("web/*")
c、 渲染文件夹下的所有html后缀的文件
比如:
r.LoadHTMLGlob("web/*.html")
2、定义模板分割
r.Delims("<<<", ">>>")
第一个参数为:模板标签开始标记
第二个参数为:模板标签结束标记
如果你需要引入静态文件需要定义一个静态文件目录
r.Static("/assets", "./static/assets")
若assets的目录结构为
可以根据 http://localhost:8000/assets/img/home.jpg 访问指定资源
如果r为路由组,则需要在assets前拼接路由组的路径包括其前缀
// 重定向两种默认应支持的首页访问方式 router.GET("/", func(c *gin.Context) { //重定向到/index.html c.Redirect(302, "/index.html") }) router.GET("/index", func(c *gin.Context) { //重定向到/index.html c.Redirect(302, "/index.html") }) router.GET("/index.html", func(c *gin.Context) { //返回渲染的html模板中的index.html c.HTML(http.StatusOK, "index.html", gin.H{ "baseUrl": "http://" + host, }) })
Next()
r.Use(Next())
允许跨域
// 允许跨域 func Next() gin.HandlerFunc { return func(c *gin.Context) { method := c.Request.Method c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Headers", "Access-Control-Allow-Headers,Authorization,User-Agent, Keep-Alive, Content-Type, X-Requested-With,X-CSRF-Token,AccessToken,Token") c.Header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, OPTIONS") c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") c.Header("Access-Control-Allow-Credentials", "true") // 放行所有OPTIONS方法 if method == "OPTIONS" { c.AbortWithStatus(http.StatusAccepted) } c.Next() } }
(1)返回一个字符串
r.GET("/news", func(c *gin.Context) { aid := c.Query("aid") c.String(200, "aid=%s", aid) })
(2)返回一个 JSON 数据
//方法一:自己拼接JSON r.GET("/json", func(c *gin.Context) { //返回json数据,使用 map[string]interface //c.JSON(返回的状态码, 任意类型的数据(如:map,struct,...) c.JSON(200, map[string]interface{}{ "success": true, "msg": "你好", }) }) //方法二:gin中的H函数 r.GET("/json2", func(c *gin.Context) { //返回json数据,使用gin中的H函数, gin.H 是 map[string]interface{}的缩写 c.JSON(200, gin.H{ "success": true, "msg": "你好gin", }) }) //方法三:使用结构体 r.GET("/json3", func(c *gin.Context) { //实例化一个结构体 a := &Article{ Title: "标题", Desc: "说明", Content: "内容", } c.JSON(200, a) })
JSOPN: jsonp请求 主要用来解决跨域问题
//jsonp请求 主要用来解决跨域问题 //http://127.0.0.1:8080/jsonp?callback=call //call({"title":"标题-jsonp","desc":"说明-jsonp","content":"内容-jsonp"}); r.GET("/jsonp", func(c *gin.Context) { //实例化一个结构体 a := &Article{ Title: "标题-jsonp", Desc: "说明-jsonp", Content: "内容-jsonp", } c.JSONP(200, a) })
(3)返回 XML 数据
//方法一:使用gin.H返回 r.GET("/xml", func(c *gin.Context) { c.XML(http.StatusOK, gin.H{ "success": true, "msg": "成功xml", }) }) //方法二:使用结构体 r.GET("/xmlStruct", func(c *gin.Context) { //实例化一个结构体 a := &Article{ Title: "标题-xmlStruct", Desc: "说明-xmlStruct", Content: "内容-xmlStruct", } c.XML(200, a) })
(4)返回HTML数据
//初始化路由 r := gin.Default() //加载templates文件中所有模板文件,以便后续c.HTML()渲染文件时使用 r.LoadHTMLGlob("templates/*") r.GET("/news", func(c *gin.Context) { //使用模板文件渲染HTML文件 //前提: r.LoadHTMLGlob("templates/*") //HTML(状态码, 要渲染的文件名, 加载的参数) c.HTML(http.StatusOK, "news.html", gin.H{ "title": "我是一个news", }) })
完整代码案例如下:
package main import ( "github.com/gin-gonic/gin" "net/http" ) type Article struct { Title string `json:"title"` Desc string `json:"desc"` Content string `json:"content"` } func main() { //初始化路由 r := gin.Default() //加载templates文件中所有模板文件,以便后续c.HTML()渲染文件时使用 r.LoadHTMLGlob("templates/*") //配置路由 r.GET("/", func(c *gin.Context) { c.String(200, "首页") }) r.GET("/json", func(c *gin.Context) { //返回json数据,使用 map[string]interface //c.JSON(返回的状态码, 任意类型的数据(如:map,struct,...) c.JSON(200, map[string]interface{}{ "success": true, "msg": "你好", }) }) r.GET("/json2", func(c *gin.Context) { //返回json数据,使用gin中的H函数 c.JSON(200, gin.H{ "success": true, "msg": "你好gin", }) }) r.GET("/json3", func(c *gin.Context) { //实例化一个结构体 a := &Article{ Title: "标题", Desc: "说明", Content: "内容", } c.JSON(200, a) }) //jsonp请求 主要用来解决跨域问题 //http://127.0.0.1:8080/jsonp?callback=call //call({"title":"标题-jsonp","desc":"说明-jsonp","content":"内容-jsonp"}); r.GET("/jsonp", func(c *gin.Context) { //实例化一个结构体 a := &Article{ Title: "标题-jsonp", Desc: "说明-jsonp", Content: "内容-jsonp", } c.JSONP(200, a) }) r.GET("/xml", func(c *gin.Context) { c.XML(http.StatusOK, gin.H{ "success": true, "msg": "成功xml", }) }) r.GET("/news", func(c *gin.Context) { //使用模板文件渲染HTML文件 //前提: r.LoadHTMLGlob("templates/*") //HTML(状态码, 要渲染的文件名, 加载的参数) c.HTML(http.StatusOK, "news.html", gin.H{ "title": "我是一个news", }) }) r.GET("/goods", func(c *gin.Context) { //使用模板文件渲染HTML文件 //前提: r.LoadHTMLGlob("templates/*") //HTML(状态码, 要渲染的文件名, 加载的参数) c.HTML(http.StatusOK, "goods.html", gin.H{ "title": "我是一个goods", "price": 12.99, }) }) r.Run() // 启动一个web服务 }
// 创建路由组 // 根据需要,可以为这种多级的路由组:r.Group("/v1/user") userApi:= r.Group("/user") // 创建用户 // 匹配POST请求的 /user userApi.POST("", userCreate) // 修改用户 // 匹配PUT请求的 /user/1 但不会匹配 /user/ 或者 /user userApi.PUT("/:id", userUpdate) // 获取用户 // 匹配GET请求的 /user/1 但不会匹配 /user/ 或者 /user userApi.GET("/:id", userGet) // 查询用户 // 匹配GET请求的 /user/list userApi.GET("/list", userQuery) // 删除用户 // 匹配DELETE请求的 /user/1 但不会匹配 /user/ 或者 /user userApi.DELETE("/:id", userDelete)
Restful风格的API
通过Context的Param方法来获取api参数
userApi.GET("/:id/:name", userGet)
第一个参数:获取url路径参数id和name的值
第二个参数:userGet函数
func userGet(ctx *gin.Context) { //api参数可以为单个或者多个也可以拼接多级 //ctx.Param()函数获取时参数需要与api中的名称一致才能获取到 id := ctx.Param("id") name:= ctx.Param("name") ctx.JSON(http.StatusOK, gin.H{"data": id,"name":name}) return }
http://localhost:8000/user/1/admin
通过Context的Query方法与DefaultQuery方法来获取url参数
userApi.GET("/list", userQuery)
userQuery方法
func userQuery(ctx *gin.Context) { //获取 id := ctx.Query("id") //获取,第二个参数为获取为空的默认值,如果参数不存在则放回第二个参数 name := ctx.DefaultQuery("name", "user") ctx.JSON(http.StatusOK, gin.H{"data": id, "name": name}) return }
备注:默认值为当客户端没有在请求中带这个参数,服务端将取name默认值为”user“
可以根据 http://localhost:8000/user/list 后面拼接查询参数,访问此接口
客户端没有有传参数,则取默认值
客户端有传参数,则不取默认值
表单参数测试与观察请求效果需要安装postman
通过Context的PostForm方法来获取表单参数
userApi.POST("", userCreate)
userCreate函数为
func userCreate(ctx *gin.Context) { id := ctx.PostForm("id") name := ctx.PostForm("name") ctx.JSON(http.StatusOK, gin.H{"data": id, "name": name}) return }
如图,访问此接口
json参数测试与观察请求效果需要安装postman
通过Context的GetRawData或者ShouldBindJSON方法来获取表单参数
userApi.PUT("/:id", userUpdate)
userUpdate函数为
GetRawData方法
func userUpdate(ctx *gin.Context) { b, err := ctx.GetRawData() // 从ctx.Request.Body读取请求数据 if err !=nil{ fmt.print(err) } // 定义map或结构体 var m map[string]interface{} // 反序列化 _ = json.Unmarshal(b, &m) ctx.JSON(http.StatusOK, gin.H{"data": m["id"], "name": m["name"]}) return }
ShouldBindJSON方法
// 先定义结构 type User struct { Id string `form:"id" json:"id" binding:"required"` Name string `form:"name" json:"name" binding:"required"` } // 函数实现 func userUpdate(ctx *gin.Context) { var user User if err := ctx.ShouldBindJSON(&user); err == nil { ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name}) return } else { ctx.JSON(http.StatusOK, gin.H{"err": err.Error()}) return } }
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中form表单、JSON、XML等参数到结构体中。
代码示例:
定义要获取的结构
type User struct { Id string `form:"id" json:"id" binding:"required"` Name string `form:"name" json:"name" binding:"required"` }
userCreate函数
func userCreate(ctx *gin.Context) { // 实例化结构体对象 var user User if err := ctx.ShouldBind(&user); err == nil { // 响应体 json 格式 ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name}) return }else{ ctx.JSON(http.StatusOK, gin.H{"err": err.Error()}) return } }
userUpdate函数
func userUpdate(ctx *gin.Context) { // 实例化结构体对象 var user User if err := ctx.ShouldBind(&user); err == nil { ctx.JSON(http.StatusOK, gin.H{"data": user.Id, "name": user.Name}) return }else{ ctx.JSON(http.StatusOK, gin.H{"err": err.Error()}) return } }
一般都是post请求表单传参
userApi.POST("/upload", userUpload)
userUpload函数
// 先定义结构 type FileUpload struct { File *multipart.FileHeader `form:"file"` Type string `form:"type"` } // 函数实现 func userUpload(ctx *gin.Context) { var fileUpload FileUpload // if err := ctx.ShouldBind(&fileUpload); err == nil { //获取运行路径 ex, err := os.Executable() // if err != nil { ctx.JSON(http.StatusOK, gin.H{"err": err.Error()}) return } //定义接收文件的存放地址 path := filepath.Dir(ex) + string(os.PathSeparator) + fileUpload.File.Filename //接收文件并保存到指定path err = ctx.SaveUploadedFile(fileUpload.File, path) ctx.JSON(http.StatusOK, gin.H{"data": path, "type": fileUpload.Type}) return } else { ctx.JSON(http.StatusOK, gin.H{"err": err.Error()}) return } }
使用Gin框架写Go项目的时候,如果把路由都写在一个.go文件中,随着路由的数量的增加,会导致路由文件显得异常臃肿。
此时需要对路由进行分组并且分文件管理,更有利于代码的组织和维护
使用Gin框架写Go项目的时候,如果把路由都写在一个.go文件中,随着路由的数量的增加,会导致路由文件显得异常臃肿。
此时需要对路由进行分组并且分文件管理,更有利于代码的组织和维护
func main() { router := gin.Default() router.POST("/one/a", OneAFunc) router.POST("/one/b", OneBFunc) router.POST("/one/c", OneCFunc) router.POST("/one/d", OneDFunc) router.POST("/one/e", OneEFunc) router.POST("/one/f", OneFFunc) router.POST("/one/g", OneGFunc) router.POST("/one/h", OneHFunc) router.POST("/one/i", OneIFunc) /* 省略n条路由 */ router.POST("/one/x", OneXFunc) router.POST("/one/y", OneYFunc) router.POST("/one/z", OneZFunc) router.POST("/two/a", TwoAFunc) router.POST("/two/b", TwoBFunc) router.POST("/two/c", TwoCFunc) router.POST("/two/d", TwoDFunc) router.POST("/two/e", TwoEFunc) router.POST("/two/f", TwoFFunc) router.POST("/two/g", TwoGFunc) router.POST("/two/h", TwoHFunc) router.POST("/two/i", TwoIFunc) /* 省略n条路由 */ router.POST("/two/x", TwoXFunc) router.POST("/two/y", TwoYFunc) router.POST("/two/z", TwoZFunc) router.Run(":8080") }
我们首先创建了一个路由组 one,它的前缀为 /one。
然后,使用了 POST 方法在路由组 one 中定义了多个路由处理函数 oneAFunc到oneZFunc,它们分别处理 /two/a到 /two/z 路由的 HTTP POST 请求
请求路径
/one/a/
/tow/a/
// 路由分组第一组 one := router.Group("/one") { one.POST("/a", OneAFunc) one.POST("/b", OneBFunc) one.POST("/c", OneCFunc) one.POST("/d", OneDFunc) one.POST("/e", OneEFunc) one.POST("/f", OneFFunc) one.POST("/g", OneGFunc) one.POST("/h", OneHFunc) one.POST("/i", OneIFunc) /* 省略n条路由 */ one.POST("/x", OneXFunc) one.POST("/y", OneYFunc) one.POST("/z", OneZFunc) } //路由分组第二组 two := router.Group("/two") { two.POST("/a", twoAFunc) two.POST("/b", twoBFunc) two.POST("/c", twoCFunc) two.POST("/d", twoDFunc) two.POST("/e", twoEFunc) two.POST("/f", twoFFunc) two.POST("/g", twoGFunc) two.POST("/h", twoHFunc) two.POST("/i", twoIFunc) /* 省略n条路由 */ two.POST("/x", twoXFunc) two.POST("/y", twoYFunc) two.POST("/z", twoZFunc) }
分组后的go代码,虽然路由都实现路由分组,路由的逻辑显得清晰,但是路由文件还是集中在一个文件中,文件还是大。
文件结构
main.go handlers/ ├── one_handlers.go └── two_handlers.go
main.go
import ( "github.com/gin-gonic/gin" "your_project/routers" ) func main() { // 创建路由 router := gin.Default() // routers.SetupRouter(router) // 路由分组第一组 routers.TwoRoutersInit(router) // 路由分组第二组 routers.OneRoutersInit(router) // 启动服务 router.Run(":8080") }
TwoRoutersInit() 和 OneRoutersInit()对应下面两个文件:
one_handlers.go
package routers import "github.com/gin-gonic/gin" func OneRoutersInit(engine *gin.Engine) { one := engine.Group("/one") { one.POST("/a", OneAFunc) one.POST("/b", OneBFunc) one.POST("/c", OneCFunc) one.POST("/d", OneDFunc) one.POST("/e", OneEFunc) one.POST("/f", OneFFunc) /* 省略n条路由 */ one.POST("/x", OneXFunc) one.POST("/y", OneYFunc) one.POST("/z", OneZFunc) } } func OneAFunc(context *gin.Context) { } /* 省略N多方法 */ func OneZFunc(context *gin.Context) { }
two_handlers.go
package routers import "github.com/gin-gonic/gin" func TwoRoutersInit(engine *gin.Engine) { two := engine.Group("/two") { two.POST("/a", twoAFunc) two.POST("/b", twoBFunc) two.POST("/c", twoCFunc) two.POST("/d", twoDFunc) two.POST("/e", twoEFunc) two.POST("/f", twoFFunc) two.POST("/g", twoGFunc) two.POST("/h", twoHFunc) two.POST("/i", twoIFunc) /* 省略n条路由 */ two.POST("/x", twoXFunc) two.POST("/y", twoYFunc) two.POST("/z", twoZFunc) } } func twoAFunc(context *gin.Context) { } /* 省略n多方法 */ func twoZFunc(context *gin.Context) { }
备注:每个路由都要放在{}中,每个路由对应的方法入参固定context *gin.Context 其中context风状态request和response
官方示例:
main.go
// main.go package main import ( "github.com/gin-gonic/gin" "your_project/routers" ) func main() { r := gin.Default() routers.SetupRouter(r) r.Run() // listen and serve on 0.0.0.0:8080 }
routers/router.go
package routers import ( "github.com/gin-gonic/gin" ) func SetupRouter(r *gin.Engine) { v1 := r.Group("/v1") { v1.GET("/hello", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello", }) }) } v2 := r.Group("/v2") { v2.GET("/hello", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "hello v2", }) }) } }
对全局路由或者已注册的路由组统一注册中间件
r.Use(CheckToken())
userApi.POST("/upload",CheckToken(),userUpload)
func CheckToken() gin.HandlerFunc { return func(c *gin.Context) { // 验证不通过直接跳出 //c.JSON(http.StatusBadRequest, gin.H{"msg": "need token"}) //c.Abort() //return // 验证通过继续 c.Next() } }
中间件场景
Go Gin JWT
https://blog.csdn.net/u013302168/article/details/132178429
go版本管理使用go module
其中moduleA包中会导入moduleB包并使用它的New方法。
(注意:函数名称大写开头才能被引用)
moduleB/moduleB.go内容如下:
package moduleB import "fmt" func New(){ fmt.Println("moduleB.New") }
注意:在一个项目(project)下我们是可以定义多个包(package)的。
目录结构如下:
接着创建moduleA的mod,在集成终端中打开,并在命令行输入
go mod init moduleA
执行后,会在项目根目录下生成一个mod.go文件,执行go mod tidy 执行安装依赖 。
moduleA的go.mod内容如下:
module moduleA go 1.17
moudleA的main.go内容如下:
package main import ( "fmt" "moduleA/moduleB" ) func main() { moduleB.New() fmt.Println("main") }
到这里完成了第一步,现在来到的第二步;
首先,将moduleB的代码拉出外面来,我们来实现不同项目下的包的引用
此时的moduleB的包(文件夹)里没有mod文件,所以需要自己mod init 。
打开终端,在命令行输入
go mod init moduleB
此时moduleB的go.mod应该为:
module moduleA go 1.17 require "moduleB" v0.0.0 replace "moduleB" => "../moduleB"
moduleB中的moduleB.go文件不动,和上面的一样
因为这两个包不在同一个项目路径下,你想要导入本地包,并且这些包也没有发布到远程的github或其他代码仓库地址。
这个时候我们就需要在go.mod文件中使用replace指令。
在调用方也就是moduleA/go.mod中按如下方式指定使用相对路径来寻找moduleB这个包。此时moduleA的go.mod文件应该为:
module moduleA go 1.17 require "moduleB" v0.0.0 replace "moduleB" => "../moduleB"
moduleA的main.go文件应该为:
package main import ( "fmt" "moduleB" )func main() { moduleB.New() fmt.Println("main") }