一.路由详解
路由(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 Userinfo
if 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 Userinfo
if 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 main
import (
"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传值 获取文章id
r.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/2
r.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 routers
import "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 routers
import "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 routers
import (
"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 main
import (
"gindemo/routers"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
//路由文件
routers.AdminRoutersInit(r)
routers.ApiRoutersInit(r)
routers.DefaultRoutersInit(r)
}
二.Gin 中自定义控制器
1.控制器分组
当项目比较大的时候有必要对控制器进行分组
(1).新建 controller/admin/articleController.go
package admin
import (
"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 admin
import "github.com/gin-gonic/gin"
type IndexController struct {
}
func (con IndexController) Index(c *gin.Context) {
c.String(200, "后台首页")
}
(3).新建 controller/admin/userController.go
package admin
import "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 routers
import (
"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 admin
import (
"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 admin
import "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 中间件
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 routers
import (
"gindemo/controllers/admin"
"gindemo/middlewares"
"github.com/gin-gonic/gin"
)
//设置admin后台路由
func AdminRoutersInit(r *gin.Engine) {
//路由分组: 配置全局中间件:middlewares.InitMiddleware
adminRouters := 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 middlewares
import (
"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 middlewares
import (
"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 admin
import (
"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是一个空接口类型,故要使用则需要用类型断言转换username
v, 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 middlewares
import (
"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)
}()
}