路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何响应客户端对某个网站节点的访问,前面介绍了路由基础以及路由配置,这里详细讲讲路由传值、路由返回值
GET POST 以及获取 Get Post 传值
(1).Get 请求传值
// GET /user?uid=20&page=1
router.GET("/user", func(c *gin.Context) {uid := c.Query("uid")page := c.DefaultQuery("page", "0")c.String(200, "uid=%v page=%v", uid, page)
})
(2).动态路由传值
// 域名/user/20
r.GET("/user/:uid", func(c *gin.Context) {uid := c.Param("uid")c.String(200, "userID=%s", uid)
})
(3).Post 请求传值 获取 form 表单数据
// 定义一个 default/add_user.html 的页面
{{ define "default/add_user.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/doAddUser" method="post">
用户名:<input type="text" name="username" />
密码: <input type="password" name="password" />
<input type="submit" value="提交">
</form>
</body>
</html>
{{end}}
// 通过 c.PostForm 接收表单传过来的数据
router.GET("/addUser", func(c *gin.Context) {c.HTML(200, "default/add_user.html", gin.H{})
})
router.POST("/doAddUser", func(c *gin.Context) {username := c.PostForm("username")password := c.PostForm("password")age := c.DefaultPostForm("age", "20")c.JSON(200, gin.H{ "usernmae": username,"password": password,"age": age,})
})
(4).获取 GET POST 传递的数据绑定到结构体
为了能够更方便的获取请求相关参数,提高开发效率,可以基于请求的 Content-Type
识别请求数据类型并利用反射机制自动提取请求中 QueryString、form 表单、JSON、XML 等
参数到结构体中,下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提
取 JSON、form 表单和 QueryString 类型的数据,并把值绑定到指定的结构体对象
//注意首字母大写
type Userinfo struct {Username string `form:"username" json:"user"` Password string `form:"password" json:"password"`}
Get 传值绑定到结构体
//?username=zhangsan&password=123456
router.GET("/", func(c *gin.Context) {var userinfo Userinfoif err := c.ShouldBind(&userinfo); err == nil {c.JSON(http.StatusOK, userinfo)} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}
})
//返回数据
{"user":"zhangsan","password":"123456"}
Post 传值绑定到结构体
router.POST("/doLogin", func(c *gin.Context) {var userinfo Userinfoif err := c.ShouldBind(&userinfo); err == nil {c.JSON(http.StatusOK, userinfo)} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
//返回数据
{"user":"zhangsan","password":"123456"}
(5).获取 Post Xml 数据
在 API 的开发中,经常会用到 JSON 或 XML 来作为数据交互的格式,这个时候可以在 gin 中使用 c.GetRawData()获取数据
<?xml version="1.0" encoding="UTF-8"?>
<article>
<content type="string">我是张三</content>
<title type="string">张三</title>
</article>
type Article struct {Title string `xml:"title"`Content string `xml:"content"`
}router.POST("/xml", func(c *gin.Context) {b, _ := c.GetRawData() // 从 c.Request.Body 读取请求数据article := &Article{}if err := xml.Unmarshal(b, &article); err == nil {c.JSON(http.StatusOK, article)} else {c.JSON(http.StatusBadRequest, err.Error())}
})
代码汇总:
main.go
package mainimport ("github.com/gin-gonic/gin""html/template""net/http""time"
)//时间戳转换成日期函数
func UnixToTime(timestamp int) string {t := time.Unix(int64(timestamp), 0)return t.Format("2006-01-02 15:04:05")
}func Println(str1 string, str2 string) string {return str1 + str2
}type UserInfo struct {Username string `json:"username" form:"username"`Password string `json:"password" form:"password"`
}type Article struct {Title string `json:"title" xml:"title"`Content string `json:"content" xml:"content"`
}func main() {//初始化路由r := gin.Default()//自定义模板函数,必须在r.LoadHTMLGlob前面r.SetFuncMap(template.FuncMap{"UnixToTime": UnixToTime, //注册模板函数"Println": Println,})//加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行r.LoadHTMLGlob("templates/**/*")//配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录r.Static("/static", "./static")//配置路由//GET传值r.GET("/", func(c *gin.Context) {username := c.Query("username")age := c.Query("age")page := c.DefaultQuery("page", "1")c.JSON(http.StatusOK, gin.H{"username": username,"age": age,"page": page,})})//GET传值 获取文章idr.GET("/article", func(c *gin.Context) {id := c.DefaultQuery("id", "1")c.JSON(http.StatusOK, gin.H{"id": id,"conent": "文章详情",})})//post演示r.GET("/user", func(c *gin.Context) {//渲染模板c.HTML(http.StatusOK, "default/user.html", gin.H{})})//获取表单post过来的数据r.POST("/doAddUser", func(c *gin.Context) {username := c.PostForm("username")password := c.PostForm("password")age := c.DefaultPostForm("age", "20")c.JSON(http.StatusOK, gin.H{"username": username,"password": password,"age": age,})})//获取GET POST传递的数据绑定到结构体//r.POST("/getUser", func(c *gin.Context) {r.GET("/getUser", func(c *gin.Context) {user := &UserInfo{}//绑定到对应的结构体if err := c.ShouldBind(&user); err == nil {c.JSON(http.StatusOK, user)} else {c.JSON(http.StatusOK, gin.H{"err": err.Error(),})}})//获取 POST xml数据绑定到结构体r.POST("/xml", func(c *gin.Context) {article := &Article{}//获取c.Request.Body读取请求数据, 返回的是一个xml切片xmlSliceData, _ := c.GetRawData()//解析xml切片if err := xml.Unmarshal(xmlSliceData, &article); err == nil {c.JSON(http.StatusOK, article)} else {c.JSON(http.StatusBadRequest, gin.H{"err": err.Error(),})}})//动态路由传值//http://127.0.0.1:8080/list/2221 http://127.0.0.1:8080/list/2r.GET("/list/:id", func(c *gin.Context) {id := c.Param("id")c.JSON(http.StatusOK, gin.H{"id": id,})})r.Run() // 启动一个web服务
}
default/user.html
<!-- 相当于给模板定义一个名字, define end 必须成对出现 -->
{{ define "default/user.html" }}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>news</title>
</head>
<body>
{{ template "public/page_header.html" .}}
<form action="/doAddUser" method="post">用户名:<input type="text" name="username" /> <br/> <br/>密码:<input type="text" name="password" /><br/> <br/><input type="submit" value="提交">
</form>
{{ template "public/page_footer.html" .}}
</body>
</html>
{{ end }}
简单的路由组
官方文档:https://gin-gonic.com/zh-cn/docs/examples/grouping-routes/
func main() {//初始化路由r := gin.Default()//简单的路由分组defaultRouters := r.Group("/"){defaultRouters.GET("/", func(c *gin.Context) {c.String(200, "首页")})defaultRouters.GET("/news", func(c *gin.Context) {c.String(200, "新闻")})}apiRouters := r.Group("/api"){apiRouters.GET("/", func(c *gin.Context) {c.String(200, "api首页")})apiRouters.GET("/userlist", func(c *gin.Context) {c.String(200, "一个userlist接口")})}adminRouters := r.Group("/admin"){adminRouters.GET("/", func(c *gin.Context) {c.String(200, "后台首页")})adminRouters.GET("/user", func(c *gin.Context) {c.String(200, "用户列表")})adminRouters.GET("/article", func(c *gin.Context) {c.String(200, "新闻列表")})}
}
3.Gin 路由文件 分组
(1).新建 routes 文件夹,routes 文件下面新建 adminRoutes.go、apiRoutes.go、defaultRoutes.go
1).新建 adminRoutes.go
package routersimport "github.com/gin-gonic/gin"//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {adminRouters := r.Group("/admin"){adminRouters.GET("/", func(c *gin.Context) {c.String(200, "后台首页")})adminRouters.GET("/user", func(c *gin.Context) {c.String(200, "用户列表")})adminRouters.GET("/article", func(c *gin.Context) {c.String(200, "新闻列表")})}
}
2).新建 apiRoutes.go
package routersimport "github.com/gin-gonic/gin"//设置api路由
func ApiRoutersInit(r *gin.Engine) {apiRouters := r.Group("/api"){apiRouters.GET("/", func(c *gin.Context) {c.String(200, "api首页")})apiRouters.GET("/userlist", func(c *gin.Context) {c.String(200, "一个userlist接口")})}
}
3).新建 defaultRoutes.go
package routersimport ("github.com/gin-gonic/gin""net/http"
)//设置前台后台路由
func DefaultRoutersInit(r *gin.Engine) {defaultRouters := r.Group("/"){defaultRouters.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "default/index.html", gin.H{"msg":"前台首页",})})defaultRouters.GET("/news", func(c *gin.Context) {c.String(200, "新闻")})}
}
(2).配置 main.go
package mainimport ("gindemo/routers""github.com/gin-gonic/gin"
)func main() {r := gin.Default()//路由文件routers.AdminRoutersInit(r)routers.ApiRoutersInit(r)routers.DefaultRoutersInit(r)
}
二.1.控制器分组
当项目比较大的时候有必要对控制器进行分组
(1).新建 controller/admin/articleController.go
package adminimport ("github.com/gin-gonic/gin""net/http"
)type ArticleController struct {}func (con ArticleController) Index(c *gin.Context) {c.String(http.StatusOK, "文章列表1")
}func (con ArticleController) Add(c *gin.Context) {c.String(http.StatusOK, "文章添加2")
}func (con ArticleController) Edit(c *gin.Context) {c.String(http.StatusOK, "文章编辑3")
}
(2).新建 controller/admin/indexController.go
package adminimport "github.com/gin-gonic/gin"type IndexController struct {}func (con IndexController) Index(c *gin.Context) {c.String(200, "后台首页")
}
(3).新建 controller/admin/userController.go
package adminimport "github.com/gin-gonic/gin"//定义一个UserController结构体,可以实例化结构体访问里面的方法
type UserController struct {}func (con UserController) Index(c *gin.Context) {//c.String(200, "用户列表1")con.success(c)
}func (con UserController) Add(c *gin.Context) {c.String(200, "用户添加2")
}func (con UserController) Edit(c *gin.Context) {c.String(200, "用户编辑3")
}
(4).配置对应的路由:adminRoutes.go
package routersimport ("gindemo/controllers/admin""github.com/gin-gonic/gin"
)//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {adminRouters := r.Group("/admin"){adminRouters.GET("/", admin.IndexController{}.Index)// 实例化控制器,并访问其中方法adminRouters.GET("/user", admin.UserController{}.Index)adminRouters.GET("/user/add",admin.UserController{}.Add)adminRouters.GET("/user/edit", admin.UserController{}.Edit)adminRouters.GET("/article", admin.ArticleController{}.Index)adminRouters.GET("/article/add", admin.ArticleController{}.Add)adminRouters.GET("/article/edit", admin.ArticleController{}.Edit)}
}
其他路由的配置方法类似
2.控制器的继承
(1).新建 controller/admin/baseController.go
package adminimport ("github.com/gin-gonic/gin""net/http"
)//基础控制器
type BaseController struct {}func (con BaseController) success(c *gin.Context) {c.String(http.StatusOK, "成功")
}func (con BaseController) error(c *gin.Context) {c.String(http.StatusOK, "失败")
}
(2).userController 继承 baseController
package adminimport "github.com/gin-gonic/gin"//定义一个UserController结构体,可以实例化结构体访问里面的方法
type UserController struct {BaseController // 继承基础控制器
}func (con UserController) Index(c *gin.Context) {con.success(c)
}func (con UserController) Add(c *gin.Context) {c.String(200, "用户添加2")
}func (con UserController) Edit(c *gin.Context) {c.String(200, "用户编辑3")
}
三.Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数,这个钩子函
数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、
记录日志、耗时统计等
通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作
1.路由中间件
(1).初识中间件
Gin 中的中间件必须是一个 gin.HandlerFunc 类型,配置路由的时候可以传递多个 func 回调函
数,最后一个 func 回调函数前面触发的方法都可以称为中间件
package main
import ( "fmt""github.com/gin-gonic/gin"
)//中间件
func initMiddleware(c *gin.Context) {c.String(200, "--1.中间件one--")
}func main() {//初始化路由r := gin.Default()//加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行r.LoadHTMLGlob("templates/**/*")//配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录r.Static("/static", "./static")//路由分组defaultRouters := r.Group("/"){defaultRouters.GET("/", initMiddleware, func(c *gin.Context) {c.String(200, "首页")})}r.Run() // 启动一个web服务
}
(2)c.Next()调用该请求的剩余处理程序
package main
import ( "fmt""github.com/gin-gonic/gin"
)//中间件
func initMiddleware(c *gin.Context) {start := time.Now().UnixNano()c.String(200, "--1.中间件one--")//调用该请求的剩余处理程序c.Next()c.String(200, "--2.中间件one--")end := time.Now().UnixNano()// 计算耗时 Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异fmt.Println( end - start)
}func main() {//初始化路由r := gin.Default()//加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行r.LoadHTMLGlob("templates/**/*")//配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录r.Static("/static", "./static")//路由分组defaultRouters := r.Group("/"){//中间件访问,调用该请求的剩余处理程序, c.Next(): 依次输出:--1.中间件one--首页--2.中间件one--defaultRouters.GET("/", initMiddleware, func(c *gin.Context) {c.String(200, "首页")})}r.Run() // 启动一个web服务
}
(3).一个路由配置多个中间件的执行顺序
package main
import ( "fmt""github.com/gin-gonic/gin"
)//中间件
func initMiddleware(c *gin.Context) {start := time.Now().UnixNano()c.String(200, "--1.中间件one--")//调用该请求的剩余处理程序c.Next()c.String(200, "--2.中间件one--")end := time.Now().UnixNano()// 计算耗时 Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异fmt.Println( end - start)
}
//中间件
func initMiddlewareTwo(c *gin.Context) {start := time.Now().UnixNano()c.String(200, "--2.中间件two--")//调用该请求的剩余处理程序c.Next()c.String(200, "--2.中间件two--")end := time.Now().UnixNano()fmt.Println( end - start)
}func main() {//初始化路由r := gin.Default()//加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行r.LoadHTMLGlob("templates/**/*")//配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录r.Static("/static", "./static")//路由分组defaultRouters := r.Group("/"){//多个中间件访问: 依次输出:--1.中间件one----2.中间件two--首页--2.中间件two----2.中间件one--defaultRouters.GET("/", initMiddleware, initMiddlewareTwo, func(c *gin.Context) {c.String(200, "首页")})}r.Run() // 启动一个web服务
}
控制台内容:
initMiddleware--1-执行中间件
initMiddlewareTwo--1-执行中间件
执行路由里面的程序
initMiddlewareTwo--2-执行中间件
initMiddleware--2-执行中间件
(4).c.Abort()终止调用该请求的剩余处理程序
Abort 是终止的意思, c.Abort() 表示终止调用该请求的剩余处理程序
package main
import ( "fmt""github.com/gin-gonic/gin"
)//中间件
func initMiddlewareAbort(c *gin.Context) {start := time.Now().UnixNano()c.String(200, "--1.中间件abort--")//调用该请求的剩余处理程序//c.Next()//终止该请求的剩余处理程序c.Abort()c.String(200, "--2.中间件abort--")end := time.Now().UnixNano()fmt.Println( end - start)
}func main() {//初始化路由r := gin.Default()//加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行r.LoadHTMLGlob("templates/**/*")//配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录r.Static("/static", "./static")//路由分组defaultRouters := r.Group("/"){//中间件访问,终止该请求的剩余处理程序, c.Abort(): 依次输出:--1.中间件abort----2.中间件abort--//不会执行最后的HandFunc程序defaultRouters.GET("/", initMiddlewareAbort, func(c *gin.Context) {c.String(200, "首页")})}r.Run() // 启动一个web服务
}
2.全局中间件
package main
import ( "fmt""github.com/gin-gonic/gin"
)//中间件
func initMiddleware(c *gin.Context) {start := time.Now().UnixNano()c.String(200, "--1.中间件one--")//调用该请求的剩余处理程序c.Next()//终止该请求的剩余处理程序//c.Abort()c.String(200, "--2.中间件one--")end := time.Now().UnixNano()fmt.Println( end - start)
}//中间件
func initMiddlewareAbort(c *gin.Context) {start := time.Now().UnixNano()c.String(200, "--1.中间件abort--")//调用该请求的剩余处理程序//c.Next()//终止该请求的剩余处理程序c.Abort()c.String(200, "--2.中间件abort--")end := time.Now().UnixNano()fmt.Println( end - start)
}//中间件
func initMiddlewareTwo(c *gin.Context) {start := time.Now().UnixNano()c.String(200, "--2.中间件two--")//调用该请求的剩余处理程序c.Next()//终止该请求的剩余处理程序//c.Abort()c.String(200, "--2.中间件two--")end := time.Now().UnixNano()fmt.Println( end - start)
}func main() {//初始化路由r := gin.Default()//加载templates中所有模板文件, 使用不同目录下名称相同的模板,注意:一定要放在配置路由之前才得行r.LoadHTMLGlob("templates/**/*")//配置静态web目录 第一个参数表示路由,第二个参数表示映射的目录r.Static("/static", "./static")//全局中间件r.Use(initMiddleware, initMiddlewareTwo)//路由分组defaultRouters := r.Group("/"){//中间件访问,终止该请求的剩余处理程序, c.Abort(): 依次输出:--1.中间件abort----2.中间件abort--//不会执行最后的HandFunc程序defaultRouters.GET("/", initMiddlewareAbort, func(c *gin.Context) {c.String(200, "首页")})}r.Run() // 启动一个web服务
}
3.在路由分组中配置中间件
(1).为路由组注册中间件有以下两种写法
写法 1:
userGroup := r.Group("/user", UserOper())
{userGroup.GET("/index", func(c *gin.Context) {...})...
}
写法 2:
userGroup := r.Group("/user")
userGroup.Use(UserOper())
{userGroup.GET("/index", func(c *gin.Context) {...})...
}
(2).分组路由adminRoutes.go 中配置中间件
adminRoutes.go
package routersimport ("gindemo/controllers/admin""gindemo/middlewares""github.com/gin-gonic/gin"
)//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {//路由分组: 配置全局中间件:middlewares.InitMiddlewareadminRouters := r.Group("/admin", middlewares.InitMiddleware){adminRouters.GET("/", admin.IndexController{}.Index)// 实例化控制器,并访问其中方法adminRouters.GET("/user", admin.UserController{}.Index)adminRouters.GET("/user/add",admin.UserController{}.Add)adminRouters.GET("/user/edit", admin.UserController{}.Edit)adminRouters.GET("/article", admin.ArticleController{}.Index)adminRouters.GET("/article/add", admin.ArticleController{}.Add)adminRouters.GET("/article/edit", admin.ArticleController{}.Edit)}
}
middlewares/init.go
package middlewaresimport ("fmt""github.com/gin-gonic/gin""time"
)func InitMiddleware( c *gin.Context) {//判断用户是否登录fmt.Println(time.Now())fmt.Println(c.Request.URL)
}
4.中间件和对应控制器之间共享数据
(1).设置值
c.Set("username","张三")
(2).获取值
username, _ := c.Get("username")
(3).中间件设置值
middlewares/init.go
package middlewaresimport ("fmt""github.com/gin-gonic/gin""time"
)func InitMiddleware( c *gin.Context) {//判断用户是否登录fmt.Println(time.Now())fmt.Println(c.Request.URL)//设置值, 和对应控制器之间共享数据c.Set("username", "张三")
}
控制器获取值 controlelers/adimn/indexController.go
package adminimport ("fmt""github.com/gin-gonic/gin"
)type IndexController struct {}func (con IndexController) Index(c *gin.Context) {//获取中间件中设置的username值,数据共享username, _ := c.Get("username")fmt.Println(username)//username是一个空接口类型,故要使用则需要用类型断言转换usernamev, ok := username.(string)if ok != true {c.String(200, "后台首页--获取用户名失败")} else {c.String(200, "后台首页,用户名:" + v)}}
5.中间件注意事项
(1).gin 默认中间件
gin.Default()默认使用了 Logger 和 Recovery 中间件,其中:
• Logger 中间件将日志写入 gin.DefaultWriter,即使配置了 GIN_MODE=release
• Recovery 中间件会 recover 任何 panic,如果有 panic 的话,会写入 500 响应码
如果不想使用上面两个默认的中间件,可以使用 gin.New()新建一个没有任何默认中间件的
路由
(2).gin 中间件中使用 goroutine
当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())
package middlewaresimport ("fmt""github.com/gin-gonic/gin""time"
)func InitMiddleware( c *gin.Context) {//判断用户是否登录fmt.Println(time.Now())fmt.Println(c.Request.URL)//设置值, 和对应控制器之间共享数据c.Set("username", "张三")//gin 中间件中使用 goroutine//当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文(c *gin.Context),//必须使用其只读副本(c.Copy())cCp := c.Copy()go func() {time.Sleep(2 * time.Second)fmt.Println("gin 中间件中使用 goroutine" + cCp.Request.URL.Path)}()
}
[上一节][golang gin框架] 2.Gin HTML模板渲染以及模板语法,自定义模板函数,静态文件服务