package service import ( "bytes" "encoding/json" "equipment-analyzer/internal/config" "equipment-analyzer/internal/model" "equipment-analyzer/internal/parser" "equipment-analyzer/internal/utils" "fmt" "io/ioutil" "math" "os" "path/filepath" "sort" "net/http" "strings" "sync" "time" ) type ParserService struct { config *config.Config logger *utils.Logger hexParser *parser.HexParser heroBase map[string]heroBaseStats heroOnce sync.Once heroErr error } func NewParserService(cfg *config.Config, logger *utils.Logger) *ParserService { return &ParserService{ config: cfg, logger: logger, hexParser: parser.NewHexParser(), heroBase: make(map[string]heroBaseStats), } } // ParseHexData 解析十六进制数据 func (ps *ParserService) ParseHexData(hexDataList []string) (*model.ParsedResult, string, error) { if len(hexDataList) == 0 { ps.logger.Warn("没有数据需要解析") return &model.ParsedResult{ Items: make([]interface{}, 0), Heroes: make([]interface{}, 0), }, "", nil } ps.logger.Info("开始远程解析数据", "count", len(hexDataList)) url := "https://krivpfvxi0.execute-api.us-west-2.amazonaws.com/dev/getItems" reqBody := map[string]interface{}{ "data": hexDataList, } jsonBytes, _ := json.Marshal(reqBody) client := &http.Client{Timeout: 15 * time.Second} req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBytes)) if err == nil { req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err == nil && resp.StatusCode == 200 { defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) // 新校验逻辑:校验data和units字段 var raw map[string]interface{} if err := json.Unmarshal(body, &raw); err != nil { ps.logger.Error("远程json解析失败", "error", err) return nil, "", fmt.Errorf("远程json解析失败: %v", err) } // 校验data字段 dataArr, dataOk := raw["data"].([]interface{}) if !dataOk || len(dataArr) == 0 { ps.logger.Error("远程json校验失败,data字段缺失或为空") return nil, "", fmt.Errorf("远程json校验失败,data字段缺失或为空") } // 校验通过,直接解析数据 ps.logger.Info("远程原始数据校验通过,开始解析") parsedResult, err := ps.ReadRawJsonFile(string(body)) if err != nil { return nil, "", err } return parsedResult, "", nil } else if err != nil { ps.logger.Error("远程解析请求失败", "error", err) return nil, "", fmt.Errorf("远程解析请求失败: %v", err) } else { ps.logger.Error("远程解析响应码异常", "status", resp.StatusCode) return nil, "", fmt.Errorf("远程解析响应码异常: %d", resp.StatusCode) } } else { ps.logger.Error("远程解析请求构建失败", "error", err) return nil, "", fmt.Errorf("远程解析请求构建失败: %v", err) } } // ReadRawJsonFile 读取rawJson内容并进行数据转换,返回ParsedResult对象 func (ps *ParserService) ReadRawJsonFile(rawJson string) (*model.ParsedResult, error) { var rawData map[string]interface{} if err := json.Unmarshal([]byte(rawJson), &rawData); err != nil { ps.logger.Error("解析JSON失败", "error", err) return nil, err } // 提取装备和英雄数据 equips, _ := rawData["data"].([]interface{}) // 修正:units 取最大长度的那组 var rawUnits []interface{} if unitsRaw, ok := rawData["units"].([]interface{}); ok && len(unitsRaw) > 0 { maxLen := 0 for _, u := range unitsRaw { if arr, ok := u.([]interface{}); ok && len(arr) > maxLen { maxLen = len(arr) rawUnits = arr } } } // 过滤有效装备 (x => !!x.f) var validEquips []interface{} for _, equip := range equips { if equipMap, ok := equip.(map[string]interface{}); ok { if f, exists := equipMap["f"]; exists && f != nil && f != "" { validEquips = append(validEquips, equip) } } } // 转换装备数据 convertedItems := ps.convertItemsAllWithLog(validEquips) // 转换英雄数据(只对最大组) convertedHeroes := ps.convertUnits(rawUnits) result := &model.ParsedResult{ Items: make([]interface{}, len(convertedItems)), Heroes: make([]interface{}, len(convertedHeroes)), } for i, v := range convertedItems { result.Items[i] = v } for i, v := range convertedHeroes { result.Heroes[i] = v } return result, nil } // convertItems 转换装备数据 func (ps *ParserService) convertItems(rawItems []interface{}) []map[string]interface{} { var convertedItems []map[string]interface{} for _, rawItem := range rawItems { if itemMap, ok := rawItem.(map[string]interface{}); ok { convertedItem := ps.convertSingleItem(itemMap) if convertedItem != nil { convertedItems = append(convertedItems, convertedItem) } } } var filteredItems []map[string]interface{} for _, item := range convertedItems { filteredItems = append(filteredItems, item) } return filteredItems } // convertSingleItem 转换单个装备 func (ps *ParserService) convertSingleItem(item map[string]interface{}) map[string]interface{} { converted := make(map[string]interface{}) // 复制基本字段 for key, value := range item { converted[key] = value } // 转换装备类型 ps.convertGear(converted) // 转换等级 ps.convertRank(converted) // 转换套装 ps.convertSet(converted) // 转换名称 ps.convertName(converted) // 转换等级 ps.convertLevel(converted) // 转换增强 ps.convertEnhance(converted) // 转换主属性 ps.convertMainStat(converted) // 转换副属性 ps.convertSubStats(converted) // 转换ID ps.convertId(converted) // 转换装备ID ps.convertEquippedId(converted) return converted } // convertUnits 转换英雄数据 func (ps *ParserService) convertUnits(rawUnits []interface{}) []map[string]interface{} { var convertedUnits []map[string]interface{} ps.ensureHeroBaseData() for _, rawUnit := range rawUnits { if unitMap, ok := rawUnit.(map[string]interface{}); ok { if name, exists := unitMap["name"]; exists && name != nil && name != "" { if id, exists := unitMap["id"]; exists && id != nil { convertedUnit := make(map[string]interface{}) for key, value := range unitMap { convertedUnit[key] = value } // 转换星星和觉醒 if g, exists := unitMap["g"]; exists { convertedUnit["stars"] = g } if z, exists := unitMap["z"]; exists { convertedUnit["awaken"] = z } if code, ok := unitMap["code"].(string); ok && code != "" { if base, ok := ps.heroBase[code]; ok { ps.applyHeroBase(convertedUnit, base) } } convertedUnits = append(convertedUnits, convertedUnit) } } } } return convertedUnits } // 转换函数实现 func (ps *ParserService) convertGear(item map[string]interface{}) { if _, exists := item["type"]; !exists { if code, exists := item["code"].(string); exists { baseCode := code if parts := strings.Split(code, "_"); len(parts) > 0 { baseCode = parts[0] } if idx := len(baseCode) - 1; idx >= 0 { gearLetter := string(baseCode[idx]) item["gear"] = gearByGearLetter[gearLetter] } } } else { if itemType, exists := item["type"].(string); exists { item["gear"] = gearByIngameType[itemType] } } } func (ps *ParserService) convertRank(item map[string]interface{}) { if g, exists := item["g"].(float64); exists { rankIndex := int(g) if rankIndex >= 0 && rankIndex < len(rankByIngameGrade) { item["rank"] = rankByIngameGrade[rankIndex] } } } func (ps *ParserService) convertSet(item map[string]interface{}) { if f, exists := item["f"].(string); exists { item["set"] = setsByIngameSet[f] } } func (ps *ParserService) convertName(item map[string]interface{}) { if _, exists := item["name"]; !exists { item["name"] = "Unknown" } } func (ps *ParserService) convertLevel(item map[string]interface{}) { if _, exists := item["level"]; !exists { item["level"] = 0 } } func (ps *ParserService) convertEnhance(item map[string]interface{}) { rank, rankExists := item["rank"].(string) op, opExists := item["op"].([]interface{}) if rankExists && opExists { countByRank := map[string]int{ "Normal": 5, "Good": 6, "Rare": 7, "Heroic": 8, "Epic": 9, } offsetByRank := map[string]int{ "Normal": 0, "Good": 1, "Rare": 2, "Heroic": 3, "Epic": 4, } count := countByRank[rank] offset := offsetByRank[rank] subsCount := len(op) - 1 if subsCount > count { subsCount = count } enhance := (subsCount - offset) * 3 if enhance < 0 { enhance = 0 } item["enhance"] = enhance } } func (ps *ParserService) convertMainStat(item map[string]interface{}) { op, opExists := item["op"].([]interface{}) mainStatValue, mainStatExists := item["mainStatValue"].(float64) if opExists && len(op) > 0 && mainStatExists { if mainOp, ok := op[0].([]interface{}); ok && len(mainOp) > 0 { if mainOpType, ok := mainOp[0].(string); ok { mainType := statByIngameStat[mainOpType] var mainValue float64 if ps.isFlat(mainOpType) { mainValue = mainStatValue } else { mainValue = ps.round10ths(mainStatValue * 100) } if mainValue == 0 || mainValue != mainValue { // NaN check mainValue = 0 } item["main"] = map[string]interface{}{ "type": mainType, "value": mainValue, } } } } } func (ps *ParserService) convertSubStats(item map[string]interface{}) { op, opExists := item["op"].([]interface{}) if !opExists || len(op) <= 1 { item["substats"] = []interface{}{} return } statAcc := make(map[string]map[string]interface{}) // 处理副属性 (从索引1开始) for i := 1; i < len(op); i++ { if opItem, ok := op[i].([]interface{}); ok && len(opItem) >= 2 { opType, _ := opItem[0].(string) opValue, _ := opItem[1].(float64) annotation := "" if len(opItem) > 2 { annotation, _ = opItem[2].(string) } statType := statByIngameStat[opType] var value float64 if ps.isFlat(opType) { value = opValue } else { value = ps.round10ths(opValue * 100) } if existingStat, exists := statAcc[statType]; exists { existingStat["value"] = existingStat["value"].(float64) + value if annotation == "c" { existingStat["modified"] = true } else if annotation != "u" { rolls := existingStat["rolls"].(int) + 1 existingStat["rolls"] = rolls existingStat["ingameRolls"] = rolls } } else { rolls := 1 if annotation == "u" { rolls = 0 } statAcc[statType] = map[string]interface{}{ "value": value, "rolls": rolls, "ingameRolls": rolls, "modified": annotation == "c", } } } } // 转换为最终格式 var substats []interface{} for statType, statData := range statAcc { substat := map[string]interface{}{ "type": statType, "value": statData["value"], "rolls": statData["rolls"], "ingameRolls": statData["ingameRolls"], "modified": statData["modified"], } substats = append(substats, substat) } item["substats"] = substats } func (ps *ParserService) convertId(item map[string]interface{}) { if id, exists := item["id"]; exists { item["ingameId"] = id } } func (ps *ParserService) convertEquippedId(item map[string]interface{}) { if p, exists := item["p"]; exists { item["ingameEquippedId"] = fmt.Sprintf("%v", p) } } func (ps *ParserService) isFlat(text string) bool { return text == "max_hp" || text == "speed" || text == "att" || text == "def" } func (ps *ParserService) round10ths(value float64) float64 { return math.Round(value*10) / 10 } // 映射常量 var ( rankByIngameGrade = []string{ "Unknown", "Normal", "Good", "Rare", "Heroic", "Epic", } gearByIngameType = map[string]string{ "weapon": "Weapon", "helm": "Helmet", "armor": "Armor", "neck": "Necklace", "ring": "Ring", "boot": "Boots", } gearByGearLetter = map[string]string{ "w": "Weapon", "h": "Helmet", "a": "Armor", "n": "Necklace", "r": "Ring", "b": "Boots", } setsByIngameSet = map[string]string{ "set_acc": "HitSet", "set_att": "AttackSet", "set_coop": "UnitySet", "set_counter": "CounterSet", "set_cri_dmg": "DestructionSet", "set_cri": "CriticalSet", "set_def": "DefenseSet", "set_immune": "ImmunitySet", "set_max_hp": "HealthSet", "set_penetrate": "PenetrationSet", "set_rage": "RageSet", "set_res": "ResistSet", "set_revenge": "RevengeSet", "set_scar": "InjurySet", "set_speed": "SpeedSet", "set_vampire": "LifestealSet", "set_shield": "ProtectionSet", "set_torrent": "TorrentSet", "set_revenant": "ReversalSet", "set_riposte": "RiposteSet", "set_chase": "PursuitSet", "set_opener": "WarfareSet", } statByIngameStat = map[string]string{ "att_rate": "AttackPercent", "max_hp_rate": "HealthPercent", "def_rate": "DefensePercent", "att": "Attack", "max_hp": "Health", "def": "Defense", "speed": "Speed", "res": "EffectResistancePercent", "cri": "CriticalHitChancePercent", "cri_dmg": "CriticalHitDamagePercent", "acc": "EffectivenessPercent", "coop": "DualAttackChancePercent", } ) // 新增:不做enhance过滤的convertItems func (ps *ParserService) convertItemsAllWithLog(rawItems []interface{}) []map[string]interface{} { var convertedItems []map[string]interface{} for _, rawItem := range rawItems { if itemMap, ok := rawItem.(map[string]interface{}); ok { convertedItem := ps.convertSingleItem(itemMap) if convertedItem != nil { convertedItems = append(convertedItems, convertedItem) } } } return convertedItems } type heroBaseStats struct { Atk float64 Def float64 Hp float64 Spd float64 Cr float64 Cd float64 Eff float64 Res float64 } type heroDataEntry struct { Code string `json:"code"` Name string `json:"name"` CalculatedStatus struct { Lv60SixStarFullyAwakened struct { Atk float64 `json:"atk"` Def float64 `json:"def"` Hp float64 `json:"hp"` Spd float64 `json:"spd"` Chc float64 `json:"chc"` Chd float64 `json:"chd"` Eff float64 `json:"eff"` Efr float64 `json:"efr"` } `json:"lv60SixStarFullyAwakened"` } `json:"calculatedStatus"` } func (ps *ParserService) ensureHeroBaseData() { ps.heroOnce.Do(func() { homeDir, err := os.UserHomeDir() if err != nil { ps.heroErr = err ps.logger.Error("load hero data failed", "error", err) return } path := filepath.Join(homeDir, ".equipment-analyzer", "herodata.json") body, err := ioutil.ReadFile(path) if err != nil { ps.heroErr = err ps.logger.Error("load hero data failed", "error", err) return } var raw map[string]heroDataEntry if err := json.Unmarshal(body, &raw); err != nil { ps.heroErr = err ps.logger.Error("load hero data failed", "error", err) return } for _, entry := range raw { if entry.Code == "" { continue } stats := entry.CalculatedStatus.Lv60SixStarFullyAwakened ps.heroBase[entry.Code] = heroBaseStats{ Atk: stats.Atk, Def: stats.Def, Hp: stats.Hp, Spd: stats.Spd, Cr: stats.Chc * 100, Cd: stats.Chd * 100, Eff: stats.Eff * 100, Res: stats.Efr * 100, } } ps.logger.Info("hero base data loaded", "count", len(ps.heroBase)) }) } func (ps *ParserService) FillHeroBase(heroes []model.OptimizeHero) error { ps.ensureHeroBaseData() if len(ps.heroBase) == 0 { return fmt.Errorf("hero base data not loaded") } for i := range heroes { code := heroes[i].Code if code == "" { continue } base, ok := ps.heroBase[code] if !ok { continue } if heroes[i].BaseAtk == 0 { heroes[i].BaseAtk = base.Atk } if heroes[i].BaseDef == 0 { heroes[i].BaseDef = base.Def } if heroes[i].BaseHp == 0 { heroes[i].BaseHp = base.Hp } if heroes[i].BaseSpd == 0 { heroes[i].BaseSpd = base.Spd } if heroes[i].BaseCr == 0 { heroes[i].BaseCr = base.Cr } if heroes[i].BaseCd == 0 { heroes[i].BaseCd = base.Cd } if heroes[i].BaseEff == 0 { heroes[i].BaseEff = base.Eff } if heroes[i].BaseRes == 0 { heroes[i].BaseRes = base.Res } if heroes[i].Atk == 0 { heroes[i].Atk = base.Atk } if heroes[i].Def == 0 { heroes[i].Def = base.Def } if heroes[i].Hp == 0 { heroes[i].Hp = base.Hp } if heroes[i].Spd == 0 { heroes[i].Spd = base.Spd } if heroes[i].Cr == 0 { heroes[i].Cr = base.Cr } if heroes[i].Cd == 0 { heroes[i].Cd = base.Cd } if heroes[i].Eff == 0 { heroes[i].Eff = base.Eff } if heroes[i].Res == 0 { heroes[i].Res = base.Res } } return nil } func (ps *ParserService) GetHeroTemplates() ([]model.HeroTemplate, error) { homeDir, err := os.UserHomeDir() if err != nil { return nil, err } path := filepath.Join(homeDir, ".equipment-analyzer", "herodata.json") body, err := ioutil.ReadFile(path) if err != nil { return nil, err } var raw map[string]heroDataEntry if err := json.Unmarshal(body, &raw); err != nil { return nil, err } list := make([]model.HeroTemplate, 0, len(raw)) for _, entry := range raw { if entry.Code == "" { continue } stats := entry.CalculatedStatus.Lv60SixStarFullyAwakened list = append(list, model.HeroTemplate{ Code: entry.Code, Name: entry.Name, BaseAtk: stats.Atk, BaseDef: stats.Def, BaseHp: stats.Hp, BaseSpd: stats.Spd, BaseCr: stats.Chc * 100, BaseCd: stats.Chd * 100, BaseEff: stats.Eff * 100, BaseRes: stats.Efr * 100, }) } sort.Slice(list, func(i, j int) bool { return list[i].Name < list[j].Name }) return list, nil } func (ps *ParserService) applyHeroBase(unit map[string]interface{}, base heroBaseStats) { if _, ok := unit["baseAtk"]; !ok { unit["baseAtk"] = base.Atk } if _, ok := unit["baseDef"]; !ok { unit["baseDef"] = base.Def } if _, ok := unit["baseHp"]; !ok { unit["baseHp"] = base.Hp } if _, ok := unit["baseSpd"]; !ok { unit["baseSpd"] = base.Spd } if _, ok := unit["baseCr"]; !ok { unit["baseCr"] = base.Cr } if _, ok := unit["baseCd"]; !ok { unit["baseCd"] = base.Cd } if _, ok := unit["baseEff"]; !ok { unit["baseEff"] = base.Eff } if _, ok := unit["baseRes"]; !ok { unit["baseRes"] = base.Res } if _, ok := unit["atk"]; !ok { unit["atk"] = base.Atk } if _, ok := unit["def"]; !ok { unit["def"] = base.Def } if _, ok := unit["hp"]; !ok { unit["hp"] = base.Hp } if _, ok := unit["spd"]; !ok { unit["spd"] = base.Spd } if _, ok := unit["cr"]; !ok { unit["cr"] = base.Cr } if _, ok := unit["cd"]; !ok { unit["cd"] = base.Cd } if _, ok := unit["eff"]; !ok { unit["eff"] = base.Eff } if _, ok := unit["res"]; !ok { unit["res"] = base.Res } }