@@ -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 . Cod e] ; ok {
if v , ok := artifactDbMap [ art . Nam e] ; 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
@@ -492,7 +524,7 @@ func (t *ThirdPartyDataSync) RefreshHeroSetContentByHeroInfo(ctx context.Context
}
}
//刷新所有角色配装字段
// 刷新所有角色配装字段
func ( t * ThirdPartyDataSync ) RefreshAllHeroSetContent ( ctx context . Context ) error {
g . Log ( ) . Info ( ctx , "开始批量刷新所有角色配装字段..." )
@@ -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
}