项目源码
中间件是一种软件设计模式,它允许在应用程序的请求 - 响应处理流程中插入自定义的逻辑,可以用于实现日志记录、权限验证、请求限流、错误处理等功能
通过合理地运用中间件,我们可以使应用更加健壮、安全和可扩展
中间件简单使用
那在Gin
框架中如何使用中间件呢?通过查看源码发现有个engine.Use
方法:
// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
// Use 函数用于向 Gin 引擎添加全局中间件。这些中间件将被应用于所有请求,包括 404、405 和静态文件请求等。
// 例如,这是添加日志记录器或错误管理中间件的理想位置。
func (engine *Engine) Use(middleware...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
可以通过这个方法来添加中间件,Use 方法可以接收多个type HandlerFunc func(*Context)
类型的方法,这个方法类型看着有点眼熟,其实它就是路由控制里边的 Handler,这个我们后面再说,这里先看一下路由的简单使用:
func main() {
route := gin.Default()
route.Use(middleware)
route.GET("/a", func(c *gin.Context) {
fmt.Println("handler executed")
})
err := route.Run(":8085")
if err != nil {
return
}
}
func middleware(c *gin.Context) {
fmt.Println("before handler")
c.Next()
fmt.Println("after handler")
}
上例中定义了一个中间件并通过Use方法加载到HTTP服务中,访问 /a
路由可以看到控制台有输出:
before handler
handler executed
after handler
通过输出内容也可以看到中间件和Handler的执行顺序,那我们再做一下调整:
func main() {
route := gin.Default()
route.GET("/a", func(c *gin.Context) {
fmt.Println("handler a executed")
})
route.Use(middleware)
route.GET("/b", func(c *gin.Context) {
fmt.Println("handler b executed")
})
err := route.Run(":8085")
if err != nil {
return
}
}
func middleware(c *gin.Context) {
fmt.Println("before handler")
c.Next()
fmt.Println("after handler")
}
访问路由/a
会发现控制台输出:
handler a executed
中间件middleware并没有被执行,再访问路由/b
查看控制台输出:
before handler
handler b executed
after handler
这里中间件方法被执行了,这说明中间件的执行顺序是跟它的注册顺序有关的
那如果是多个中间件呢?再做一下调整:
func main() {
route := gin.Default()
route.GET("/a", func(c *gin.Context) {
fmt.Println("handler a executed")
})
route.Use(middleware1, middleware2)
route.GET("/b", func(c *gin.Context) {
fmt.Println("handler b executed")
})
err := route.Run(":8085")
if err != nil {
return
}
}
func middleware1(c *gin.Context) {
fmt.Println("middleware 1 before handler")
c.Next()
fmt.Println("middleware 1 after handler")
}
func middleware2(c *gin.Context) {
fmt.Println("middleware 2 before handler")
c.Next()
fmt.Println("middleware 2 after handler")
}
访问/b
路由,查看控制台输出:
middleware 1 before handler
middleware 2 before handler
handler b executed
middleware 2 after handler
middleware 1 after handler
可以看到先注册的中间件是先执行的,并且在调用 c.Next() 方法之后会执行后边的中间件,然后再执行 c.Next 后边的内容
局部使用中间件
到这里我们会发现一个问题,在中间件注册之前的Handler 都不受中间件的影响,在注册之后的Handler 都会执行中间件,那如果我们只想在一个路由或者一组路由 中使用某个中间件,其他地方不使用呢?如果仅仅通过路由和中间件的注册顺序去控制,会很麻烦,好在Gin 框架也支持,看源码:
// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
可以对我们之前使用的RouterGroup
通过Use 方法来注册中间件,并且只对 group 内部的路由生效,哪怕在 group 之后注册的路由也不会生效,调整一下代码:
func main() {
route := gin.Default()
rg := route.Group("/admin").Use(middleware1)
rg.GET("/a", func(c *gin.Context) {
fmt.Println("handler a executed")
})
route.Use(middleware2)
route.GET("/b", func(c *gin.Context) {
fmt.Println("handler b executed")
})
err := route.Run(":8085")
if err != nil {
return
}
}
func middleware1(c *gin.Context) {
fmt.Println("middleware 1 before handler")
c.Next()
fmt.Println("middleware 1 after handler")
}
func middleware2(c *gin.Context) {
fmt.Println("middleware 2 before handler")
c.Next()
fmt.Println("middleware 2 after handler")
}
分别访问路由/admin/a
和/b
,查看控制台输出:
/admin/a
middleware 1 before handler
handler a executed
middleware 1 after handler
/b
middleware 2 before handler
handler b executed
middleware 2 after handler
可以看到 group 注册的中间件 middleware1 对路由/b
不生效
单个路由注册中间件
那如果有需要让我们只给某一个路由注册中间件的时候,怎么办?单独搞一个 group 出来?也不是不可以,但是有更简单的方法,前边我们提到中间件其实是一个 type HandlerFunc func(*Context)
类型的方法,而路由的 Handler 其实也是一样的类型,所谓中间件其实也是一个Handler,那我们在注册路由时直接加上中间件就可以了,修改代码:
func main() {
route := gin.Default()
route.GET("/a", middleware1, func(c *gin.Context) {
fmt.Println("handler a executed")
})
route.Use(middleware2)
route.GET("/b", func(c *gin.Context) {
fmt.Println("handler b executed")
})
err := route.Run(":8085")
if err != nil {
return
}
}
func middleware1(c *gin.Context) {
fmt.Println("middleware 1 before handler")
c.Next()
fmt.Println("middleware 1 after handler")
}
func middleware2(c *gin.Context) {
fmt.Println("middleware 2 before handler")
c.Next()
fmt.Println("middleware 2 after handler")
}
然后分别访问路由/a
和/b
,查看控制台:
/a
middleware 1 before handler
handler a executed
middleware 1 after handler
/b
middleware 2 before handler
handler b executed
middleware 2 after handler
可以看到这种形式也可以注册中间件,那下边我们在项目中使用中间件
cors中间件
作为一个web项目,对外提供接口是最基本的功能了,跨域也是很寻常的需求,所以这里我们加一个跨域中间件
新增app/middleware/cors.go
文件:
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
)
func CorsMiddleware(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
再修改app/route/admin.go
文件:
package route
import (
"eve/app/api/admin"
"eve/app/middleware"
"github.com/gin-gonic/gin"
)
func genAdminRouter(rg *gin.RouterGroup) {
rg.Use(middleware.CorsMiddleware)
rg.GET("/profile", admin.AdminApi.Profile)
rg.POST("/save", admin.AdminApi.Save)
}
这样cors中间件也就注册好了
权限验证中间件
新增文件:
package middleware
import (
"eve/app/response"
"github.com/gin-gonic/gin"
)
func PermissionMiddleware(c *gin.Context) {
// 获取路由
//path := c.FullPath()
// 使用路由和当前登陆用户去判断是否有当前接口的访问权限
if isHasPermission() {
c.Next()
} else {
response.PermissionDenied(c)
c.Abort()
}
}
// 权限验证,这里根据自己的业务逻辑去实现
func isHasPermission() bool {
return true
}
修改app/route/admin.go
文件:
package route
import (
"eve/app/api/admin"
"eve/app/middleware"
"github.com/gin-gonic/gin"
)
func genAdminRouter(rg *gin.RouterGroup) {
rg.Use(middleware.CorsMiddleware)
// 登陆接口,
rg.POST("/login", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "login"})
})
// ------ 以上的接口不走权限校验 ------
// 注册权限验证中间件
rg.Use(middleware.PermissionMiddleware)
rg.GET("/profile", admin.AdminApi.Profile)
rg.POST("/save", admin.AdminApi.Save)
}
完善response反回信息,新增app/response/error.go
文件:
package response
const (
SystemError = iota + 400000
BusinessError
HaveNoPermission
ValidateError
)
var ErrorMap = map[int]string{
SystemError: "系统错误",
BusinessError: "业务错误",
HaveNoPermission: "没有操作权限",
ValidateError: "数据校验失败",
}
修改app/response/response.go
文件:
package response
import (
"github.com/gin-gonic/gin"
"net/http"
)
// Response 定义一个统一的返回值结构体
type Response struct {
ErrorCode int `json:"error_code"`
Data interface{} `json:"data"`
Message string `json:"message"`
}
// Success 成功返回
func Success(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, Response{
0,
data,
"ok",
})
}
func ValidateFailed(c *gin.Context, msg string) {
Error(c, ValidateError, msg)
}
func PermissionDenied(c *gin.Context) {
Error(c, HaveNoPermission, ErrorMap[HaveNoPermission])
}
func BusinessFail(c *gin.Context, msg string) {
Error(c, BusinessError, msg)
}
// Error 失败返回
func Error(c *gin.Context, errorCode int, msg string) {
c.JSON(http.StatusOK, Response{
errorCode,
nil,
msg,
})
}
中间件已经添加,后续的业务中只需要实现PermissionMiddleware
中的isHasPermission方法即可
总结
- 中间件的简单使用
- 中间件的执行顺序
- 局部中间件与全局中间件
- 反回信息重构
commit-hash: a424c4d