4.3 KiB
4.3 KiB
第 5 章 Gin 中间件
学完本章你将:理解中间件洋葱模型、编写自定义中间件、掌握 Recovery/Logger/CORS/鉴权等常用中间件。
5.1 中间件原理
中间件是 func(*gin.Context),通过 c.Next() 控制调用链,形成洋葱模型:
请求 -> MW1 -> MW2 -> Handler -> MW2 -> MW1 -> 响应
func MW1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("MW1 before")
c.Next() // 调用后续
fmt.Println("MW1 after")
}
}
r.Use(MW1(), MW2())
若不调用 c.Next() 或调用 c.Abort(),则中断后续。c.AbortWithStatusJSON 直接返回响应。
5.2 注册级别
// 全局
r.Use(middleware.ZapLogger())
// 分组
v1 := r.Group("/api/v1", authMiddleware())
authed := r.Group("/admin")
authed.Use(authMiddleware())
// 单路由
r.GET("/secret", authMiddleware(), secretHandler)
本项目 router.New 中三件套全局注册:Recovery、ZapLogger、Cors。
5.3 Recovery 恢复 panic
internal/middleware/recovery.go 源码精读:
func Recovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
logger.Log.Error("panic recovered", ...)
c.AbortWithStatusJSON(500, gin.H{"code":500,"msg":"internal server error"})
}
}()
c.Next()
}
}
- 防止单请求 panic 导致进程崩溃
- 记录错误日志并返回 500
- 业务代码中不要滥用 panic
5.4 ZapLogger 请求日志
internal/middleware/logger.go:
func ZapLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
latency := time.Since(start)
logger.Log.Info("request",
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.Int("status", c.Writer.Status()),
zap.Duration("latency", latency),
)
}
}
记录方法、路径、状态码、延迟、IP、User-Agent。生产可在此追加 traceID。
5.5 CORS 跨域
internal/middleware/cors.go 核心逻辑:
- 根据
config.CorsConfig.AllowOrigins判断是否允许 Origin - 设置
Access-Control-Allow-Origin/Methods/Headers OPTIONS预检请求直接204返回
配置见 config/config.yaml:
cors:
allow_origins: ["*"]
allow_methods: ["GET","POST","PUT","DELETE","OPTIONS"]
生产建议收紧为具体域名,避免 * + Allow-Credentials: true 的组合(浏览器会拦截)。
5.6 自定义鉴权中间件示例
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(401, gin.H{"code": 401, "msg": "unauthorized"})
return
}
// 校验 token,解析用户
c.Set("userID", 123) // 传递给后续 handler
c.Next()
}
}
// handler 中获取
func Me(c *gin.Context) {
uid, _ := c.Get("userID")
c.JSON(200, gin.H{"userID": uid})
}
本项目已引入 github.com/golang-jwt/jwt/v5 与 casbin,可在鉴权 middleware 中集成 JWT 校验 + Casbin 鉴权。
5.7 限流/超时/请求ID(扩展)
// 请求ID
func RequestID() gin.HandlerFunc {
return func(c *gin.Context) {
id := c.GetHeader("X-Request-ID")
if id == "" { id = uuid.NewString() }
c.Set("requestID", id)
c.Header("X-Request-ID", id)
c.Next()
}
}
// 超时
func Timeout(d time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), d)
defer cancel()
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
5.8 常见坑
- 中间件顺序重要:Recovery 应最先注册,保证捕获所有后续 panic
c.Abort()后仍会执行已进入的中间件的c.Next()之后代码,需注意逻辑- 不要在中间件中做过重同步操作,会阻塞所有请求
5.9 动手练习
- 编写
RequestID中间件并在日志中打印。 - 编写
Auth中间件,校验Authorization: Bearer <token>,失败返回 401。 - 调整本项目
router.New中间件顺序,观察 panic 时日志差异。