Gin快速入门03 静态文件服务、路由详解、自定义控制器、Gin中间件 | 青训营

145 阅读7分钟

静态文件服务

当我们渲染的 HTML 文件中引用了静态文件时,像这样,

image.png

我们需要配置静态 web 服务

r.Static("/static", "./static")

配置静态web目录,第一个参数"/static"表示路由,第二个参数"./static"表示映射的目录

在main文件中配置

func main() {
	r := gin.Default()
	//自定义模板函数 注意要把这个函数放在加载模板前
	r.SetFuncMap(template.FuncMap{
		"UnixToTime": UnixToTime,
	})
	//配置模板目录,加载模板
	r.LoadHTMLGlob("templates/**/*") // /**表示一个目录

	//配置静态web目录,第一个参数表示路由,第二个参数表示映射的目录
	r.Static("/static", "./static")
        ...
        r.Run(":8080")
}

在html文件中引入静态文件

<link rel="stylesheet" href="static/css/base.css">
<img src="/static/images/1.jpg" alt="">

路由详解

路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等) 组成的,涉及到应用如何响应客户端对某个网站节点的访问。

1、GET POST 以及获取 Get Post 传值

1.1、GET请求传值

在输入地址栏中自行输入参数,如下图所示:
localhost:8080/?username=zhangsan&age=12

//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,
		})
	})

image.png

1.2、POST请求传值 获取form表单数据

定义一个提交表单的页面,如下:

{{ define "default/user.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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" /><br>
        密码:<input type="password" name="password"/> <br>
        <input type="submit" value="提交">
    </form>
</body>
</html>
{{end}}

通过 c.PostForm 接收表单传过来的数据

//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,
			"age":      age,
			"page":     page,
		})
	})

image.png

1.3、获取 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

//获取GET POST 传递的数据绑定到结构体
	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(),
			})
		}
	})

返回数据 {"user":"zhangsan","password":"123456"}

Post 传值绑定到结构体

	r.POST("/doAddUser2", 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(),
			})
		}
	})

返回数据 {"user":"zhangsan","password":"123456"}

1.4、获取 Post Xml 数据

在 API 的开发中,我们经常会用到 JSON 或 XML 来作为数据交互的格式,这个时候我们可以在 gin 中使用c.GetRawData()获取数据

r.POST("/xml", func(c *gin.Context) {
		article := &Article{}
		xmlSliceData, _ := c.GetRawData() //获取c.Request.Body读取数据请求
		if err := xml.Unmarshal(xmlSliceData, &article); err == nil {
			c.JSON(http.StatusOK, article)
		} else {
			c.JSON(http.StatusBadRequest, gin.H{
				"err": err.Error(),
			})
		}
	})

2、路由分组

当项目比较大时,将所有路由全部配置到main.go中显然是不合理的,这时我们就需要将路由分组抽离出来。

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("/userlist", func(c *gin.Context) {
			c.String(200, "我是一个api接口")
		})

		apiRouters.GET("/plist", func(c *gin.Context) {
			c.String(200, "我是一个api接口")
		})
	}

	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("/user/add", func(c *gin.Context) {
			c.String(200, "增加用户")
		})

		adminRouters.GET("/user/edit", func(c *gin.Context) {
			c.String(200, "修改用户")
		})
	}

前台分组 defaultRouters := r.Group("/")
后台分组 adminRouters := r.Group("/admin")
api接口分组 apiRouters := r.Group("/api")

分组抽离

新建 routes 文件夹,routes 文件下面新建adminRoutes.go、apiRoutes.go、defaultRoutes.go
1、新建adminRouters.go

package routers

import "github.com/gin-gonic/gin"

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("/user/add", func(c *gin.Context) {
			c.String(200, "增加用户")
		})

		adminRouters.GET("/user/edit", func(c *gin.Context) {
			c.String(200, "修改用户")
		})
	}
}

2、新建defaultRouters.go

package routers

import "github.com/gin-gonic/gin"

func DefaultRoutersInit(r *gin.Engine) {
	defaultRouters := r.Group("/api")
	{
		defaultRouters.GET("/", func(c *gin.Context) {
			c.String(200, "首页")
		})

		defaultRouters.GET("/news", func(c *gin.Context) {
			c.String(200, "新闻")
		})
	}
}

3、新建apiRouters.go

package routers

import "github.com/gin-gonic/gin"

func ApiRoutersInit(r *gin.Engine) {
	apiRouters := r.Group("/api")
	{
		apiRouters.GET("/userlist", func(c *gin.Context) {
			c.String(200, "我是一个api接口")
		})

		apiRouters.GET("/plist", func(c *gin.Context) {
			c.String(200, "我是一个api接口")
		})
	}
}

4、配置main.go

package main

import (
	"fmt"
	"gindemo04/routers"
	"text/template"
	"time"

	"github.com/gin-gonic/gin"
)

type UserInfo struct {
	Username string `json:"username" form:"username"`
	Password string `json:"password" form:"password"`
}

func main() {
	//创建一个默认的路由引擎
	r := gin.Default()
	//自定义模板函数 注意要把这个函数放在加载模板前
	r.SetFuncMap(template.FuncMap{
		"UnixToTime": UnixToTime,
	})
	//配置模板目录,加载模板
	r.LoadHTMLGlob("templates/**/*") // /**表示一个目录

	//配置静态web目录,第一个参数表示路由,第二个参数表示映射的目录
	r.Static("/static", "./static")

	routers.AdminRoutersInit(r)
	routers.ApiRoutersInit(r)
	routers.DefaultRoutersInit(r)

	r.Run(":8080")
}

