优化限流功能
This commit is contained in:
@@ -8,7 +8,6 @@ import (
|
|||||||
"gitee.com/red-future---jilin-g/common/utils"
|
"gitee.com/red-future---jilin-g/common/utils"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/net/ghttp"
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,46 +62,23 @@ func IPLimiter(r *ghttp.Request) {
|
|||||||
|
|
||||||
// UserLimiter 用户维度限流中间件(防止单用户滥用)
|
// UserLimiter 用户维度限流中间件(防止单用户滥用)
|
||||||
func UserLimiter(r *ghttp.Request) {
|
func UserLimiter(r *ghttp.Request) {
|
||||||
// 从JWT获取用户ID(如果已登录)
|
var userName string
|
||||||
var userId string
|
user, err := utils.GetUserInfo(r.GetCtx())
|
||||||
var isAuth bool = false
|
if err != nil {
|
||||||
|
r.Response.WriteStatusExit(429, err.Error())
|
||||||
if token := r.Header.Get("Authorization"); token != "" && gstr.HasPrefix(token, "Bearer ") {
|
return
|
||||||
// 这里应该解析JWT获取用户ID,简化示例中直接使用token
|
|
||||||
tokenStr := gstr.SubStrFrom(token, "7")
|
|
||||||
if tokenStr != "" && validateToken(tokenStr) {
|
|
||||||
userId = tokenStr
|
|
||||||
isAuth = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
userName = gconv.String(user.UserName)
|
||||||
// 如果没有userId,使用IP作为标识
|
|
||||||
if userId == "" {
|
|
||||||
userId = "anon:" + r.GetClientIp()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从配置文件读取用户限流参数
|
// 从配置文件读取用户限流参数
|
||||||
var userLimit int64
|
userLimit := g.Cfg().MustGet(r.GetCtx(), "rate.user.limit", 50).Int64()
|
||||||
if isAuth {
|
key := fmt.Sprintf(redis.RateLimitKeyUser, userName)
|
||||||
userLimit = g.Cfg().MustGet(r.GetCtx(), "rate.user.authenticated.limit", 50).Int64()
|
|
||||||
} else {
|
|
||||||
userLimit = g.Cfg().MustGet(r.GetCtx(), "rate.user.anonymous.limit", 20).Int64()
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf(redis.RateLimitKeyUser, userId)
|
|
||||||
count, err := redis.IncrRateLimit(r.GetCtx(), key, 1)
|
count, err := redis.IncrRateLimit(r.GetCtx(), key, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.Log().Errorf(r.GetCtx(), "用户限流Redis错误: %v", err)
|
g.Log().Errorf(r.GetCtx(), "用户限流Redis错误: %v", err)
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if count > userLimit {
|
if count > userLimit {
|
||||||
userType := "已登录"
|
|
||||||
if !isAuth {
|
|
||||||
userType = "未登录"
|
|
||||||
}
|
|
||||||
g.Log().Warningf(r.GetCtx(), "用户限流触发: %s, count: %d, limit: %d, type: %s", userId, count, userLimit, userType)
|
|
||||||
r.Response.WriteStatusExit(429, "您的请求过于频繁,请稍后再试")
|
r.Response.WriteStatusExit(429, "您的请求过于频繁,请稍后再试")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -147,153 +123,3 @@ func ServiceLimiter(r *ghttp.Request) {
|
|||||||
|
|
||||||
r.Middleware.Next()
|
r.Middleware.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderCreateLimiter 订单创建限流中间件
|
|
||||||
// 限制: 每个用户每分钟最多创建10个订单
|
|
||||||
func OrderCreateLimiter(r *ghttp.Request) {
|
|
||||||
userId := getUserIdFromContext(r) // 从context获取用户ID
|
|
||||||
if userId == "" {
|
|
||||||
// 如果无法获取用户信息,跳过限流检查
|
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf(redis.RateLimitKeyOrder, userId)
|
|
||||||
|
|
||||||
// 限制: 每个用户每分钟最多创建10个订单
|
|
||||||
count, err := redis.IncrRateLimit(r.GetCtx(), key, 60) // 60秒窗口
|
|
||||||
if err != nil {
|
|
||||||
g.Log().Errorf(r.GetCtx(), "订单创建限流Redis错误: %v", err)
|
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 10 {
|
|
||||||
g.Log().Warningf(r.GetCtx(), "订单创建限流触发: %s, count: %d", userId, count)
|
|
||||||
r.Response.WriteJsonExit(ghttp.DefaultHandlerResponse{
|
|
||||||
Code: 429,
|
|
||||||
Message: "下单过于频繁,请稍后再试",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Middleware.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WalletTransferLimiter 钱包转账限流中间件
|
|
||||||
// 限制: 每个用户每分钟最多转账5次
|
|
||||||
func WalletTransferLimiter(r *ghttp.Request) {
|
|
||||||
userId := getUserIdFromContext(r) // 从context获取用户ID
|
|
||||||
if userId == "" {
|
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf(redis.RateLimitKeyTransfer, userId)
|
|
||||||
|
|
||||||
// 限制: 每个用户每分钟最多转账5次
|
|
||||||
count, err := redis.IncrRateLimit(r.GetCtx(), key, 60) // 60秒窗口
|
|
||||||
if err != nil {
|
|
||||||
g.Log().Errorf(r.GetCtx(), "钱包转账限流Redis错误: %v", err)
|
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 5 {
|
|
||||||
g.Log().Warningf(r.GetCtx(), "钱包转账限流触发: %s, count: %d", userId, count)
|
|
||||||
r.Response.WriteJsonExit(ghttp.DefaultHandlerResponse{
|
|
||||||
Code: 429,
|
|
||||||
Message: "转账操作过于频繁,请稍后再试",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Middleware.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CSMessageLimiter 客服消息限流中间件
|
|
||||||
// 限制: 每个用户每分钟最多发送30条消息
|
|
||||||
func CSMessageLimiter(r *ghttp.Request) {
|
|
||||||
userId := getUserIdFromContext(r) // 从context获取用户ID
|
|
||||||
if userId == "" {
|
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf(redis.RateLimitKeyMessage, userId)
|
|
||||||
|
|
||||||
// 限制: 每个用户每分钟最多发送30条消息
|
|
||||||
count, err := redis.IncrRateLimit(r.GetCtx(), key, 60) // 60秒窗口
|
|
||||||
if err != nil {
|
|
||||||
g.Log().Errorf(r.GetCtx(), "客服消息限流Redis错误: %v", err)
|
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 30 {
|
|
||||||
g.Log().Warningf(r.GetCtx(), "客服消息限流触发: %s, count: %d", userId, count)
|
|
||||||
r.Response.WriteJsonExit(ghttp.DefaultHandlerResponse{
|
|
||||||
Code: 429,
|
|
||||||
Message: "消息发送过于频繁,请稍后再试",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Middleware.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// OSSUploadLimiter 文件上传限流中间件
|
|
||||||
// 限制: 每个用户每分钟最多上传10个文件
|
|
||||||
func OSSUploadLimiter(r *ghttp.Request) {
|
|
||||||
userId := getUserIdFromContext(r) // 从context获取用户ID
|
|
||||||
if userId == "" {
|
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf(redis.RateLimitKeyUpload, userId)
|
|
||||||
|
|
||||||
// 限制: 每个用户每分钟最多上传10个文件
|
|
||||||
count, err := redis.IncrRateLimit(r.GetCtx(), key, 60) // 60秒窗口
|
|
||||||
if err != nil {
|
|
||||||
g.Log().Errorf(r.GetCtx(), "文件上传限流Redis错误: %v", err)
|
|
||||||
r.Middleware.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if count > 10 {
|
|
||||||
g.Log().Warningf(r.GetCtx(), "文件上传限流触发: %s, count: %d", userId, count)
|
|
||||||
r.Response.WriteJsonExit(ghttp.DefaultHandlerResponse{
|
|
||||||
Code: 429,
|
|
||||||
Message: "文件上传过于频繁,请稍后再试",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Middleware.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUserIdFromContext 从请求上下文中获取用户ID
|
|
||||||
// 使用项目中已有的utils.GetUserInfo方法
|
|
||||||
func getUserIdFromContext(r *ghttp.Request) string {
|
|
||||||
// 使用项目中已有的utils.GetUserInfo方法获取用户信息
|
|
||||||
user, err := utils.GetUserInfo(r.GetCtx())
|
|
||||||
if err != nil {
|
|
||||||
// 如果获取用户信息失败,返回空字符串
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在这个项目中,UserName就是用来标识用户的ID
|
|
||||||
// 转换为字符串类型
|
|
||||||
if user.UserName != nil {
|
|
||||||
return gconv.String(user.UserName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateToken 验证token有效性
|
|
||||||
func validateToken(token string) bool {
|
|
||||||
// 实现 token 验证逻辑
|
|
||||||
return token == "valid-token"
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user