4、中间件开发:Gin中间件原理、自定义日志中间件、鉴权中间件、CORS中间件、中间件的执行顺序
4.1 Gin 中间件核心原理
Gin 框架的中间件(Middleware)本质是一个处理 HTTP 请求的函数链。每个中间件都可以在请求到达最终处理函数之前或之后执行逻辑,形成经典的“洋葱模型”。
核心接口:Gin 中间件的签名是
func(c *gin.Context)。通过调用 c.Next() 将控制权传递给下一个中间件或处理函数;通过 c.Abort() 终止后续处理。
中间件通过 r.Use() 注册到路由组或全局路由上。Gin 内部维护一个 Handlers 链,每个请求到来时按注册顺序依次执行。
4.2 自定义日志中间件
日志中间件用于记录每个 HTTP 请求的方法、路径、状态码、耗时等信息。Gin 默认提供了 gin.Logger(),但自定义日志可以更灵活地控制输出格式和存储目标。
// 自定义日志中间件
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求开始时间
start := time.Now()
path := c.Request.URL.Path
method := c.Request.Method
// 处理请求
c.Next()
// 请求结束后记录
latency := time.Since(start)
statusCode := c.Writer.Status()
clientIP := c.ClientIP()
log.Printf("[%s] %s %s %d %v",
method, path, clientIP, statusCode, latency)
}
}
最佳实践:生产环境中建议将日志写入文件或集中式日志系统(如 ELK、Loki),而非标准输出。可使用
io.MultiWriter 同时输出到控制台和文件。
4.3 鉴权中间件
鉴权中间件用于验证请求的合法性,常见方式包括 JWT(JSON Web Token)验证、API Key 验证、OAuth2 代理验证等。以下是一个基于 JWT 的鉴权中间件示例:
// JWT 鉴权中间件
func AuthMiddleware(secret string) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.JSON(401, gin.H{"error": "缺少认证令牌"})
c.Abort()
return
}
// 去除 "Bearer " 前缀
if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
tokenString = tokenString[7:]
}
// 解析和验证 JWT
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("非预期的签名方法: %v", token.Header["alg"])
}
return []byte(secret), nil
})
if err != nil || !token.Valid {
c.JSON(401, gin.H{"error": "无效的认证令牌"})
c.Abort()
return
}
// 将用户信息存入上下文,供后续处理函数使用
claims, _ := token.Claims.(jwt.MapClaims)
c.Set("userID", claims["user_id"])
c.Set("username", claims["username"])
c.Next()
}
}
安全注意:鉴权中间件应尽早执行(通常放在中间件链的前端),避免未授权请求进入后续处理逻辑。同时,对于公开接口(如登录、注册),应使用
r.GET("/login", handler) 绕过鉴权中间件。
4.4 CORS 中间件
CORS(跨域资源共享)中间件用于处理浏览器跨域请求。当前端应用与后端 API 部署在不同域名或端口时,必须配置 CORS 策略。
// CORS 中间件
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*") // 生产环境应指定具体域名
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization")
c.Header("Access-Control-Max-Age", "86400")
// 处理预检请求
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
| CORS 头部 | 说明 | 推荐值 |
|---|---|---|
Access-Control-Allow-Origin |
允许的源 | 生产环境指定具体域名,如 https://example.com |
Access-Control-Allow-Methods |
允许的 HTTP 方法 | GET, POST, PUT, DELETE, OPTIONS |
Access-Control-Allow-Headers |
允许的自定义头部 | Origin, Content-Type, Authorization |
Access-Control-Max-Age |
预检请求缓存时间(秒) | 86400(24小时) |
4.5 中间件的执行顺序
中间件的执行顺序遵循“先注册先执行”的原则,但通过 c.Next() 和 c.Abort() 可以控制流程。理解执行顺序对于正确编写中间件至关重要。
4.6 中间件注册与组合
Gin 支持在全局、路由组和单个路由三个层级注册中间件,灵活控制作用范围。
func main() {
r := gin.New()
// 全局中间件(所有路由生效)
r.Use(LoggerMiddleware())
r.Use(gin.Recovery())
// 路由组中间件(组内路由生效)
api := r.Group("/api")
api.Use(CORSMiddleware())
{
// 公开接口
api.POST("/login", loginHandler)
// 需要鉴权的子组
authorized := api.Group("/v1")
authorized.Use(AuthMiddleware("my-secret-key"))
{
authorized.GET("/users", getUsersHandler)
authorized.POST("/orders", createOrderHandler)
}
}
// 单个路由中间件
r.GET("/health", gin.BasicAuth(gin.Accounts{
"admin": "password123",
}), healthHandler)
r.Run(":8080")
}
执行顺序总结:
- 全局中间件按注册顺序执行。
- 路由组中间件在全局中间件之后、处理函数之前执行。
- 单个路由中间件在路由组中间件之后执行。
- 同一层级按
Use()注册顺序执行。 c.Next()将控制权交给下一个处理器;c.Abort()终止后续处理器。
4.7 中间件开发最佳实践
| 实践要点 | 说明 |
|---|---|
| 尽早返回错误 | 鉴权、限流等中间件应放在链的前端,避免无效请求消耗资源 |
使用 c.Set() 传递数据 |
中间件处理结果(如用户信息)通过上下文传递给后续处理器 |
| 避免在中间件中修改请求体 | 请求体只能读取一次,如需多次读取应使用 c.Request.Body 缓存 |
| 注意 goroutine 安全 | 中间件中启动 goroutine 时,必须复制 c.Copy() 上下文 |
| 保持中间件无状态 | 中间件应尽量无状态,或通过依赖注入管理共享状态 |
生产建议:使用
gin.New() 而非 gin.Default() 创建引擎,手动添加所需中间件,避免引入不必要的默认中间件(如 gin.Logger() 可能泄露敏感信息)。