此时分组路由就建立完成啦,访问试试吧!

gin中自定义控制器

控制器分组

当我们的项目比较大的时候有必要对我们的控制器进行分组

新建 controller/admin/userController.go

package admain

import "github.com/gin-gonic/gin"

func UserIndex(c *gin.Context) {
	c.String(200, "用户列表--")
}

func UserAdd(c *gin.Context) {
	c.String(200, "用户列表-add")
}

在adminRouters.go中引入controller/admin,配置对应路由

package routers

import (
	"gindemo05/controllers/admin"
	"github.com/gin-gonic/gin"
)

func AdminRoutersInit(r *gin.Engine) {
	adminRouters := r.Group("/admin")
	{
		adminRouters.GET("/", func(c *gin.Context) {
			c.String(200, "后台首页")
		})
		adminRouters.GET("/user", admin.UserIndex)

		adminRouters.GET("/user/add", admin.UserAdd)

		adminRouters.GET("/user/edit", func(c *gin.Context) {
			c.String(200, "修改用户")
		})
	}
}

Gin中间件

Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作。

路由中间件

1、初识中间件

Gin 中的中间件必须是一个 gin.HandlerFunc 类型,配置路由的时候可以传递多个 func 回调函数,最后一个 func 回调函数前面触发的方法都可以称为中间件。

package main

import (
        "fmt"
        "github.com/gin-gonic/gin"
)

func initMiddleware(ctx *gin.Context) {
    fmt.Println("我是一个中间件")
}

func main() {
    r := gin.Default()
    r.GET("/", initMiddleware, func(ctx *gin.Context) {
        ctx.String(200, "首页--中间件演示")
    })
    r.GET("/news", initMiddleware, func(ctx *gin.Context) {
        ctx.String(200, "新闻页面--中间件演示")
    })

    r.Run(":8080")

}

2、ctx.Next()调用该请求的剩余处理程序

中间件里面加上 ctx.Next()可以让我们在路由匹配完成后执行一些操作。比如我们统计一个请求的执行时间。

package main

import (
     "fmt"
     "time"
     "github.com/gin-gonic/gin"
)

func initMiddleware(ctx *gin.Context) {
    fmt.Println("1-执行中中间件")
    start := time.Now().UnixNano()
    // 调用该请求的剩余处理程序
    ctx.Next()
    fmt.Println("3-程序执行完成 计算时间")
    // 计算耗时 Go 语言中的 Since()函数保留时间值,并用于评估与实际时间的差异
    end := time.Now().UnixNano()
    fmt.Println(end - start)
}

func main() {
    r := gin.Default()
    r.GET("/", initMiddleware, func(ctx *gin.Context) {
        fmt.Println("2-执行首页返回数据")
        ctx.String(200, "首页--中间件演示")
    })
    r.GET("/news", initMiddleware, func(ctx *gin.Context) {
        ctx.String(200, "新闻页面--中间件演示")
    })
    
    r.Run(":8080")
}

3、一个路由配置多个中间件的执行顺序

func initMiddlewareOne(ctx *gin.Context) {
    fmt.Println("initMiddlewareOne--1-执行中中间件")
    // 调用该请求的剩余处理程序
    ctx.Next()
    fmt.Println("initMiddlewareOne--2-执行中中间件")
}

func initMiddlewareTwo(ctx *gin.Context) {
    fmt.Println("initMiddlewareTwo--1-执行中中间件")
    // 调用该请求的剩余处理程序
    ctx.Next()
    fmt.Println("initMiddlewareTwo--2-执行中中间件")
}

func main() {
    r := gin.Default()
    r.GET("/", initMiddlewareOne, initMiddlewareTwo, func(ctx *gin.Context) {
        fmt.Println("执行路由里面的程序")
        ctx.String(200, "首页--中间件演示")
    })
    
    r.Run(":8080")
}

控制台内容:

initMiddlewareOne--1-执行中中间件
initMiddlewareTwo--1-执行中中间件
执行路由里面的程序
initMiddlewareTwo--2-执行中中间件
initMiddlewareOne--2-执行中中间件

4、 c.Abort()--(了解)

Abort 是终止的意思, c.Abort() 表示终止调用该请求的剩余处理程序。

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

func initMiddlewareOne(ctx *gin.Context) {
    fmt.Println("initMiddlewareOne--1-执行中中间件")
    // 调用该请求的剩余处理程序
    ctx.Next()
    fmt.Println("initMiddlewareOne--2-执行中中间件")
}

func initMiddlewareTwo(ctx *gin.Context) {
    fmt.Println("initMiddlewareTwo--1-执行中中间件")
    // 终止调用该请求的剩余处理程序
    ctx.Abort()
    fmt.Println("initMiddlewareTwo--2-执行中中间件")
}

func main() {
    r := gin.Default()
    r.GET("/", initMiddlewareOne, initMiddlewareTwo, func(ctx *gin.Context) {
        fmt.Println("执行路由里面的程序")
        ctx.String(200, "首页--中间件演示")
    })

r.Run(":8080")

}

initMiddlewareOne--1-执行中间件
initMiddlewareTwo--1-执行中间件
initMiddlewareTwo--2-执行中间件
initMiddlewareOne--2-执行中间件