Files
wails-epic/internal/service/parser_service.go

768 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}