feat(cron): 添加角色配装信息刷新任务并优化神器数据同步功能

- 新增每5天执行一次的角色配装信息刷新任务
- 重构神器数据同步功能,优化数据处理和保存逻辑- 添加神器图片URL获取和上传逻辑
- 更新相关测试用例
This commit is contained in:
hxt
2025-07-17 22:05:27 +08:00
parent c36a2cb8b0
commit f8001aef5b
3 changed files with 105 additions and 22 deletions

View File

@@ -180,6 +180,13 @@ func (l *Logic) registerDefaultJobs(ctx context.Context) error {
// return err // return err
//} //}
// 每5天执行一次角色配装信息刷新任务
if err := l.AddJob(ctx, "hero_set_refresh_5days", "0 0 0 */5 * *", func() {
l.refreshHeroSetContent(ctx)
}); err != nil {
return err
}
return nil return nil
} }
@@ -196,7 +203,7 @@ func (l *Logic) syncDataFromThirdParty(ctx context.Context) {
g.Log().Info(ctx, "Data sync completed") g.Log().Info(ctx, "Data sync completed")
} }
// syncHeroData 同步英雄数据 //同步英雄数据
func (l *Logic) syncHeroData(ctx context.Context) { func (l *Logic) syncHeroData(ctx context.Context) {
g.Log().Info(ctx, "Starting hero data sync...") g.Log().Info(ctx, "Starting hero data sync...")
@@ -284,3 +291,13 @@ func (l *Logic) refreshOssPresignUrlCacheJob(ctx context.Context) {
g.Log().Info(ctx, "OSS presigned URL cache refresh completed") g.Log().Info(ctx, "OSS presigned URL cache refresh completed")
} }
} }
// 新增:定时刷新角色配装信息
func (l *Logic) refreshHeroSetContent(ctx context.Context) {
g.Log().Info(ctx, "Starting hero set content refresh...")
if err := l.sync.RefreshAllHeroSetContent(ctx); err != nil {
g.Log().Error(ctx, "Hero set content refresh failed:", err)
return
}
g.Log().Info(ctx, "Hero set content refresh completed")
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/gogf/gf/v2/net/gclient" "github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/os/gtime"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -132,10 +133,9 @@ func (t *ThirdPartyDataSync) FetchHeroBuildsFromAPI(ctx context.Context, heroNam
// 从API获取神器数据 // 从API获取神器数据
func (t *ThirdPartyDataSync) fetchArtifactDataFromAPI(ctx context.Context) (string, error) { func (t *ThirdPartyDataSync) fetchArtifactDataFromAPI(ctx context.Context) (string, error) {
// 示例API地址 // 示例API地址
apiURL := "https://static.smilegatemegaport.com/gameRecord/epic7/epic7_artifact.json?_=1729322698936" apiURL := consts.ArtifactDataURL
headers := map[string]string{ headers := map[string]string{
//"User-Agent": "EpicGameBot/1.0",
"Accept": "application/json", "Accept": "application/json",
} }
@@ -282,27 +282,76 @@ func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data []
// processAndSaveArtifactData 处理并保存神器数据 // processAndSaveArtifactData 处理并保存神器数据
func (t *ThirdPartyDataSync) processAndSaveArtifactData(ctx context.Context, data string) error { func (t *ThirdPartyDataSync) processAndSaveArtifactData(ctx context.Context, data string) error {
// 使用 gjson 解析 // 1. 解析json为map
j := gjson.New(data) var artifactMap map[string]struct {
zhcn := j.Get("zh-CN") Name string `json:"name"`
// 检查json对象本身和其内部值并使用 .Var().IsSlice() 这种更可靠的方式判断是否为数组 Rarity int `json:"rarity"`
if !zhcn.IsSlice() { Role string `json:"role"`
return fmt.Errorf("神器数据格式错误期望是一个JSON数组") Stats struct {
Attack int `json:"attack"`
Health int `json:"health"`
Defense int `json:"defense"`
} `json:"stats"`
Code string `json:"code"`
}
if err := json.Unmarshal([]byte(data), &artifactMap); err != nil {
return fmt.Errorf("解析神器数据失败: %v", err)
}
g.Log().Info(ctx, "解析到", len(artifactMap), "个神器数据")
// 2. 查询数据库所有神器构建map
var dbArtifacts []*entity.EpicArtifactInfo
err := dao.EpicArtifactInfo.Ctx(ctx).Scan(&dbArtifacts)
if err != nil {
return fmt.Errorf("查询数据库神器失败: %v", err)
}
artifactDbMap := make(map[string]*entity.EpicArtifactInfo, len(dbArtifacts))
for _, a := range dbArtifacts {
artifactDbMap[a.ArtifactCode] = a
} }
var artifacts []*dto.ThirdPartyArtifactDTO for _, art := range artifactMap {
if err := zhcn.Scan(&artifacts); err != nil { var dbArt *entity.EpicArtifactInfo
return fmt.Errorf("解析神器数据到DTO失败: %v", err) if v, ok := artifactDbMap[art.Code]; ok {
dbArt = v
} }
customImgUrl := getArtifactImageUrl(ctx, art.Code, dbArt)
// 批量处理数据 artifact := &entity.EpicArtifactInfo{
for _, artifact := range artifacts { ArtifactName: i18n.GetZh(ctx, art.Name),
if err := t.saveArtifactData(ctx, artifact); err != nil { ArtifactNameEn: art.Name,
g.Log().Error(ctx, "保存神器数据失败:", err) ArtifactCode: art.Code,
Rarity: strconv.Itoa(art.Rarity),
Role: art.Role,
StatsAttack: art.Stats.Attack,
StatsHealth: art.Stats.Health,
StatsDefense: art.Stats.Defense,
ImageUrl: customImgUrl,
Updater: "sync",
UpdateTime: gtime.Now(),
Deleted: false,
}
if dbArt != nil {
artifact.Id = dbArt.Id
_, err := dao.EpicArtifactInfo.Ctx(ctx).
Where(dao.EpicArtifactInfo.Columns().ArtifactCode, art.Code).
Data(artifact).
Update()
if err != nil {
g.Log().Error(ctx, "更新神器失败:", art.Name, err)
continue continue
} }
g.Log().Info(ctx, "更新神器:", art.Name)
} else {
artifact.Creator = "sync"
artifact.CreateTime = gtime.Now()
_, err := dao.EpicArtifactInfo.Ctx(ctx).Data(artifact).Insert()
if err != nil {
g.Log().Error(ctx, "插入神器失败:", art.Name, err)
continue
}
g.Log().Info(ctx, "插入神器:", art.Name)
}
} }
return nil return nil
} }
@@ -443,7 +492,7 @@ func (t *ThirdPartyDataSync) RefreshHeroSetContentByHeroInfo(ctx context.Context
} }
} }
// RefreshAllHeroSetContent 刷新所有角色配装字段 //刷新所有角色配装字段
func (t *ThirdPartyDataSync) RefreshAllHeroSetContent(ctx context.Context) error { func (t *ThirdPartyDataSync) RefreshAllHeroSetContent(ctx context.Context) error {
g.Log().Info(ctx, "开始批量刷新所有角色配装字段...") g.Log().Info(ctx, "开始批量刷新所有角色配装字段...")
@@ -477,3 +526,20 @@ func (t *ThirdPartyDataSync) RefreshAllHeroSetContent(ctx context.Context) error
g.Log().Info(ctx, "所有角色配装字段刷新完成") g.Log().Info(ctx, "所有角色配装字段刷新完成")
return nil return nil
} }
// 获取神器图片URL已上传则复用否则上传
func getArtifactImageUrl(ctx context.Context, artCode string, dbArt *entity.EpicArtifactInfo) string {
if dbArt != nil && dbArt.ImageUrl != "" && strings.HasPrefix(dbArt.ImageUrl, consts.S3CustomDomain) {
return dbArt.ImageUrl
}
ossObjectKey := fmt.Sprintf("epic/artifact/images/%s.png", artCode)
ossUrl, err := util.DownloadAndUploadToOSS(ctx, "", ossObjectKey)
if err != nil || ossUrl == "" {
return ""
}
prefix := consts.S3Endpoint + "/" + consts.S3Bucket
if strings.HasPrefix(ossUrl, prefix) {
return consts.S3CustomDomain + ossUrl[len(prefix):]
}
return ossUrl
}

View File

@@ -49,8 +49,8 @@ func TestSyncHeroData(t *testing.T) {
* 测试同步神器数据 * 测试同步神器数据
*/ */
func TestSyncArtifactData_Success(t *testing.T) { func TestSyncArtifactData_Success(t *testing.T) {
_ = i18n.RefreshI18n(context.Background())
sync := NewThirdPartyDataSync() sync := NewThirdPartyDataSync()
err := sync.SyncArtifactData(context.Background()) err := sync.SyncArtifactData(context.Background())
if err != nil { if err != nil {
t.Errorf("expected success, but got error: %v", err) t.Errorf("expected success, but got error: %v", err)