一、路由配置
gin 框架中采用的路由库是基于httprouter做的,httproter会将所有路由规则构造一颗前缀树
(1) 基本路由与路由组
1、基本路由
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name") //通过Context的Param方法来获取API参数
action := c.Param("action")
//截取
action = strings.Trim(action, "/")
c.String(http.StatusOK, name+" is "+action)
})
//默认为监听8080端口
r.Run(":8000")
}
2、路由分组
路由分组主要是用于将多个路由进行统一的处理,例如统一的前缀,统一的中间,方便功能划分等。
package main
import (
"github.com/gin-gonic/gin"
"fmt"
)
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 路由组
v1 := r.Group("/group")
{
v1.GET("/login", login)
v1.POST("submit", submit)
}
r.Run(":8080")
}
(2) 处理我们自己搭的项目中的路由
1、路由拆分
如果项目不断扩展和变大,继续在main.go里面去实现路由注册将会让main.go变得非常恐怖,所以在项目初始就应该把路由的拆分和注册规划好,我这里选择新建一个文件夹来管理路由相关的配置,并且新建router.go来作为路由配置的入口。
- 新建router.go来作为路由配置的入口,并且添加初始化方法
/router/router.go
package routers
import (
"github.com/gin-gonic/gin"
)
// 初始化路由
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
return r
}
- 为了方便不同功能的划分,我们对路由进行分层管理,在router的文件夹下新建一个文件夹api来存放api相关的路由,并且新建 index.go来引入路由,添加处理方法,并且在router.go 里初始化该文件夹下内容。
/router/api/index.go
package api
import (
"github.com/gin-gonic/gin"
)
func InitApi(r *gin.Engine) {
api := r.Group("/api")
}
/router/router.go
package routers
import (
"github.com/gin-gonic/gin"
"go-server-template/routers/api"
)
// 初始化路由
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
api.InitApi(r) // 新增
return r
}
- 每个不同模块的路由入口单独新建一个文件处理,例如我有一个登陆相关接口处理的路由,那我们可以新建一个文件夹userRouter来处理用户相关的路由 ,然后在下面新建一个index.go文件来处理当前模块下路由,然后再新建一个auth.go 来放用户权限校验相关的路由
/router/api/userRouter/index.go
package userRouter
import (
"github.com/gin-gonic/gin"
)
// 初始化模块路由
func UserInitRouter(r *gin.RouterGroup) {
userAuth := r.Group("/user-auth")
UserAuthRouter(userAuth)
}
/router/api/userRouter/auth.go
package userRouter
import (
"github.com/gin-gonic/gin"
)
func UserAuthRouter(g *gin.RouterGroup) {
g.GET("/login", 传入的方法)
}
- 如果有新的路由添加就可以按照这种布局方式来添加路由了,处理完后的整个路由文件夹目录大概是这样的
- 这时候我们执行一下 go run main.go ,然后访问当前启动服务的端口下,我们刚刚定义的路由,就可以访问成功啦
2、处理路由入口
路由写完之后就得在项目入口中去初始化路由啦,这时候我们可以暂时这么写(因为后面还要进一步抽象)
/main.go
package main
import (
"fmt"
"go-server-template/routers"
)
func main() {
// 注册路由
r := routers.InitRouter()
err := r.Run(":8080")
if err != nil {
fmt.Println("服务器启动失败!")
}
}
(3) 各种不同的路由定义以及参数获取
1、GET, POST, PUT, PATCH, DELETE, OPTIONS
r.GET("/routerNameGet", getting)
r.POST("/routerNamePost", posting)
r.PUT("/routerNamePut", putting)
r.DELETE("/routerNameDelete", deleting)
r.PATCH("/routerNamePatch", patching)
r.OPTIONS("/routerNameOptions", options)
2、参数获取
-
URL中的参数获取
1. /user/:name 形式 r.GET("/user/:account", func(c *gin.Context) { account := c.Param("account") }) 2. /user/:account/ *action 形式 r.GET("/user/:account/*action", func(c *gin.Context) { account := c.Param("account") action := c.Param("action") })
-
GET参数获取
// 匹配的url格式: /user?username=guy&account=123456 r.GET("/user", func(c *gin.Context) { // 使用该方法获取query参数时需要设定一个默认值 // 如果query参数有值则返回,无值则返回设置的默认值 username := c.DefaultQuery("username", "Guy") lastname := c.Query("account") // 是 c.Request.URL.Query().Get("account") 的简写 })
-
POST参数获取
type User struct { name string `json:"name"` account int64 `json:"account"` } r.POST("/userInfo", func(c *gin.Context) { // 获取表单参数 message := c.PostForm("userName") // 表单参数 nick := c.DefaultPostForm("account", "123456") // 此方法可以设置默认值,和上面的get一样 // 获取body中的参数方式一 json := make(map[string]interface{}) //注意该结构接受的内容 c.BindJSON(&json) log.Printf("%v",&json) // 获取body中的参数方式二 json := User{} c.BindJSON(&json) })
-
获取请求头参数
例如 token := c.GetHeader("token")
二、中间件配置
(1) 什么是中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等
Gin的中间件,本质是一个匿名回调函数。这和绑定到一个路径下的处理函数本质是一样的。
1、gin中间件处理顺序
其实gin的中间件和node.js的框架egg.js一样是洋葱模型
简单地表示一下就是下图:
模型的中心是最终处理请求的 handler,称之为 main handler,其他为称为 middleware handler,每一个 middleware handle 可以分为两部分,随着 request 的流动,左边是入,右边为出,而分割点就是 next,本质就是通过这个next来执行函数链 ,各个中间件符合先进后出原则
(2) 如何定义中间件
**注:**Gin中的中间件必须是一个gin.HandlerFunc类型
1、Gin内置中间件
在使用Gin框架开发Web应用时,常常需要自定义中间件,不过,Gin也内置一些中间件,我们可以直接使用,这里就不过多描述了
func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定
func ErrorLogger() HandlerFunc //错误日志处理
func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理
func Logger() HandlerFunc //日志记录
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件
func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件
2、全局中间件
全局中间件作用于所有的路由上,所有的路由请求都需要经过这些全局中间件。
例:
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
// 定义中间件
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中,可以通过Get()取
c.Set("request", "中间件")
status := c.Writer.Status()
fmt.Println("中间件执行完毕", status)
t2 := time.Since(t)
fmt.Println("time:", t2)
}
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 注册中间件,全局中间件用.Use注册
r.Use(MiddleWare())
// {}为了代码规范
{
r.GET("/ce", func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
}
r.Run()
}
来源:gin文档
3、局部中间件
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
// 定义中间件
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中,可以通过Get()取
c.Set("request", "中间件")
// 执行函数
c.Next()
// 中间件执行完后续的一些事情
status := c.Writer.Status()
fmt.Println("中间件执行完毕", status)
t2 := time.Since(t)
fmt.Println("time:", t2)
}
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
//局部中间键使用
r.GET("/ce", MiddleWare(), func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
r.Run()
}
来源:gin文档
注1: 在gin框架中,可以为每个路由添加任意数量的中间件,可以跨中间件取值。
注2: gin.Default()默认使用了Logger和Recovery中间件,其中:Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。
**注3:**当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。
(3) 中间件的取值
1、获取请求中的参数
在return 的方法中获取即可,和上面路由部分的内容一样,例如获取请求头中的参数
// 定义中间件
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("token")
}
}
2、跨中间件存取值
当我们在中间件拦截并预先处理好数据之后,如果想要将数据传递到我们定义的处理请求的方法,可以使用gin.Context中的Set()方法。使用Set()通过一个key来存储作何类型的数据,方便下一层的中间件或者处理方法使用gin.Context的Get方法获取。
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("request", "中间件")
c.Next()
}
}
func MiddleWare2() gin.HandlerFunc {
return func(c *gin.Context) {
c.Get("request")
}
}
当我们用Set 方法设置了对应数据类型的值的时候,还能通过特定的方法来获取相应数据类型的值
func (c *Context) GetString(key string) (s string)
func (c *Context) GetStringMap(key string) (sm map[string]interface{})
func (c *Context) GetStringMapString(key string) (sms map[string]string)
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
func (c *Context) GetStringSlice(key string) (ss []string)
func (c *Context) GetBool(key string) (b bool)
func (c *Context) GetDuration(key string) (d time.Duration)
func (c *Context) GetFloat64(key string) (f64 float64)
func (c *Context) GetInt(key string) (i int)
func (c *Context) GetInt64(key string) (i64 int64)
func (c *Context) GetTime(key string) (t time.Time)
(4) 请求拦截
1、前置拦截
毫无疑问,对于中间件来说,请求拦截是最重要的作用,如果你和我一样之前是写前端的,可以自己代入一下 vue-router 中的路由守卫的作用。
那如果我们认为用户的请求有问题,不想让他进行下一步操作的时候我们可以怎么做呢?
这时候,gin的Abort系列函数就能帮助我们做到这件事了,注意,错误处理请求返回要使用c.Abort,不要只是return哦
func (c *Context) Abort()
func (c *Context) AbortWithStatus(code int)
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{})
func (c *Context) AbortWithError(code int, err error)
Abort()
Abort 在被调用的函数中阻止挂起函数。注意这将不会停止当前的函数,所以还得配合 return 食用。例如,你有一个验证当前的请求是否是认证过的 Authorization 中间件。如果验证失败(例如,密码不匹配),调用 Abort 以确保这个请求的其他函数不会被调用。
使用Abort() 中断请求之后会直接返回200,但响应的body中不会有数据。
AbortWithStatusJSON()
使用AbortWithStatusJSON()方法,中断用户请求后,则可以返回json格式的数据
2、后置拦截
gin.Context的Next()方法,可以请求到达并完成业务处理后,再经过中间件后置拦截处理
例如:
func Middleware1(c *gin.Context){
fmt.Println("1 开始")
//c.Next()会跳过当前中间件后续的逻辑,类似defer,最后再执行c.Next后面的逻辑
//多个c.Next()谁在前面谁后执行,跟defer很像,类似先进后出的栈
c.Next()
fmt.Println("1 结束")
}
func Middleware2(c *gin.Context){
fmt.Println("2 开始")
c.Next()
fmt.Println("2 结束")
}
r.Use(Middleware1, Middleware2)
r.GET("/", func(c *gin.Context) {
c.Next()
fmt.Println("处理方法执行")
})
会输出
1 开始
2 开始
处理方法执行
2 结束
1 结束
调用 Next() 之后会执行下一个中间件的操作或自定义的处理方法,简单来说可以理解为继续执行下一个的意思。
(5) 定义项目中的全局中间件
-
老规矩,我们在根目录下新建一个文件夹 middleware ,然后在下面再新建一个global 来存放我们定义好的全局中间件,新建一个custom来存放我们的局部中间件
-
在global下新建一个示例中间件文件夹 auth ,并在文件夹中新建 auth.go
/middleware/global/auth/auth.go package AuthMiddleware import ( "github.com/gin-gonic/gin" ) // 用户校验 func UserAuth() gin.HandlerFunc { //自定义逻辑 //返回中间件 return func(c *gin.Context) { //中间件逻辑 } }
-
在main.go中引入该中间件并使用
package main import ( "fmt" "go-server-template/middleware/global/auth" "go-server-template/routers" ) func main() { r := routers.InitRouter() r.Use(authMiddleware.UserAuth()) err := r.Run(":8080") if err != nil { fmt.Println("服务器启动失败!") } }