package cron import ( "context" "encoding/json" "epic/internal/consts" "epic/internal/dao" "epic/internal/logic/i18n" "epic/internal/model/dto" "epic/internal/model/entity" "epic/internal/util" "fmt" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/gclient" "github.com/gogf/gf/v2/os/gtime" "strconv" "time" ) // ThirdPartyDataSync 第三方数据同步器 type ThirdPartyDataSync struct { client *gclient.Client } // NewThirdPartyDataSync 创建第三方数据同步器 func NewThirdPartyDataSync() *ThirdPartyDataSync { return &ThirdPartyDataSync{ client: gclient.New().Timeout(30 * time.Second), } } func (t *ThirdPartyDataSync) SyncHeroData(ctx context.Context) error { g.Log().Info(ctx, "开始同步英雄数据...") // 示例:从第三方API获取英雄数据 heroData, err := t.fetchHeroDataFromAPI(ctx) if err != nil || heroData == nil { g.Log().Error(ctx, "获取英雄数据失败:", err) return err } // 处理并保存数据 if err := t.processAndSaveHeroData(ctx, heroData); err != nil { g.Log().Error(ctx, "处理英雄数据失败:", err) return err } g.Log().Info(ctx, "英雄数据同步完成") return nil } // SyncArtifactData 同步神器数据 func (t *ThirdPartyDataSync) SyncArtifactData(ctx context.Context) error { g.Log().Info(ctx, "开始同步神器数据...") util.RedisCache.Set(ctx, "artifacts_all11", "asd", 0) // 示例:从第三方API获取神器数据 //artifactData, err := t.fetchArtifactDataFromAPI(ctx) //if err != nil { // g.Log().Error(ctx, "获取神器数据失败:", err) // return err //} // //// 处理并保存数据 //if err := t.processAndSaveArtifactData(ctx, artifactData); err != nil { // g.Log().Error(ctx, "处理神器数据失败:", err) // return err //} g.Log().Info(ctx, "神器数据同步完成") return nil } // fetchHeroDataFromAPI 从API获取英雄数据 func (t *ThirdPartyDataSync) fetchHeroDataFromAPI(ctx context.Context) ([]byte, error) { // 示例API地址,实际使用时需要替换为真实的API apiURL := consts.HeroListURL // 添加请求头 headers := map[string]string{ "User-Agent": "EpicGameBot/1.0", "Accept": "application/json", } // 发送GET请求 resp, err := t.client.Header(headers).Get(ctx, apiURL) if err != nil { return nil, fmt.Errorf("API请求失败: %v", err) } defer resp.Close() // 检查响应状态 if resp.StatusCode != 200 { return nil, fmt.Errorf("API响应错误,状态码: %d", resp.StatusCode) } // 读取响应内容 content := resp.ReadAll() g.Log().Debug(ctx, "API响应内容长度:", len(content)) return content, nil } // 从API获取神器数据 func (t *ThirdPartyDataSync) fetchArtifactDataFromAPI(ctx context.Context) (string, error) { // 示例API地址 apiURL := "https://static.smilegatemegaport.com/gameRecord/epic7/epic7_artifact.json?_=1729322698936" headers := map[string]string{ //"User-Agent": "EpicGameBot/1.0", "Accept": "application/json", } resp, err := t.client.Header(headers).Get(ctx, apiURL) if err != nil { return "", fmt.Errorf("API请求失败: %v", err) } defer resp.Close() if resp.StatusCode != 200 { return "", fmt.Errorf("API响应错误,状态码: %d", resp.StatusCode) } content := resp.ReadAll() g.Log().Debug(ctx, "神器API响应内容长度:", len(content)) return string(content), nil } func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data []byte) error { // 使用 gjson 解析 j := gjson.New(data) if j == nil || j.IsNil() { return fmt.Errorf("英雄数据格式错误,期望是一个JSON对象") } // 先解析为 map[string]*ThirdPartyHeroDTO var heroes map[string]*dto.ThirdPartyHeroDTO if err := j.Scan(&heroes); err != nil { return fmt.Errorf("解析英雄数据到DTO失败: %v", err) } g.Log().Info(ctx, "解析到", len(heroes), "个英雄数据") // 一次性查出所有数据库英雄,构建map var dbHeroes []*entity.EpicHeroInfo err := dao.EpicHeroInfo.Ctx(ctx).Scan(&dbHeroes) if err != nil { return fmt.Errorf("查询数据库英雄失败: %v", err) } heroMap := make(map[string]*entity.EpicHeroInfo, len(dbHeroes)) for _, h := range dbHeroes { heroMap[h.HeroCode] = h } // 遍历 map,设置 Name 字段,并保存 for name, hero := range heroes { hero.Name = name // 将 map 的 key 作为 Name 字段 dbHero, exists := heroMap[hero.Code] if exists && dbHero.RawJson != "" { // 已有且rawJson有值,跳过 continue } if exists && dbHero.RawJson == "" { // 只更新 rawJson 字段 rawJsonBytes, err := json.Marshal(hero) if err != nil { g.Log().Error(ctx, "序列化英雄数据失败:", err) continue } rawJson := string(rawJsonBytes) _, err = dao.EpicHeroInfo.Ctx(ctx). Where(dao.EpicHeroInfo.Columns().HeroCode, hero.Code). Data(g.Map{ dao.EpicHeroInfo.Columns().RawJson: rawJson, dao.EpicHeroInfo.Columns().UpdateTime: gtime.Now(), }). Update() if err != nil { g.Log().Error(ctx, "更新英雄rawJson失败:", err) continue } g.Log().Debug(ctx, "更新英雄rawJson:", hero.Code) continue } // 新增逻辑保持原样 status60 := hero.CalculatedStatus.Lv60SixStarFullyAwakened status60json, _ := gjson.EncodeString(status60) heroJson, _ := gjson.EncodeString(hero) zhHeroName := i18n.GetZh(ctx, hero.Name) //zhRole := i18n.GetZh(ctx, hero.Role) //zhAttribute := i18n.GetZh(ctx, hero.Attribute) fmt.Println(hero.Assets.Image) newHero := &entity.EpicHeroInfo{ Id: 0, HeroName: zhHeroName, HeroCode: hero.Code, HeroAttrLv60: status60json, Creator: "", CreateTime: gtime.Now(), Updater: "", UpdateTime: gtime.Now(), Deleted: false, //NickName: "", Rarity: strconv.Itoa(hero.Rarity), Role: hero.Role, //Zodiac: "", HeadImgUrl: "", Attribute: hero.Attribute, Remark: "", RawJson: heroJson, } _, err = dao.EpicHeroInfo.Ctx(ctx).Data(newHero).Insert() if err != nil { g.Log().Error(ctx, "插入新英雄失败:", err) continue } g.Log().Debug(ctx, "插入新英雄:", hero.Code) } return nil } // processAndSaveArtifactData 处理并保存神器数据 func (t *ThirdPartyDataSync) processAndSaveArtifactData(ctx context.Context, data string) error { // 使用 gjson 解析 j := gjson.New(data) zhcn := j.Get("zh-CN") // 检查json对象本身和其内部值,并使用 .Var().IsSlice() 这种更可靠的方式判断是否为数组 if !zhcn.IsSlice() { return fmt.Errorf("神器数据格式错误,期望是一个JSON数组") } var artifacts []*dto.ThirdPartyArtifactDTO if err := zhcn.Scan(&artifacts); err != nil { return fmt.Errorf("解析神器数据到DTO失败: %v", err) } // 批量处理数据 for _, artifact := range artifacts { if err := t.saveArtifactData(ctx, artifact); err != nil { g.Log().Error(ctx, "保存神器数据失败:", err) continue } } return nil } // saveHeroData 保存单个英雄数据 func (t *ThirdPartyDataSync) saveHeroData(ctx context.Context, hero *dto.ThirdPartyHeroDTO) error { // 查询是否存在 var dbHero *entity.EpicHeroInfo err := dao.EpicHeroInfo.Ctx(ctx). Where(dao.EpicHeroInfo.Columns().HeroCode, hero.Code). Scan(&dbHero) if err != nil { return err } // 获取原始 JSON 字符串 rawJsonBytes, err := json.Marshal(hero) if err != nil { return err } rawJson := string(rawJsonBytes) if dbHero != nil && dbHero.HeroCode != "" { // 查到记录 if dbHero.RawJson == "" { // 只更新 rawJson 字段 _, err = dao.EpicHeroInfo.Ctx(ctx). Where(dao.EpicHeroInfo.Columns().HeroCode, hero.Code). Data(g.Map{ dao.EpicHeroInfo.Columns().RawJson: rawJson, dao.EpicHeroInfo.Columns().UpdateTime: gtime.Now(), }). Update() if err != nil { return err } g.Log().Debug(ctx, "更新英雄rawJson:", hero.Code) } // 已有 rawJson,不做处理 } else { status60 := hero.CalculatedStatus.Lv60SixStarFullyAwakened status60json, _ := gjson.EncodeString(status60) heroJson, _ := gjson.EncodeString(hero) // 使用i18n服务转换字段 zhHeroName := i18n.GetZh(ctx, hero.Name) //zhRole := i18n.GetZh(ctx, hero.Role) //zhAttribute := i18n.GetZh(ctx, hero.Attribute) newHero := &entity.EpicHeroInfo{ Id: 0, HeroName: zhHeroName, HeroCode: hero.Code, HeroAttrLv60: status60json, Creator: "", CreateTime: gtime.Now(), Updater: "", UpdateTime: gtime.Now(), Deleted: false, //NickName: nil, Rarity: strconv.Itoa(hero.Rarity), Role: hero.Role, Zodiac: "", HeadImgUrl: "", Attribute: hero.Attribute, Remark: "", RawJson: heroJson, } // 没查到,插入新记录,字段按 DO 结构体补全 _, err = dao.EpicHeroInfo.Ctx(ctx).Data(newHero).Insert() if err != nil { return err } g.Log().Debug(ctx, "插入新英雄:", hero.Code) } return nil } // 保存单个神器数据 func (t *ThirdPartyDataSync) saveArtifactData(ctx context.Context, artifact *dto.ThirdPartyArtifactDTO) error { // TODO: 实现具体的数据库保存逻辑 // 现在 artifact 是一个强类型对象, 可以直接使用 artifact.Code, artifact.Name 等 // 示例:记录同步日志 syncLog := map[string]interface{}{ "type": "artifact_sync", "artifact_code": artifact.Code, "sync_time": gtime.Now(), "status": "success", } g.Log().Debug(ctx, "保存神器数据:", syncLog) return nil } // SyncAllData 同步所有数据 func (t *ThirdPartyDataSync) SyncAllData(ctx context.Context) error { g.Log().Info(ctx, "开始同步所有第三方数据...") // 同步英雄数据 if err := t.SyncHeroData(ctx); err != nil { g.Log().Error(ctx, "英雄数据同步失败:", err) // 继续同步其他数据 } // 同步神器数据 if err := t.SyncArtifactData(ctx); err != nil { g.Log().Error(ctx, "神器数据同步失败:", err) // 继续同步其他数据 } // 可以继续添加其他数据类型的同步 g.Log().Info(ctx, "所有第三方数据同步完成") return nil }