package limitHandler import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "time" "videoweb/pub/common" httpUtil "videoweb/pub/common/http" "videoweb/pub/common/srvdef" "videoweb/web/webg" "github.com/gin-gonic/gin" "github.com/patrickmn/go-cache" ) var limitURI = []string{ "/api/web/login/slogin", "/api/web/login/acc", } var ( tgBotToken = "2013237388:AAFwvKvEmQfEvcI7wt_64Zkp9KPfgHo8sow" tgGroupId = "-1003735177636" ) var ( limitDuration = time.Minute limitCount = 5 ) var memCache = cache.New(1*time.Minute, 10*time.Minute) // LoginLimit 登陆频率限制:按账号维度限制,不依赖 ClientIP(可被伪造)。 func LoginLimit(ctx *gin.Context) { path := ctx.Request.URL.Path check := false for _, p := range limitURI { if strings.HasPrefix(path, p) { check = true break } } if !check { ctx.Next() return } // 测试环境不限制 if webg.Conf.RunMod != "prod" { ctx.Next() return } var params map[string]interface{} var acc string 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["acc"].(string); ok { acc = strings.TrimSpace(a) } } } // 无账号时无法按账号限流,交由后续登录逻辑处理参数错误 if acc == "" { ctx.Next() return } key := fmt.Sprintf("web-limit:%s:%s", path, acc) count := 1 if aCount, ok := memCache.Get(key); ok { if n, ok2 := aCount.(int); ok2 { count = n + 1 } } memCache.Set(key, count, limitDuration) if count > limitCount { go func(acc, path string, count int, params map[string]interface{}) { message := fmt.Sprintf( "[91porn精选]\n登陆请求频率过高!\n时间: %s\n请求地址: %s\n账号: %s\n请求参数: %+v\n错误次数: %d", time.Now().Format("2006-01-02 15:04:05"), path, acc, params, 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, } jsonBytes, err := json.Marshal(req) if err != nil { return } header := map[string]string{"Content-Type": "application/json"} resp, _ := httpUtil.Post(url, header, bytes.NewReader(jsonBytes)) _ = resp.Data }(acc, path, count, params) common.ServerJSON(ctx, srvdef.ErrReqForbidden, nil) ctx.Abort() return } ctx.Next() }