package util import ( "backend/msg" "bytes" "crypto/aes" "crypto/cipher" crand "crypto/rand" "encoding/base64" "encoding/json" "errors" "fmt" "io" "log" "math/rand" "net" "os" "path/filepath" "reflect" "regexp" "slices" "strconv" "strings" "text/template" "time" "unicode" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "google.golang.org/protobuf/proto" ) var MergeData = make(map[string]interface{}) var MergeEmitData = make(map[string]interface{}) var FrameData = make(map[string]interface{}) var CardData = make(map[string]interface{}) var CardCollectData = make(map[string]interface{}) var NetAssetData = make(map[string]interface{}) var ItemData = make(map[string]interface{}) func init() { data, err := os.ReadFile("config/MergeData.json") if err == nil { var m map[string]interface{} if err := json.Unmarshal(data, &m); err != nil { log.Printf("failed to unmarshal MergeData.json: %v", err) } else { MergeData = m } } data, err = os.ReadFile("config/MergeDataEmit.json") if err == nil { var m map[string]interface{} if err := json.Unmarshal(data, &m); err != nil { log.Printf("failed to unmarshal MergeEmitData.json: %v", err) } else { MergeEmitData = m } } data, err = os.ReadFile("config/Avatar.json") if err == nil { var m map[string]interface{} if err := json.Unmarshal(data, &m); err != nil { log.Printf("failed to unmarshal FrameData.json: %v", err) } else { FrameData = m } } data, err = os.ReadFile("config/CardDetail.json") if err == nil { var m map[string]interface{} if err := json.Unmarshal(data, &m); err != nil { log.Printf("failed to unmarshal CardData.json: %v", err) } else { CardData = m } } data, err = os.ReadFile("config/CardCollect.json") if err == nil { var m map[string]interface{} if err := json.Unmarshal(data, &m); err != nil { log.Printf("failed to unmarshal CardCollectData.json: %v", err) } else { CardCollectData = m } } data, err = os.ReadFile("config/NetAssetData.json") if err == nil { var m map[string]interface{} if err := json.Unmarshal(data, &m); err != nil { log.Printf("failed to unmarshal NetAssetData.json: %v", err) } else { NetAssetData = m } } data, err = os.ReadFile("config/Item.json") if err == nil { var m map[string]interface{} if err := json.Unmarshal(data, &m); err != nil { log.Printf("failed to unmarshal ItemData.json: %v", err) } else { ItemData = m } } } func GetItemName(itemId int) string { id := strconv.Itoa(itemId) if v, ok := ItemData[id]; ok && v != nil { entry, ok := v.(map[string]interface{}) if ok { return String(entry["Name"]) } } return id } // 获取结构体名称 func GetStructName(v interface{}) string { t := reflect.TypeOf(v) if t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() == reflect.Struct { return t.Name() } return "" } func PackMsg(m proto.Message) []byte { buf, _ := proto.Marshal(m) Func := GetStructName(m) req := &msg.AdminReq{ Func: Func, Info: buf, } buf, _ = proto.Marshal(req) return append([]byte{0, 2}, buf...) } func UnpackMsg(buf []byte, n int) (string, error) { // 检查数据长度 if n < 2 { return "", fmt.Errorf("message too short: got %d bytes, need at least 2", n) } res := &msg.AdminRes{} err := proto.Unmarshal(buf[2:n], res) if err != nil { // 记录更详细的错误信息 log.Printf("UnpackMsg error: %v, data length: %d, hex: %x", err, n-2, buf[2:n]) return "", fmt.Errorf("proto unmarshal error: %v", err) } return res.Info, nil } func ParseUid(uid int) (int, int) { AppId := uid / 100000000 ServerId := uid % 100000000 / 100000 return AppId, ServerId } func Int(a interface{}) int { if a == nil { return 0 } switch v := a.(type) { case int: return v case int32: return int(v) case int64: return int(v) case float64: return int(v) case string: r, err := strconv.Atoi(v) if err != nil { return 0 } return r } return 0 } func String(a interface{}) string { if a == nil { return "" } switch v := a.(type) { case string: return v case int: return strconv.Itoa(v) case int32: return strconv.Itoa(int(v)) case int64: return strconv.FormatInt(v, 10) case float64: return strconv.FormatFloat(v, 'f', -1, 64) case []byte: return string(v) default: rv := reflect.ValueOf(a) if !rv.IsValid() { return "" } // unwrap pointer if rv.Kind() == reflect.Ptr && !rv.IsNil() { rv = rv.Elem() } kind := rv.Kind() // if it's a struct/map/slice/array convert to JSON if kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice || kind == reflect.Array { b, err := json.Marshal(a) if err == nil { return string(b) } } return "" } } func InArray(Id int, s []int) bool { for _, v := range s { if v == Id { return true } } return false } func Now() int64 { return time.Now().Unix() } func Year() int { return time.Now().Year() } func NowFormat() string { return time.Now().Format("2006-01-02 15:04:05") } func SendAdminMsg(ws *websocket.Conn, req proto.Message) (map[string]interface{}, error) { reqBuf := PackMsg(req) err := ws.WriteMessage(websocket.BinaryMessage, reqBuf) if err != nil { return nil, fmt.Errorf("failed to write to websocket: %v", err) } _, readbuf, err := ws.ReadMessage() if err != nil { return nil, fmt.Errorf("failed to read from websocket: %v", err) } n := len(readbuf) resBuf, err := UnpackMsg(readbuf, n) if err != nil { return nil, fmt.Errorf("failed to unpack message: %v", err) } r := make(map[string]interface{}) err = json.Unmarshal([]byte(resBuf), &r) if err != nil { log.Printf("Failed to unmarshal response: %v, %s", err, resBuf) return nil, fmt.Errorf("failed to unmarshal response: %v, %s", err, resBuf) } return r, nil } func FloatDecimals(f float64, n int) float64 { format := fmt.Sprintf("%%.%df", n) r, _ := strconv.ParseFloat(fmt.Sprintf(format, f), 64) return r } // 将两个参数转换成浮点数,如果无法转换则为0,实现两个浮点数的除法,除数为0的话,返回0 func FloatDiv(a, b interface{}, Decimal int) float64 { af := toFloat64(a) bf := toFloat64(b) if bf == 0 { return 0 } result := af / bf format := fmt.Sprintf("%%.%df", Decimal) r, _ := strconv.ParseFloat(fmt.Sprintf(format, result), 64) return r } func Decimal(f float64, n int) float64 { format := fmt.Sprintf("%%.%df", n) r, _ := strconv.ParseFloat(fmt.Sprintf(format, f), 64) return r } // 辅助函数:将interface{}转换为float64 func toFloat64(v interface{}) float64 { switch val := v.(type) { case int: return float64(val) case int32: return float64(val) case int64: return float64(val) case float32: return float64(val) case float64: return val case string: r, err := strconv.ParseFloat(val, 64) if err != nil { return 0 } return r default: return 0 } } const ( SECRET_KEY = ")VQbB(vpy=U(wcp)" ) // 加密字符串 func Encrypt(plainText string) (string, error) { block, err := aes.NewCipher([]byte(SECRET_KEY)) if err != nil { return "", err } cipherText := make([]byte, aes.BlockSize+len(plainText)) iv := cipherText[:aes.BlockSize] if _, err := io.ReadFull(crand.Reader, iv); err != nil { return "", err } stream := cipher.NewCFBEncrypter(block, iv) stream.XORKeyStream(cipherText[aes.BlockSize:], []byte(plainText)) return base64.URLEncoding.EncodeToString(cipherText), nil } // 解密字符串 func Decrypt(cipherText string) (string, error) { cipherTextBytes, err := base64.URLEncoding.DecodeString(cipherText) if err != nil { return "", err } block, err := aes.NewCipher([]byte(SECRET_KEY)) if err != nil { return "", err } if len(cipherTextBytes) < aes.BlockSize { return "", fmt.Errorf("cipherText too short") } iv := cipherTextBytes[:aes.BlockSize] cipherTextBytes = cipherTextBytes[aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(cipherTextBytes, cipherTextBytes) return string(cipherTextBytes), nil } func ParseTmpl(file string, data interface{}) string { t, err := template.ParseFiles(file) if err != nil { log.Fatalf("failed to parse template file: %v", err) } var buf bytes.Buffer err = t.Execute(&buf, data) if err != nil { log.Fatalf("failed to execute template: %v", err) } s := buf.String() return s } // 将字符串转换成一行的字符串格式,并再进行一次转义输出 func ToTmplStr(input string) string { // 转义换行符 escapedStr := strings.ReplaceAll(input, "\r", "") escapedStr = strings.ReplaceAll(escapedStr, "\n", "\\n") escapedStr = strings.ReplaceAll(escapedStr, "\"", "\\\"") // 转义输出 // return template.HTMLEscapeString(escapedStr) return escapedStr } func Ternary(condition bool, trueVal, falseVal interface{}) interface{} { if condition { return trueVal } return falseVal } func TimestampToDate(timestamp int64, Tz string) string { loc, err := time.LoadLocation(Tz) if err != nil { log.Printf("failed to load location %s: %v", Tz, err) loc = time.UTC // 默认使用UTC } t := time.Unix(timestamp, 0).In(loc) return t.Format("2006-01-02") } func TimestampToDateTime(timestamp int64, Tz string) string { loc, err := time.LoadLocation(Tz) if err != nil { log.Printf("failed to load location %s: %v", Tz, err) loc = time.UTC // 默认使用UTC } t := time.Unix(timestamp, 0).In(loc) return t.Format("2006-01-02 15:04:05") } func GenerateToken() string { // 生成一个安全的随机token,适合网页登录 b := make([]byte, 32) if _, err := crand.Read(b); err != nil { return "" } return base64.URLEncoding.EncodeToString(b) } func GeneratedCode(Phone string) (string, error) { Code := Rand6DigitNumber() err := SmsCode(Phone, Code) if err != nil { return "", fmt.Errorf("failed to send SMS code: %v", err) } return Code, err } func Rand6DigitNumber() string { n := rand.Intn(1000000) return fmt.Sprintf("%06d", n) } func GetToken(c *gin.Context) string { // 从请求头中获取Token token := c.GetHeader("Authorization") if len(token) > 7 && token[:7] == "Bearer " { token = token[7:] // 去掉"Bearer "前缀 } return token } const ( RoleSuper = "super" RoleAdmin = "admin" RoleUser = "user" RoleWbTransfer = "wb_transfer" RoleGuest = "guest" ) func GetRole(code int) string { switch code { case 0: return RoleSuper case 1: return RoleAdmin case 2: return RoleUser case 99: return RoleWbTransfer default: return RoleGuest } } func ToJson(v interface{}) string { data, err := json.Marshal(v) if err != nil { log.Printf("failed to marshal to JSON: %v", err) return "" } return string(data) } func AddAdminLog(c *gin.Context, action string, params interface{}) { admin := c.GetString("admin") ip := c.ClientIP() db := MPool.GetGameDB() defer db.Close() _, err := db.Exec("INSERT INTO admin_log (admin, action, params, ip, createTime) VALUES (?, ?, ?, ?, ?)", admin, action, ToJson(params), ip, Now()) if err != nil { fmt.Printf("failed to insert admin log: %v", err) return } } func ParseParam(s string) map[string]interface{} { result := make(map[string]interface{}) if strings.TrimSpace(s) == "" { return result } if err := json.Unmarshal([]byte(s), &result); err != nil { return map[string]interface{}{} } return result } func CheckContainChess(ChessList []string, Emit []string) bool { if len(Emit) == 0 { return true } for _, c := range ChessList { d, ok := MergeData[c] if !ok { continue } if d == nil { continue } m, ok := d.(map[string]interface{}) if !ok { continue } Color := m["Color"].(string) Serise := getSeriseByColor(Color) if InArrayStr(Serise, Emit) { return true } } return false } func InArrayStr(s string, arr []string) bool { return slices.Contains(arr, s) } func getSeriseByColor(Color string) string { for k, v := range MergeEmitData { d := v.(map[string]interface{}) colors, ok := d["Order_Type"].(string) if !ok { continue } colorList := strings.Split(colors, ",") if InArrayStr(Color, colorList) { return k } } return Color } func Success(c *gin.Context, message string) { c.JSON(200, gin.H{ "code": 0, "message": message, }) } func LoginSuccess(c *gin.Context, message string, Port int, Host string, ServerId int) { c.JSON(200, gin.H{ "code": 0, "message": message, "Port": Port, "Host": Host, "ServerId": ServerId, }) } func GetChessURL(ChessId string) string { key := fmt.Sprintf("UI_MergeData_%s", ChessId) return GetLanguageImageURL(key) } func GetLanguageImageURL(key string) string { // 检查是否符合 UI_MergeData_ 格式 re := regexp.MustCompile(`^UI_MergeData_(\d+)$`) m := re.FindStringSubmatch(key) if len(m) == 2 { id := m[1] // 从 MergeData 中取出对应 id 的 Icon 字段 if v, ok := MergeData[id]; ok && v != nil { if entry, ok2 := v.(map[string]interface{}); ok2 { var iconStr string if iv, ok3 := entry["Icon"]; ok3 && iv != nil { switch t := iv.(type) { case string: iconStr = t case float64: iconStr = strconv.Itoa(int(t)) default: iconStr = fmt.Sprintf("%v", t) } } if iconStr != "" { // baseDir := `D:\Github\AplusB_Pet_nation\Assets\GameMain\UI\UISprites\MergeObj` baseDir := `/data/AplusB_Pet_nation/Assets/Art_SubModule/GameMain/UI/UISprites/MergeObj` if fi, err := os.Stat(baseDir); err == nil && fi.IsDir() { var found string walkErr := filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } if info.IsDir() { return nil } // 忽略大小写匹配文件名包含 iconStr if strings.Contains(strings.ToLower(info.Name()), strings.ToLower(iconStr)) { found = path return errors.New("found") } return nil }) if found != "" { // 截取从 "Assets" 开始的相对路径,统一为斜杠形式 lower := strings.ToLower(found) idx := strings.Index(lower, "assets") if idx >= 0 { return filepath.ToSlash(found[idx:]) } return filepath.ToSlash(found) } if walkErr != nil && walkErr.Error() == "found" && found != "" { lower := strings.ToLower(found) idx := strings.Index(lower, "assets") if idx >= 0 { return filepath.ToSlash(found[idx:]) } return filepath.ToSlash(found) } } } } } } // 头像 reHead := regexp.MustCompile(`^Data_HeadName_(\d+)$`) if m := reHead.FindStringSubmatch(key); len(m) == 2 { id := Int(m[1]) return GetFaceURL(id) } // 头像框 reFrame := regexp.MustCompile(`^Data_HeadFrameName_(\d+)$`) if m := reFrame.FindStringSubmatch(key); len(m) == 2 { id := Int(m[1]) return GetFrameURL(id) } // Emoji reEmoji := regexp.MustCompile(`^Data_EmojiName_(\d+)$`) if m := reEmoji.FindStringSubmatch(key); len(m) == 2 { return GetEmojiURL(Int(m[1])) } // 卡牌 reCard := regexp.MustCompile(`^UI_MainCardPanel_cardName_`) if reCard.MatchString(key) { parts := strings.Split(key, "_") name := strings.TrimSpace(parts[len(parts)-1]) for _, v := range CardData { if v == nil { continue } entry, ok := v.(map[string]interface{}) if !ok { continue } if String(entry["Name"]) == name { icon := String(entry["Icon"]) if icon != "" { return fmt.Sprintf("UI/UISprites/%s.png", icon) } } } } // 卡牌收集 reCardCollect := regexp.MustCompile(`^UI_MainCardPanel_groupName_`) if reCardCollect.MatchString(key) { parts := strings.Split(key, "_") name := strings.TrimSpace(parts[len(parts)-1]) for _, v := range CardCollectData { if v == nil { continue } entry, ok := v.(map[string]interface{}) if !ok { continue } if String(entry["Name"]) == name { return String(entry["ResourcesPath"]) } } } //猫咪毛皮 reCat := regexp.MustCompile(`^UI_PetFurName_(\d+)$`) if m := reCat.FindStringSubmatch(key); len(m) == 2 { id := Int(m[1]) return fmt.Sprintf("UI/UISprites/Shop/Packed/Skin_pic_cat%d.png", id) } // 场景预览图 reScene := regexp.MustCompile(`^CS_ScenePanel_Scene(\d+)$`) m = reScene.FindStringSubmatch(key) if len(m) == 2 { return fmt.Sprintf("UI/UISprites/Area/merge_pic_s%s.png", m[1]) } //发射器插图 reNetAsset := regexp.MustCompile(`^UI_MainLvPanel_chapterTip_lv_(\d+)$`) if reNetAsset.MatchString(key) { if v, ok := NetAssetData[key]; ok && v != nil { entry, ok := v.(map[string]interface{}) if !ok { return "" } icon := String(entry["Picture"]) if icon != "" { return fmt.Sprintf("UI/UISprites/%s.png", icon) } } } return "" } func ParseNodeTags(tags string) []string { if tags == "" { return []string{} } return strings.Split(tags, ",") } func GetAppName(AppId int) string { switch AppId { case 0: return "正式服" case 1: return "测试服" case 2: return "QA服" default: return "未知服" } } func GetAppRegion(AppId int) string { switch AppId { case 0: return "prod" default: return "test" } } func GetEmojiURL(EmojiId int) string { return _GetURL(EmojiId, 109) } func GetFaceURL(HeadId int) string { return _GetURL(HeadId, 110) } func GetFrameURL(FrameId int) string { return _GetURL(FrameId, 105) } func _GetURL(Id, Type int) string { for _, v := range ItemData { info, ok := v.(map[string]interface{}) if !ok { continue } if Int(info["IType"]) == Type { effect := String(info["Effect"]) effectList := strings.Split(effect, ",") if Id == Int(effectList[0]) && len(effectList) > 0 { return info["FullResourcePath"].(string) } } } return "" } func GetCardURL(CardId string) string { info, ok := CardData[CardId] if ok && info != nil { entry, ok := info.(map[string]interface{}) if ok { return String(entry["ResourcesPath"]) } } return "" } func GetCardCollectURL(name string) string { for _, v := range CardCollectData { info, ok := v.(map[string]interface{}) if !ok { continue } if String(info["Name"]) == name { return String(info["ResourcesPath"]) } } return "" } func GetNetAssetURL(Key string) string { info, ok := NetAssetData[Key] if ok && info != nil { entry, ok := info.(map[string]interface{}) if ok { return String(entry["Picture"]) } } return "" } func CountDisplayLen(s string) int { total := 0 for _, r := range s { if unicode.In(r, unicode.Han) { total += 2 continue } total += 1 } return total } func FormatJson(v string) string { var data interface{} err := json.Unmarshal([]byte(v), &data) if err != nil { log.Printf("failed to unmarshal JSON: %v", err) return "" } jsonBytes, err := json.Marshal(data) if err != nil { log.Printf("failed to marshal JSON: %v", err) return "" } return string(jsonBytes) } func GetAddressLatency(Host string, Port int) (int64, error) { address := fmt.Sprintf("%s:%d", Host, Port) timeout := 3 * time.Second start := time.Now() conn, err := net.DialTimeout("tcp", address, timeout) if err != nil { return 0, err } conn.Close() latency := time.Since(start).Milliseconds() return latency, nil } func Float(a interface{}) float64 { if a == nil { return 0 } return toFloat64(a) } func parseMemoryTextToMB(value string) float64 { s := strings.TrimSpace(strings.ToUpper(value)) if s == "" { return 0 } multiplier := 1.0 switch { case strings.HasSuffix(s, "TB"): multiplier = 1024 * 1024 s = strings.TrimSuffix(s, "TB") case strings.HasSuffix(s, "GB"): multiplier = 1024 s = strings.TrimSuffix(s, "GB") case strings.HasSuffix(s, "MB"): s = strings.TrimSuffix(s, "MB") case strings.HasSuffix(s, "KB"): multiplier = 1.0 / 1024 s = strings.TrimSuffix(s, "KB") case strings.HasSuffix(s, "B"): multiplier = 1.0 / (1024 * 1024) s = strings.TrimSuffix(s, "B") } numberText := strings.TrimSpace(s) if numberText == "" { return 0 } parsed, err := strconv.ParseFloat(numberText, 64) if err != nil { return 0 } if parsed < 0 { return 0 } return parsed * multiplier } func clampFloat(value, min, max float64) float64 { if value < min { return min } if value > max { return max } return value } func GetServerWeight(resp *msg.ResServerInfo) int { const ( memCapMB = 16384.0 playerSoftCap = 1500.0 goroutineSoftCap = 20000.0 gcSoftCap = 2000.0 warmupSeconds = 600.0 ) freeMemMB := float64(resp.FreeMem) if freeMemMB < 0 { freeMemMB = 0 } usagePercent := clampFloat(float64(resp.UsageMem), 0, 100) if usagePercent == 0 && resp.Sys > 0 { allocMB := parseMemoryTextToMB(resp.Alloc) sysMB := float64(resp.Sys) / 1024 / 1024 if sysMB > 0 && allocMB > 0 { usagePercent = clampFloat(allocMB/sysMB*100, 0, 100) } } memScore := clampFloat(freeMemMB/memCapMB*100, 0, 100) memPressureScore := 100 - usagePercent cpuScore := 100 - clampFloat(resp.CPU, 0, 100) playerScore := 100 - clampFloat(float64(resp.PlayerNum)/playerSoftCap*100, 0, 100) goroutineScore := 100 - clampFloat(float64(resp.NumGoroutine)/goroutineSoftCap*100, 0, 100) gcScore := 100 - clampFloat(float64(resp.NumGC)/gcSoftCap*100, 0, 100) warmupScore := 100.0 if resp.StartTime > 0 { uptime := float64(Now() - int64(resp.StartTime)) if uptime < 0 { uptime = 0 } warmupScore = clampFloat(uptime/warmupSeconds*100, 0, 100) } allocRatioScore := 100.0 totalAllocMB := parseMemoryTextToMB(resp.TotalAlloc) allocMB := parseMemoryTextToMB(resp.Alloc) if totalAllocMB > 0 && allocMB >= 0 { allocRatioScore = 100 - clampFloat(allocMB/totalAllocMB*100, 0, 100) } weight := memScore*0.22 + memPressureScore*0.23 + cpuScore*0.23 + playerScore*0.18 + goroutineScore*0.05 + gcScore*0.03 + warmupScore*0.03 + allocRatioScore*0.02 return int(clampFloat(weight, 1, 100)) } func LogStructured(level string, message string, fields map[string]any) { payload := map[string]any{ "level": level, "msg": message, "time": time.Now().Format(time.RFC3339), } for key, value := range fields { payload[key] = value } data, err := json.Marshal(payload) if err != nil { log.Printf("level=%s msg=%q marshal_error=%v", level, message, err) return } log.Print(string(data)) }