368 lines
10 KiB
Go
368 lines
10 KiB
Go
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
|
||
}
|