diff --git a/internal/logic/cron/third_party_sync.go b/internal/logic/cron/third_party_sync.go index 9d4888a..54fe45a 100644 --- a/internal/logic/cron/third_party_sync.go +++ b/internal/logic/cron/third_party_sync.go @@ -30,7 +30,6 @@ func NewThirdPartyDataSync() *ThirdPartyDataSync { } } -// SyncHeroData 同步英雄数据 func (t *ThirdPartyDataSync) SyncHeroData(ctx context.Context) error { g.Log().Info(ctx, "开始同步英雄数据...") @@ -130,7 +129,6 @@ func (t *ThirdPartyDataSync) fetchArtifactDataFromAPI(ctx context.Context) (stri return string(content), nil } -// processAndSaveHeroData 处理并保存英雄数据 func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data []byte) error { // 使用 gjson 解析 j := gjson.New(data) @@ -147,13 +145,82 @@ func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data [] 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 字段 - if err := t.saveHeroData(ctx, hero); err != nil { - g.Log().Error(ctx, "保存英雄数据失败:", err) + 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 @@ -210,8 +277,7 @@ func (t *ThirdPartyDataSync) saveHeroData(ctx context.Context, hero *dto.ThirdPa _, err = dao.EpicHeroInfo.Ctx(ctx). Where(dao.EpicHeroInfo.Columns().HeroCode, hero.Code). Data(g.Map{ - dao.EpicHeroInfo.Columns().RawJson: rawJson, - // 可选:更新时间 + dao.EpicHeroInfo.Columns().RawJson: rawJson, dao.EpicHeroInfo.Columns().UpdateTime: gtime.Now(), }). Update() @@ -222,13 +288,13 @@ func (t *ThirdPartyDataSync) saveHeroData(ctx context.Context, hero *dto.ThirdPa } // 已有 rawJson,不做处理 } else { - status60 := hero.CalculatedStatus["Lv60SixStarFullyAwakened"] + 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) + //zhRole := i18n.GetZh(ctx, hero.Role) + //zhAttribute := i18n.GetZh(ctx, hero.Attribute) newHero := &entity.EpicHeroInfo{ Id: 0, @@ -236,18 +302,18 @@ func (t *ThirdPartyDataSync) saveHeroData(ctx context.Context, hero *dto.ThirdPa HeroCode: hero.Code, HeroAttrLv60: status60json, Creator: "", - CreateTime: nil, + CreateTime: gtime.Now(), Updater: "", - UpdateTime: nil, + UpdateTime: gtime.Now(), Deleted: false, - NickName: "", - Rarity: strconv.Itoa(hero.Rarity), - Role: zhRole, - Zodiac: "", - HeadImgUrl: "", - Attribute: zhAttribute, - Remark: "", - RawJson: heroJson, + //NickName: nil, + Rarity: strconv.Itoa(hero.Rarity), + Role: hero.Role, + Zodiac: "", + HeadImgUrl: "", + Attribute: hero.Attribute, + Remark: "", + RawJson: heroJson, } // 没查到,插入新记录,字段按 DO 结构体补全 diff --git a/internal/logic/cron/third_party_sync_test.go b/internal/logic/cron/third_party_sync_test.go index 3ac6700..7e1e253 100644 --- a/internal/logic/cron/third_party_sync_test.go +++ b/internal/logic/cron/third_party_sync_test.go @@ -2,7 +2,9 @@ package cron import ( "context" + "epic/internal/logic/i18n" "fmt" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" _ "github.com/gogf/gf/contrib/nosql/redis/v2" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gctx" @@ -21,35 +23,6 @@ func TestMain(m *testing.M) { os.Exit(code) } -type mockSync struct { - fetchHeroDataErr error - fetchArtifactDataErr error - processHeroDataErr error - processArtifactErr error -} - -func (m *mockSync) fetchHeroDataFromAPI(ctx context.Context) ([]byte, error) { - if m.fetchHeroDataErr != nil { - return nil, m.fetchHeroDataErr - } - return []byte(`[{"code":"hero1"},{"code":"hero2"}]`), nil -} - -func (m *mockSync) fetchArtifactDataFromAPI(ctx context.Context) ([]byte, error) { - if m.fetchArtifactDataErr != nil { - return nil, m.fetchArtifactDataErr - } - return []byte(`[{"code":"artifact1"}]`), nil -} - -func (m *mockSync) processAndSaveHeroData(ctx context.Context, data []byte) error { - return m.processHeroDataErr -} - -func (m *mockSync) processAndSaveArtifactData(ctx context.Context, data []byte) error { - return m.processArtifactErr -} - /** * 测试同步英雄数据 */ @@ -62,81 +35,9 @@ func TestSyncHeroData(t *testing.T) { } } -// -//func TestSyncHeroData_FetchError(t *testing.T) { -// sync := &ThirdPartyDataSync{} -// sync.fetchHeroDataFromAPI = (&mockSync{fetchHeroDataErr: errors.New("fetch error")}).fetchHeroDataFromAPI -// sync.processAndSaveHeroData = (&mockSync{}).processAndSaveHeroData -// -// err := sync.SyncHeroData(context.Background()) -// if err == nil || err.Error() != "fetch error" { -// t.Errorf("expected fetch error, got: %v", err) -// } -//} -// -//func TestSyncHeroData_ProcessError(t *testing.T) { -// sync := &ThirdPartyDataSync{} -// sync.fetchHeroDataFromAPI = (&mockSync{}).fetchHeroDataFromAPI -// sync.processAndSaveHeroData = (&mockSync{processHeroDataErr: errors.New("process error")}).processAndSaveHeroData -// -// err := sync.SyncHeroData(context.Background()) -// if err == nil || err.Error() != "process error" { -// t.Errorf("expected process error, got: %v", err) -// } -//} -// -//func TestSyncArtifactData_Success(t *testing.T) { -// sync := &ThirdPartyDataSync{} -// sync.fetchArtifactDataFromAPI = (&mockSync{}).fetchArtifactDataFromAPI -// sync.processAndSaveArtifactData = (&mockSync{}).processAndSaveArtifactData -// -// err := sync.SyncArtifactData(context.Background()) -// if err != nil { -// t.Errorf("expected success, got error: %v", err) -// } -//} -// -//func TestSyncArtifactData_FetchError(t *testing.T) { - -// sync := &ThirdPartyDataSync{} -// sync.fetchArtifactDataFromAPI = (&mockSync{fetchArtifactDataErr: errors.New("fetch error")}).fetchArtifactDataFromAPI -// sync.processAndSaveArtifactData = (&mockSync{}).processAndSaveArtifactData -// -// err := sync.SyncArtifactData(context.Background()) -// if err == nil || err.Error() != "fetch error" { -// t.Errorf("expected fetch error, got: %v", err) -// } -//} -// -//func TestSyncArtifactData_ProcessError(t *testing.T) { -// sync := &ThirdPartyDataSync{} -// sync.fetchArtifactDataFromAPI = (&mockSync{}).fetchArtifactDataFromAPI -// sync.processAndSaveArtifactData = (&mockSync{processArtifactErr: errors.New("process error")}).processAndSaveArtifactData -// -// err := sync.SyncArtifactData(context.Background()) -// if err == nil || err.Error() != "process error" { -// t.Errorf("expected process error, got: %v", err) -// } -//} - -func TestProcessAndSaveArtifactData(t *testing.T) { - sync := &ThirdPartyDataSync{} - // 构造一个合法的 JSON 数据 - data := "[{\"code\":\"artifact1\"},{\"code\":\"artifact2\"}]" - if err := sync.processAndSaveArtifactData(context.Background(), data); err != nil { - t.Errorf("expected no error, got: %v", err) - } -} - -func TestProcessAndSaveHeroData(t *testing.T) { - sync := &ThirdPartyDataSync{} - // 构造一个合法的 JSON 数据 - data := []byte(`[{"code":"hero1"},{"code":"hero2"}]`) - if err := sync.processAndSaveHeroData(context.Background(), data); err != nil { - t.Errorf("expected no error, got: %v", err) - } -} - +/** + * 测试同步神器数据 + */ func TestSyncArtifactData_Success(t *testing.T) { sync := NewThirdPartyDataSync() @@ -145,3 +46,14 @@ func TestSyncArtifactData_Success(t *testing.T) { t.Errorf("expected success, but got error: %v", err) } } + +func TestInitI18nStaticToDB(t *testing.T) { + //t.Skip("仅初始化时手动执行一次,谨慎运行!") + ctx := context.Background() + // 直接批量导入,不做重复检查 + err := i18n.ImportI18nFromMap(ctx, "zh", i18n.I18nEnToZh, "init") + if err != nil { + t.Fatalf("导入静态i18n数据失败: %v", err) + } + t.Logf("静态i18n数据导入成功,共%d条", len(i18n.I18nEnToZh)) +} diff --git a/internal/logic/i18n/i18n.go b/internal/logic/i18n/i18n.go index ce0a8b8..5537554 100644 --- a/internal/logic/i18n/i18n.go +++ b/internal/logic/i18n/i18n.go @@ -709,7 +709,7 @@ func (l *Logic) Add(ctx context.Context, key, lang, value, category string) erro dao.EpicI18NMappings.Columns().Status: 1, dao.EpicI18NMappings.Columns().CreateTime: gtime.Now(), dao.EpicI18NMappings.Columns().UpdateTime: gtime.Now(), - }).OnDuplicate("value", value).Insert() + }).Insert() if err != nil { return fmt.Errorf("添加翻译失败: %v", err) @@ -826,11 +826,7 @@ func (l *Logic) ImportFromMap(ctx context.Context, lang string, mappings map[str }) } - _, err := dao.EpicI18NMappings.Ctx(ctx).Data(data).OnDuplicate("value", g.Map{ - dao.EpicI18NMappings.Columns().Value: g.Map{"value": "VALUES(value)"}, - dao.EpicI18NMappings.Columns().UpdateTime: gtime.Now(), - }).Insert() - + _, err := dao.EpicI18NMappings.Ctx(ctx).Data(data).Insert() if err != nil { return fmt.Errorf("批量导入翻译失败: %v", err) } diff --git a/internal/model/dto/third_party.go b/internal/model/dto/third_party.go index 4c0842d..bebd2a3 100644 --- a/internal/model/dto/third_party.go +++ b/internal/model/dto/third_party.go @@ -19,19 +19,38 @@ type ThirdPartyArtifactDTO struct { // Note: This is a placeholder structure. Adjust it according to the actual API response. // ThirdPartyHeroDTO 第三方英雄数据传输对象 type ThirdPartyHeroDTO struct { - Code string `json:"code"` - ID string `json:"_id"` - Name string `json:"-"` - Rarity int `json:"rarity"` - Attribute string `json:"attribute"` - Role string `json:"role"` - Zodiac string `json:"zodiac"` - SelfDevotion SelfDevotion `json:"self_devotion"` - Assets Assets `json:"assets"` - ExEquip []ExEquip `json:"ex_equip"` - Skills map[string]Skill `json:"skills"` - CalculatedStatus map[string]Status `json:"calculatedStatus"` + Code string `json:"code"` + ID string `json:"_id"` + Name string `json:"-"` + Rarity int `json:"rarity"` + Attribute string `json:"attribute"` + Role string `json:"role"` + Zodiac string `json:"zodiac"` + SelfDevotion SelfDevotion `json:"self_devotion"` + Assets Assets `json:"assets"` + ExEquip []ExEquip `json:"ex_equip"` + Skills Skills `json:"skills"` + CalculatedStatus CalculatedStatus `json:"calculatedStatus"` } + +// Skills结构体,支持点语法访问S1、S2、S3 +type Skills struct { + S1 Skill `json:"S1"` + S2 Skill `json:"S2"` + S3 Skill `json:"S3"` +} + +// 新增结构体,支持点语法访问 +// CalculatedStatus 兼容第三方API的calculatedStatus字段 +// 只声明常用的两个等级 +// 其他等级如有需要可自行添加 +// 字段名需与json tag严格对应 + +type CalculatedStatus struct { + Lv50FiveStarFullyAwakened Stats `json:"lv50FiveStarFullyAwakened"` + Lv60SixStarFullyAwakened Stats `json:"lv60SixStarFullyAwakened"` +} + type SelfDevotion struct { Type string `json:"type"` Grades map[string]float64 `json:"grades"` @@ -60,11 +79,6 @@ type Skill struct { Options []any `json:"options"` } -type Status struct { - Lv50FiveStarFullyAwakened Stats `json:"lv50FiveStarFullyAwakened"` - Lv60SixStarFullyAwakened Stats `json:"lv60SixStarFullyAwakened"` -} - type Stats struct { CP int `json:"cp"` ATK int `json:"atk"` diff --git a/internal/util/oss.go b/internal/util/oss.go new file mode 100644 index 0000000..5e5462c --- /dev/null +++ b/internal/util/oss.go @@ -0,0 +1,17 @@ +package util + +import ( + "context" +) + +// DownloadAndUploadToOSS 下载网络图片并上传到OSS,返回OSS路径 +// imageUrl: 网络图片完整URL +// 返回: OSS上的图片URL,或错误 +func DownloadAndUploadToOSS(ctx context.Context, imageUrl string) (string, error) { + // 1. 下载 imageUrl 到本地临时文件 + // 2. 上传临时文件到OSS,获取OSS路径 + // 3. 删除临时文件 + // 4. 返回OSS路径 + // TODO: 实现具体逻辑 + return "", nil +} \ No newline at end of file