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
}