fastGin脚手架——中间件部分
web系统的灵魂所在,用好了中间件,可以做到事半功倍的效果 认证中间件 通过登录接口颁发token 访问需要认证的接口,就把token放到请求头上,中间件进行验证token是否有效,是否过期
fastGin脚手架——中间件部分
发布时间:2025-01-17 (2025-01-17)

web系统的灵魂所在,用好了中间件,可以做到事半功倍的效果

认证中间件

通过登录接口颁发token

访问需要认证的接口,就把token放到请求头上,中间件进行验证token是否有效,是否过期

不合法的直接拦截,走不到视图层,合法的就再往下走到视图层

package middleware

import (
  "fast_gin/utils/jwts"
  "github.com/gin-gonic/gin"
)

func AuthMiddleware(c *gin.Context) {
  token := c.GetHeader("token")
  _, err := jwts.CheckToken(token)
  if err != nil {
    c.JSON(200, gin.H{"code": 7, "msg": "认证失败", "data": gin.H{}})
    c.Abort()
    return
  }
  c.Next()
}

func AdminMiddleware(c *gin.Context) {
  token := c.GetHeader("token")
  claims, err := jwts.CheckToken(token)
  if err != nil {
    c.JSON(200, gin.H{"code": 7, "msg": "认证失败", "data": gin.H{}})
    c.Abort()
    return
  }
  if claims.RoleID != 1 {
    c.JSON(200, gin.H{"code": 7, "msg": "角色认证失败", "data": gin.H{}})
    c.Abort()
    return
  }
  c.Next()
}

限流中间件

为什么要限流?

去过景区就知道,景区一般要预约,目的就是为了去限流

有很多

https://blog.csdn.net/YouMing_Li/article/details/141787087

基于ip的限流

https://www.jb51.net/article/281431.htm

package middleware

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

func LimitMiddleware(limit int) gin.HandlerFunc {
  return NewLimiter(limit, 1*time.Second).Middleware
}

// NewLimiter 创建限流器
func NewLimiter(limit int, duration time.Duration) *Limiter {
  return &Limiter{
    limit:      limit,
    duration:   duration,
    timestamps: make(map[string][]int64),
  }
}

// Limiter 限流器
type Limiter struct {
  limit      int                // 限制的请求数量
  duration   time.Duration      // 时间窗口
  timestamps map[string][]int64 // 请求的时间戳
}

// Middleware 限流中间件
func (l *Limiter) Middleware(c *gin.Context) {
  ip := c.ClientIP() // 获取客户端IP地址

  // 检查请求时间戳切片是否存在
  if _, ok := l.timestamps[ip]; !ok {
    l.timestamps[ip] = make([]int64, 0)
  }

  now := time.Now().Unix() // 当前时间戳

  // 移除过期的请求时间戳
  for i := 0; i < len(l.timestamps[ip]); i++ {
    if l.timestamps[ip][i] < now-int64(l.duration.Seconds()) {
      l.timestamps[ip] = append(l.timestamps[ip][:i], l.timestamps[ip][i+1:]...)
      i--
    }
  }

  // 检查请求数量是否超过限制
  if len(l.timestamps[ip]) >= l.limit {
    c.JSON(429, gin.H{
      "code": 9,
      "data": gin.H{},
      "msg":  "Too Many Requests",
    })
    c.Abort()
    return
  }

  // 添加当前请求时间戳到切片
  l.timestamps[ip] = append(l.timestamps[ip], now)

  // 继续处理请求
  c.Next()
}

参数绑定中间件

先看看之前是怎么进行参数绑定的

都是在视图层做参数绑定

type LoginRequest struct {
  Username string `json:"username" binding:"required" label:"用户名"`
  Password string `json:"password" binding:"required" label:"密码"`
}

func (UserApi) LoginView(c *gin.Context) {
  var cr LoginRequest
  err := c.ShouldBindJSON(&cr)
  if err != nil {
    res.FailWithError(err, c)
    return
  }
  fmt.Println(cr)
  res.OkWithData("用户登录", c)
  return
}

然后就会发现 参数绑定的步骤都是一样的,只有映射的结构体类型不一样

那么我们可以写一个泛型函数,来进行参数绑定

func BindJson[T any](c *gin.Context) (cr T, err error) {
  err = c.ShouldBindJSON(&cr)
  if err != nil {
    res.FailWithError(err, c)
    return 
  }
  return 
}

func (UserApi) LoginView(c *gin.Context) {
  cr, err := BindJson[LoginRequest](c)
  if err != nil {
    return
  }
  fmt.Println(cr)
  res.OkWithData("用户登录", c)
  return
}

然后你会发现,之前6行代码变成了4行,那个err的判断还是少不了

那么怎么样可以把那个err也给抽离出去呢

我们可以从之前的中间件上下手

如果在中间件里面我们去进行参数绑定,绑定失败直接返回,绑定成功才往下走,那么到视图函数这一层,一定是有参数的

package middleware

import (
  "fast_gin/utils/res"
  "github.com/gin-gonic/gin"
)

func BindJsonMiddleware[T any](c *gin.Context) {
  var cr T
  err := c.ShouldBindJSON(&cr)
  if err != nil {
    res.FailWithError(err, c)
    c.Abort()
    return
  }
  c.Set("request", cr)
  return
}

func BindQueryMiddleware[T any](c *gin.Context) {
  var cr T
  err := c.ShouldBindQuery(&cr)
  if err != nil {
    res.FailWithError(err, c)
    c.Abort()
    return
  }
  c.Set("request", cr)
  return
}
func BindUriMiddleware[T any](c *gin.Context) {
  var cr T
  err := c.ShouldBindUri(&cr)
  if err != nil {
    res.FailWithError(err, c)
    c.Abort()
    return
  }
  c.Set("request", cr)
  return
}

func GetBind[T any](c *gin.Context) (cr T) {
  return c.MustGet("request").(T)
}

路由上使用

g.POST("users/login", middleware.LimitMiddleware(2), middleware.BindJsonMiddleware[user_api.LoginRequest], app.LoginView)

视图里面使用

type LoginRequest struct {
  Username string `json:"username" binding:"required" label:"用户名"`
  Password string `json:"password" binding:"required" label:"密码"`
}

func (UserApi) LoginView(c *gin.Context) {
  cr := middleware.GetBind[LoginRequest](c)
  fmt.Println(cr)
  res.OkWithData("用户登录", c)
  return
}