i18n翻译

This commit is contained in:
hu xiaotong
2025-07-14 17:14:58 +08:00
parent 6061508ec9
commit b8c9817cb3
5 changed files with 151 additions and 146 deletions

View File

@@ -30,7 +30,6 @@ func NewThirdPartyDataSync() *ThirdPartyDataSync {
} }
} }
// SyncHeroData 同步英雄数据
func (t *ThirdPartyDataSync) SyncHeroData(ctx context.Context) error { func (t *ThirdPartyDataSync) SyncHeroData(ctx context.Context) error {
g.Log().Info(ctx, "开始同步英雄数据...") g.Log().Info(ctx, "开始同步英雄数据...")
@@ -130,7 +129,6 @@ func (t *ThirdPartyDataSync) fetchArtifactDataFromAPI(ctx context.Context) (stri
return string(content), nil return string(content), nil
} }
// processAndSaveHeroData 处理并保存英雄数据
func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data []byte) error { func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data []byte) error {
// 使用 gjson 解析 // 使用 gjson 解析
j := gjson.New(data) j := gjson.New(data)
@@ -147,13 +145,82 @@ func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data []
g.Log().Info(ctx, "解析到", len(heroes), "个英雄数据") 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 字段,并保存 // 遍历 map设置 Name 字段,并保存
for name, hero := range heroes { for name, hero := range heroes {
hero.Name = name // 将 map 的 key 作为 Name 字段 hero.Name = name // 将 map 的 key 作为 Name 字段
if err := t.saveHeroData(ctx, hero); err != nil { dbHero, exists := heroMap[hero.Code]
g.Log().Error(ctx, "保存英雄数据失败:", err) if exists && dbHero.RawJson != "" {
// 已有且rawJson有值跳过
continue 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 return nil
@@ -210,8 +277,7 @@ func (t *ThirdPartyDataSync) saveHeroData(ctx context.Context, hero *dto.ThirdPa
_, err = dao.EpicHeroInfo.Ctx(ctx). _, err = dao.EpicHeroInfo.Ctx(ctx).
Where(dao.EpicHeroInfo.Columns().HeroCode, hero.Code). Where(dao.EpicHeroInfo.Columns().HeroCode, hero.Code).
Data(g.Map{ Data(g.Map{
dao.EpicHeroInfo.Columns().RawJson: rawJson, dao.EpicHeroInfo.Columns().RawJson: rawJson,
// 可选:更新时间
dao.EpicHeroInfo.Columns().UpdateTime: gtime.Now(), dao.EpicHeroInfo.Columns().UpdateTime: gtime.Now(),
}). }).
Update() Update()
@@ -222,13 +288,13 @@ func (t *ThirdPartyDataSync) saveHeroData(ctx context.Context, hero *dto.ThirdPa
} }
// 已有 rawJson不做处理 // 已有 rawJson不做处理
} else { } else {
status60 := hero.CalculatedStatus["Lv60SixStarFullyAwakened"] status60 := hero.CalculatedStatus.Lv60SixStarFullyAwakened
status60json, _ := gjson.EncodeString(status60) status60json, _ := gjson.EncodeString(status60)
heroJson, _ := gjson.EncodeString(hero) heroJson, _ := gjson.EncodeString(hero)
// 使用i18n服务转换字段 // 使用i18n服务转换字段
zhHeroName := i18n.GetZh(ctx, hero.Name) zhHeroName := i18n.GetZh(ctx, hero.Name)
zhRole := i18n.GetZh(ctx, hero.Role) //zhRole := i18n.GetZh(ctx, hero.Role)
zhAttribute := i18n.GetZh(ctx, hero.Attribute) //zhAttribute := i18n.GetZh(ctx, hero.Attribute)
newHero := &entity.EpicHeroInfo{ newHero := &entity.EpicHeroInfo{
Id: 0, Id: 0,
@@ -236,18 +302,18 @@ func (t *ThirdPartyDataSync) saveHeroData(ctx context.Context, hero *dto.ThirdPa
HeroCode: hero.Code, HeroCode: hero.Code,
HeroAttrLv60: status60json, HeroAttrLv60: status60json,
Creator: "", Creator: "",
CreateTime: nil, CreateTime: gtime.Now(),
Updater: "", Updater: "",
UpdateTime: nil, UpdateTime: gtime.Now(),
Deleted: false, Deleted: false,
NickName: "", //NickName: nil,
Rarity: strconv.Itoa(hero.Rarity), Rarity: strconv.Itoa(hero.Rarity),
Role: zhRole, Role: hero.Role,
Zodiac: "", Zodiac: "",
HeadImgUrl: "", HeadImgUrl: "",
Attribute: zhAttribute, Attribute: hero.Attribute,
Remark: "", Remark: "",
RawJson: heroJson, RawJson: heroJson,
} }
// 没查到,插入新记录,字段按 DO 结构体补全 // 没查到,插入新记录,字段按 DO 结构体补全

View File

@@ -2,7 +2,9 @@ package cron
import ( import (
"context" "context"
"epic/internal/logic/i18n"
"fmt" "fmt"
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2" _ "github.com/gogf/gf/contrib/nosql/redis/v2"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gctx"
@@ -21,35 +23,6 @@ func TestMain(m *testing.M) {
os.Exit(code) 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) { func TestSyncArtifactData_Success(t *testing.T) {
sync := NewThirdPartyDataSync() sync := NewThirdPartyDataSync()
@@ -145,3 +46,14 @@ func TestSyncArtifactData_Success(t *testing.T) {
t.Errorf("expected success, but got error: %v", err) 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))
}

View File

@@ -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().Status: 1,
dao.EpicI18NMappings.Columns().CreateTime: gtime.Now(), dao.EpicI18NMappings.Columns().CreateTime: gtime.Now(),
dao.EpicI18NMappings.Columns().UpdateTime: gtime.Now(), dao.EpicI18NMappings.Columns().UpdateTime: gtime.Now(),
}).OnDuplicate("value", value).Insert() }).Insert()
if err != nil { if err != nil {
return fmt.Errorf("添加翻译失败: %v", err) 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{ _, err := dao.EpicI18NMappings.Ctx(ctx).Data(data).Insert()
dao.EpicI18NMappings.Columns().Value: g.Map{"value": "VALUES(value)"},
dao.EpicI18NMappings.Columns().UpdateTime: gtime.Now(),
}).Insert()
if err != nil { if err != nil {
return fmt.Errorf("批量导入翻译失败: %v", err) return fmt.Errorf("批量导入翻译失败: %v", err)
} }

View File

@@ -19,19 +19,38 @@ type ThirdPartyArtifactDTO struct {
// Note: This is a placeholder structure. Adjust it according to the actual API response. // Note: This is a placeholder structure. Adjust it according to the actual API response.
// ThirdPartyHeroDTO 第三方英雄数据传输对象 // ThirdPartyHeroDTO 第三方英雄数据传输对象
type ThirdPartyHeroDTO struct { type ThirdPartyHeroDTO struct {
Code string `json:"code"` Code string `json:"code"`
ID string `json:"_id"` ID string `json:"_id"`
Name string `json:"-"` Name string `json:"-"`
Rarity int `json:"rarity"` Rarity int `json:"rarity"`
Attribute string `json:"attribute"` Attribute string `json:"attribute"`
Role string `json:"role"` Role string `json:"role"`
Zodiac string `json:"zodiac"` Zodiac string `json:"zodiac"`
SelfDevotion SelfDevotion `json:"self_devotion"` SelfDevotion SelfDevotion `json:"self_devotion"`
Assets Assets `json:"assets"` Assets Assets `json:"assets"`
ExEquip []ExEquip `json:"ex_equip"` ExEquip []ExEquip `json:"ex_equip"`
Skills map[string]Skill `json:"skills"` Skills Skills `json:"skills"`
CalculatedStatus map[string]Status `json:"calculatedStatus"` 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 SelfDevotion struct {
Type string `json:"type"` Type string `json:"type"`
Grades map[string]float64 `json:"grades"` Grades map[string]float64 `json:"grades"`
@@ -60,11 +79,6 @@ type Skill struct {
Options []any `json:"options"` Options []any `json:"options"`
} }
type Status struct {
Lv50FiveStarFullyAwakened Stats `json:"lv50FiveStarFullyAwakened"`
Lv60SixStarFullyAwakened Stats `json:"lv60SixStarFullyAwakened"`
}
type Stats struct { type Stats struct {
CP int `json:"cp"` CP int `json:"cp"`
ATK int `json:"atk"` ATK int `json:"atk"`

17
internal/util/oss.go Normal file
View File

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