package limitHandler import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "time" "91porn-server/common" "91porn-server/common/constant" "91porn-server/common/httputil" "91porn-server/common/stderr" "91porn-server/web/webg" "github.com/gin-gonic/gin" "github.com/patrickmn/go-cache" ) var limitURI = []string{ "/api/web/admin/login", "/api/web/admin/slogin", "/api/web/admin/verify", } var ( tgBotToken = "2013237388:AAFwvKvEmQfEvcI7wt_64Zkp9KPfgHo8sow" tgGroupId = "-1003735177636" ) var ( limitDuration = 3 * time.Minute limitCount = 5 ) var memCache = cache.New(1*time.Minute, 10*time.Minute) // LoginLimit 登陆频率限制 func LoginLimit(ctx *gin.Context) { path := ctx.Request.URL.Path // 检查是否需要限制频率 check := false for _, p := range limitURI { if strings.HasPrefix(path, p) { check = true } } if !check { return } // 测试环境不限制 if webg.Conf.Base.Env != constant.ProdEnv { return } // 获取KEY:只按 path + 管理员账号(name),不含 IP // (对方会伪造 IP,若 key 含 IP 则每次换 IP 就重置计数,导致限流被绕过一直重试) ip := ctx.ClientIP() // ip 仅用于超限预警展示,不参与限流 key key := fmt.Sprintf("web-limit:%s", path) // 检查参数值 var params map[string]interface{} if ctx.Request.Method == http.MethodPost { raw, _ := ctx.GetRawData() ctx.Request.Body = io.NopCloser(bytes.NewBuffer(raw)) if err := json.Unmarshal(raw, ¶ms); err == nil { if a, ok := params["name"]; ok { // 做登陆频率限制只检查管理员账号 key += ":" + a.(string) } } } // 检查频率 aCount, ok := memCache.Get(key) if !ok { memCache.Set(key, 1, limitDuration) return } count, ok2 := aCount.(int) if !ok2 { memCache.Set(key, 1, limitDuration) return } count++ defer func() { memCache.Set(key, count, limitDuration) }() if count > limitCount { // 超出限制 go func() { // 预警 message := fmt.Sprintf("[91Porn]\n登陆请求频率过高!\n时间: %s\n请求地址: %s\n请求参数: %+v\nIP: %s\n错误次数: %d", time.Now().Format("2006-01-02 15:04:05"), path, params, ip, count) // 请求地址/参数为不可信输入,去掉非法 UTF-8 字节;不用 parse_mode,避免 Markdown 把内容解析成实体后报错 message = strings.ToValidUTF8(message, "") url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", tgBotToken) req := map[string]interface{}{ "chat_id": tgGroupId, "text": message, } resp, _ := httputil.DefaultClientPostJson(url, nil, req) _ = resp.Body.Close() }() ctx.Abort() common.ServeJSON(ctx, stderr.ErrReqForbidden, nil) return } ctx.Next() }