refactor(internal): 优化 OSS 预签名 URL 缓存刷新任务和英雄数据缓存逻辑

- 注释掉 OSS预签名 URL 缓存刷新任务的定时执行代码
- 在 hero/hero.go 中增加对 Redis缓存和英雄数据集的非空校验
- 修改 OSS预签名 URL 生成逻辑,自动替换为 CDN 域名
This commit is contained in:
hu xiaotong
2025-07-18 16:56:36 +08:00
parent f8001aef5b
commit ce0fa7f2ed
4 changed files with 210 additions and 19 deletions

View File

@@ -18,6 +18,8 @@ const (
// 角色图片基础 URL
GfHeroPngURL = "https://static.smilegatemegaport.com/event/live/epic7/guide/images/hero/"
EPIC_DB_URL = "https://epic7db.com/"
// S3/OSS 配置
S3AccessKey = "s5iWm6wXVvhCNN9nJlXwgWRf"
S3SecretKey = "91sTurpFtugXijPg0uSof3JcJma0HED"

View File

@@ -51,7 +51,7 @@ func (t *ThirdPartyDataSync) SyncHeroData(ctx context.Context) error {
return nil
}
// SyncArtifactData 同步神器数据
// 同步神器数据
func (t *ThirdPartyDataSync) SyncArtifactData(ctx context.Context) error {
g.Log().Info(ctx, "开始同步神器数据...")
@@ -280,7 +280,7 @@ func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data []
return nil
}
// processAndSaveArtifactData 处理并保存神器数据
// 处理并保存神器数据
func (t *ThirdPartyDataSync) processAndSaveArtifactData(ctx context.Context, data string) error {
// 1. 解析json为map
var artifactMap map[string]struct {
@@ -307,14 +307,21 @@ func (t *ThirdPartyDataSync) processAndSaveArtifactData(ctx context.Context, dat
}
artifactDbMap := make(map[string]*entity.EpicArtifactInfo, len(dbArtifacts))
for _, a := range dbArtifacts {
artifactDbMap[a.ArtifactCode] = a
artifactDbMap[a.ArtifactNameEn] = a
}
for _, art := range artifactMap {
var dbArt *entity.EpicArtifactInfo
if v, ok := artifactDbMap[art.Code]; ok {
if v, ok := artifactDbMap[art.Name]; ok {
dbArt = v
}
// 如果数据库中已有自定义CDN图片跳过处理
if dbArt != nil && dbArt.ImageUrl != "" && strings.HasPrefix(dbArt.ImageUrl, consts.S3CustomDomain) {
//g.Log().Debug(ctx, "跳过已有CDN图片的神器:", art.Name)
continue
}
customImgUrl := getArtifactImageUrl(ctx, art.Code, dbArt)
artifact := &entity.EpicArtifactInfo{
ArtifactName: i18n.GetZh(ctx, art.Name),
@@ -334,8 +341,19 @@ func (t *ThirdPartyDataSync) processAndSaveArtifactData(ctx context.Context, dat
artifact.Id = dbArt.Id
_, err := dao.EpicArtifactInfo.Ctx(ctx).
Where(dao.EpicArtifactInfo.Columns().ArtifactCode, art.Code).
Data(artifact).
Update()
Data(g.Map{
dao.EpicArtifactInfo.Columns().ArtifactName: artifact.ArtifactName,
dao.EpicArtifactInfo.Columns().ArtifactNameEn: artifact.ArtifactNameEn,
dao.EpicArtifactInfo.Columns().Rarity: artifact.Rarity,
dao.EpicArtifactInfo.Columns().Role: artifact.Role,
dao.EpicArtifactInfo.Columns().StatsAttack: artifact.StatsAttack,
dao.EpicArtifactInfo.Columns().StatsHealth: artifact.StatsHealth,
dao.EpicArtifactInfo.Columns().StatsDefense: artifact.StatsDefense,
dao.EpicArtifactInfo.Columns().ImageUrl: artifact.ImageUrl,
dao.EpicArtifactInfo.Columns().Updater: artifact.Updater,
dao.EpicArtifactInfo.Columns().UpdateTime: gtime.Now(),
dao.EpicArtifactInfo.Columns().Deleted: artifact.Deleted,
}).Update()
if err != nil {
g.Log().Error(ctx, "更新神器失败:", art.Name, err)
continue
@@ -343,8 +361,22 @@ func (t *ThirdPartyDataSync) processAndSaveArtifactData(ctx context.Context, dat
g.Log().Info(ctx, "更新神器:", art.Name)
} else {
artifact.Creator = "sync"
artifact.CreateTime = gtime.Now()
_, err := dao.EpicArtifactInfo.Ctx(ctx).Data(artifact).Insert()
_, err := dao.EpicArtifactInfo.Ctx(ctx).Data(g.Map{
dao.EpicArtifactInfo.Columns().ArtifactName: artifact.ArtifactName,
dao.EpicArtifactInfo.Columns().ArtifactNameEn: artifact.ArtifactNameEn,
dao.EpicArtifactInfo.Columns().ArtifactCode: artifact.ArtifactCode,
dao.EpicArtifactInfo.Columns().Rarity: artifact.Rarity,
dao.EpicArtifactInfo.Columns().Role: artifact.Role,
dao.EpicArtifactInfo.Columns().StatsAttack: artifact.StatsAttack,
dao.EpicArtifactInfo.Columns().StatsHealth: artifact.StatsHealth,
dao.EpicArtifactInfo.Columns().StatsDefense: artifact.StatsDefense,
dao.EpicArtifactInfo.Columns().ImageUrl: artifact.ImageUrl,
dao.EpicArtifactInfo.Columns().Creator: "sync",
dao.EpicArtifactInfo.Columns().CreateTime: gtime.Now(),
dao.EpicArtifactInfo.Columns().Updater: artifact.Updater,
dao.EpicArtifactInfo.Columns().UpdateTime: artifact.UpdateTime,
dao.EpicArtifactInfo.Columns().Deleted: artifact.Deleted,
}).Insert()
if err != nil {
g.Log().Error(ctx, "插入神器失败:", art.Name, err)
continue
@@ -527,19 +559,176 @@ func (t *ThirdPartyDataSync) RefreshAllHeroSetContent(ctx context.Context) error
return nil
}
// 获取神器图片URL已上传则复用否则上传
// 获取神器图片URL已上传则复用否则从epic7db.com匹配并上传到OSS
func getArtifactImageUrl(ctx context.Context, artCode string, dbArt *entity.EpicArtifactInfo) string {
if dbArt != nil && dbArt.ImageUrl != "" && strings.HasPrefix(dbArt.ImageUrl, consts.S3CustomDomain) {
g.Log().Debug(ctx, "神器已有CDN图片直接返回:", artCode, dbArt.ImageUrl)
return dbArt.ImageUrl
}
// 取神器英文名优先用dbArt.ArtifactNameEn否则artCode
var artifactNameEn string
if dbArt != nil && dbArt.ArtifactNameEn != "" {
artifactNameEn = dbArt.ArtifactNameEn
} else {
artifactNameEn = artCode
}
g.Log().Debug(ctx, "开始匹配神器图片:", artCode, "英文名:", artifactNameEn)
// 从Redis获取神器爬虫数据
redisVal, err := util.RedisCache.Get(ctx, "epic7:artifacts")
if err != nil || redisVal == nil {
g.Log().Error(ctx, "获取Redis神器数据失败:", err)
return ""
}
var artifactArr []map[string]interface{}
if err := json.Unmarshal([]byte(redisVal.String()), &artifactArr); err != nil {
g.Log().Error(ctx, "解析Redis神器数据失败:", err)
return ""
}
g.Log().Debug(ctx, "Redis中共有", len(artifactArr), "个神器数据")
normalize := func(s string) string {
r := []rune{}
for _, c := range s {
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
r = append(r, c)
}
}
return strings.ToLower(string(r))
}
// 计算字符串相似度Levenshtein距离
similarity := func(s1, s2 string) float64 {
if s1 == s2 {
return 1.0
}
if len(s1) == 0 || len(s2) == 0 {
return 0.0
}
// 计算编辑距离
d := make([][]int, len(s1)+1)
for i := range d {
d[i] = make([]int, len(s2)+1)
}
for i := 0; i <= len(s1); i++ {
d[i][0] = i
}
for j := 0; j <= len(s2); j++ {
d[0][j] = j
}
for i := 1; i <= len(s1); i++ {
for j := 1; j <= len(s2); j++ {
if s1[i-1] == s2[j-1] {
d[i][j] = d[i-1][j-1]
} else {
d[i][j] = min(d[i-1][j]+1, min(d[i][j-1]+1, d[i-1][j-1]+1))
}
}
}
maxLen := max(len(s1), len(s2))
if maxLen == 0 {
return 1.0
}
return 1.0 - float64(d[len(s1)][len(s2)])/float64(maxLen)
}
localNorm := normalize(artifactNameEn)
g.Log().Debug(ctx, "本地神器名标准化后:", localNorm)
var bestMatch map[string]interface{}
var bestSimilarity float64
var bestMatchName string
// 先尝试精确匹配
g.Log().Debug(ctx, "开始精确匹配...")
for _, item := range artifactArr {
archName, ok := item["arch_name"].(string)
if !ok {
continue
}
archNorm := normalize(archName)
if archNorm == localNorm {
bestMatch = item
bestMatchName = archName
g.Log().Debug(ctx, "精确匹配成功:", archName, "->", archNorm)
break
}
}
// 如果精确匹配失败,尝试模糊匹配
if bestMatch == nil {
g.Log().Debug(ctx, "精确匹配失败,开始模糊匹配...")
for _, item := range artifactArr {
archName, ok := item["arch_name"].(string)
if !ok {
continue
}
archNorm := normalize(archName)
sim := similarity(localNorm, archNorm)
if sim > bestSimilarity {
bestSimilarity = sim
bestMatch = item
bestMatchName = archName
g.Log().Debug(ctx, "发现更高相似度匹配:", archName, "相似度:", sim)
}
}
// 只有相似度达到90%才使用模糊匹配结果
if bestSimilarity < 0.9 {
g.Log().Debug(ctx, "模糊匹配相似度不足90%,最高相似度:", bestSimilarity, "最佳匹配:", bestMatchName)
bestMatch = nil
} else {
g.Log().Debug(ctx, "模糊匹配成功,相似度:", bestSimilarity, "匹配名称:", bestMatchName)
}
}
if bestMatch != nil {
archSrc, ok := bestMatch["arch_src"].(string)
if !ok || archSrc == "" {
g.Log().Error(ctx, "匹配成功但arch_src为空:", bestMatchName)
return ""
}
g.Log().Debug(ctx, "开始下载并上传图片:", bestMatchName, "图片路径:", archSrc)
// 拼接第三方图片完整URL
imgUrl := consts.EPIC_DB_URL + strings.TrimPrefix(archSrc, "/")
// 上传到OSS
ossObjectKey := fmt.Sprintf("epic/artifact/images/%s.png", artCode)
ossUrl, err := util.DownloadAndUploadToOSS(ctx, "", ossObjectKey)
ossUrl, err := util.DownloadAndUploadToOSS(ctx, imgUrl, ossObjectKey)
if err != nil || ossUrl == "" {
g.Log().Error(ctx, "下载上传图片失败:", bestMatchName, "错误:", err)
return ""
}
prefix := consts.S3Endpoint + "/" + consts.S3Bucket
if strings.HasPrefix(ossUrl, prefix) {
return consts.S3CustomDomain + ossUrl[len(prefix):]
finalUrl := consts.S3CustomDomain + ossUrl[len(prefix):]
g.Log().Debug(ctx, "图片处理成功:", bestMatchName, "最终URL:", finalUrl)
return finalUrl
}
g.Log().Debug(ctx, "图片处理成功:", bestMatchName, "OSS URL:", ossUrl)
return ossUrl
}
g.Log().Debug(ctx, "神器匹配失败:", artifactNameEn, "标准化后:", localNorm)
return ""
}
// 辅助函数
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}

View File

@@ -420,7 +420,7 @@ func (l *Logic) GetHeroDetailByCode(ctx context.Context, code string) (*v1.HeroD
return heroDetailVO, nil
}
// ClearHeroCache 清理英雄相关缓存
// 清理英雄相关缓存
func (l *Logic) ClearHeroCache(ctx context.Context, code string) error {
redis := g.Redis()

View File

@@ -19,5 +19,5 @@ database:
redis:
default:
address: "193.112.151.199:6379"
db: 1
db: 0
pass: "hu123"