feat: 从黄果 NET hgdjnet-server 9bfbafe 导入 HKDJ 官网后端

来源:hgdjnet-server 9bfbafe10d417deea959bd53117bc88a10e60a83
项目身份 HKDJ / JHA-216,独立官网仓库,Jenkins 守卫键 hkdjnet
测试目录 /server/hkdj_net,App/Web API 30331/30332

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 11:20:04 +08:00
co-authored by Claude Opus 4.7
parent 99a4854def
commit 85c0071fd8
653 changed files with 85000 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
package limitHandler
import (
"bytes"
"encoding/json"
"fmt"
"hkdj-web-server/pub/common"
httpUtil "hkdj-web-server/pub/common/http"
"hkdj-web-server/pub/common/srvdef"
"hkdj-web-server/web/webg"
"io"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/patrickmn/go-cache"
)
var limitURI = []string{
"/api/web/login/slogin",
"/api/web/login/acc",
}
var (
tgBotToken = ""
tgGroupId = ""
)
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, &params); 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(
"[91猎艳]\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, "")
if tgBotToken == "" || tgGroupId == "" {
return
}
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()
}