Files
epic-go/internal/logic/hero/hero.go
hxt c36a2cb8b0 ci(drone): 添加 Go 模块和构建缓存
- 在 restore cache 和 rebuild cache 步骤中添加了 go-mod-cache 和 go
2025-07-17 20:26:11 +08:00

499 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package hero
import (
"context"
v1 "epic/api/hero/v1"
"epic/internal/dao"
"epic/internal/logic/cron"
"epic/internal/model/dto"
"epic/internal/model/entity"
"epic/internal/service"
"epic/internal/util"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"math"
"math/rand"
"sort"
"strings"
"time"
)
type Logic struct{}
func New() *Logic {
return &Logic{}
}
func init() {
service.SetHero(New())
}
// GetHeroByCode 根据 code 获取英雄信息
func (l *Logic) GetHeroByCode(ctx context.Context, code string) (*entity.EpicHeroInfo, error) {
// 2. 缓存未命中,查数据库
var hero *entity.EpicHeroInfo
err := dao.EpicHeroInfo.Ctx(ctx).
Where(dao.EpicHeroInfo.Columns().HeroCode, code).
Scan(&hero)
if err != nil {
return nil, err
}
return hero, nil
}
// GetHeroList 查询所有英雄信息,并按创建时间倒序排列
func (l *Logic) GetHeroList(ctx context.Context) ([]*v1.EpicHeroVO, error) {
//util.RedisCache.Set(ctx, "epic_artifact_map_key111", "122", 1000*time.Second)
//util.RedisCache.Set(ctx, "epic_artifact_map_key222", "6565", 0)
//fmt.Println(util.RedisCache.Get(ctx, "NAME"))
var (
doList []*entity.EpicHeroInfo // 数据库原始结构
voList []*v1.EpicHeroVO // 要返回的视图结构
)
// 2. 缓存未命中,查数据库
err := dao.EpicHeroInfo.Ctx(ctx).
OrderDesc(dao.EpicHeroInfo.Columns().CreateTime). // 按创建时间倒序
Scan(&doList)
if err != nil {
return nil, err
}
// 3. 手动映射 DO -> VO
for _, hero := range doList {
voList = append(voList, &v1.EpicHeroVO{
Id: hero.Id,
HeroName: hero.HeroName,
HeroCode: hero.HeroCode,
Attribute: hero.Attribute,
HeadImgUrl: hero.HeadImgUrl,
HeroAttrLv60: hero.HeroAttrLv60,
NickName: hero.NickName,
Stars: gconv.Int(hero.Rarity),
Role: hero.Role,
Zodiac: hero.Zodiac,
Remark: hero.Remark,
})
}
return voList, nil
}
func (l *Logic) GetHeroDetailByCode(ctx context.Context, code string) (*v1.HeroDetailVO, error) {
var (
heroDetailVO *v1.HeroDetailVO
heroRespSimpleVO *v1.HeroRespSimpleVO
hero60AttributeVO *v1.Hero60AttributeVO
heroSetAvgVO *v1.HeroSetAvgVO
heroSetPercentVOS []*v1.HeroSetPercentVO
heroArtifactPercentVOS []*v1.HeroArtifactPercentVO
heroSetShowS []*v1.HeroSetShowVO
epicHeroInfo *entity.EpicHeroInfo
fribbleHeroSet *entity.FribbleHeroSet
heroSetData dto.HeroSetData
err error
)
err = dao.EpicHeroInfo.Ctx(ctx).
Where(dao.EpicHeroInfo.Columns().HeroCode, code).
Scan(&epicHeroInfo)
//设置基基本属性
if err := gconv.Struct(epicHeroInfo, &heroRespSimpleVO); err != nil {
return nil, err
}
//设置60级属性
err = gjson.DecodeTo(epicHeroInfo.HeroAttrLv60, &hero60AttributeVO)
if err != nil {
return nil, err
}
thirdPartySync := cron.NewThirdPartyDataSync()
// 优化:先查 RedisCache再查数据库
cacheKey := "epic_hero_set:" + code
jsonContent, err := util.RedisCache.Get(ctx, cacheKey)
if err == nil && !jsonContent.IsEmpty() {
fribbleHeroSet = &entity.FribbleHeroSet{
HeroCode: code,
JsonContent: jsonContent.String(),
}
} else {
err = dao.FribbleHeroSet.Ctx(ctx).
Where(dao.FribbleHeroSet.Columns().HeroCode, code).
Scan(&fribbleHeroSet)
if err != nil {
return nil, err
}
if util.RedisCache == nil {
panic("util.RedisCache is nil")
}
if fribbleHeroSet == nil {
// 新增如果fribbleHeroSet为nil调用第三方接口获取配装json
//thirdPartySync := cron.NewThirdPartyDataSync()
jsonStr, err := thirdPartySync.FetchHeroBuildsFromAPI(ctx, epicHeroInfo.HeroName)
if err != nil {
return nil, err
}
fribbleHeroSet = &entity.FribbleHeroSet{
HeroCode: code,
JsonContent: jsonStr,
}
}
if fribbleHeroSet.JsonContent != "" {
err := thirdPartySync.RefreshHeroSetContentByHeroInfo(ctx, epicHeroInfo.HeroName, code, fribbleHeroSet.JsonContent)
if err != nil {
return nil, err
}
// 写入 Redis 缓存,永久
util.RedisCache.Set(ctx, cacheKey, fribbleHeroSet.JsonContent, 0)
}
}
// 解析 JsonContent 字段
if err := gjson.DecodeTo(fribbleHeroSet.JsonContent, &heroSetData); err != nil {
return nil, err
}
// 优化:合并多次遍历 heroSetData.Data
var (
totalCp, totalAtk, totalHp, totalSpd, totalDef int
totalChc, totalChd, totalEff, totalEfr float64
setsNameCount = make(map[string]int)
artifactCount = make(map[string]int)
allItems []dto.HeroSetItem
)
//now := time.Now()
for i := range heroSetData.Data {
item := &heroSetData.Data[i]
// 1. 解析 SetsName
item.SetsName = GetSetNames(item.Sets)
// 2. 累加属性
totalCp += item.Gs
totalAtk += item.Atk
totalHp += item.Hp
totalSpd += item.Speed
totalDef += item.Def
totalChc += float64(item.Chc)
totalChd += float64(item.Chd)
totalEff += float64(item.Eff)
totalEfr += float64(item.Efr)
// 3. 统计套装组合
if item.SetsName != "" {
setsNameCount[item.SetsName]++
}
// 4. 统计神器
if item.ArtifactCode != "" {
artifactCount[item.ArtifactCode]++
}
// 5. 收集 heroSetShowS 原始数据
allItems = append(allItems, *item)
}
count := float64(len(heroSetData.Data))
if count > 0 {
heroSetAvgVO = &v1.HeroSetAvgVO{
Cp: int(float64(totalCp) / count),
Atk: int(float64(totalAtk) / count),
Hp: int(float64(totalHp) / count),
Spd: int(float64(totalSpd) / count),
Def: int(float64(totalDef) / count),
Chc: totalChc / count,
Chd: totalChd / count,
Dac: 0,
Eff: totalEff / count,
Efr: totalEfr / count,
}
}
// 统计所有套装组合SetsName出现次数
setsNameCount = make(map[string]int)
total := len(heroSetData.Data)
for _, item := range heroSetData.Data {
setsName := item.SetsName
if setsName != "" {
setsNameCount[setsName]++
}
}
// 生成百分比VO并按百分比从大到小排序
type percentVO struct {
SetName string
Percent float64
}
var percentVOList []percentVO
for setsName, cnt := range setsNameCount {
percent := 0.0
if total > 0 {
percent = float64(cnt) * 100.0 / float64(total) / 100.0 // 得到 0.156 而不是 15.6
}
percent = math.Round(percent*1000) / 1000 // 保留三位小数
percentVOList = append(percentVOList, percentVO{
SetName: setsName,
Percent: percent,
})
}
// 排序
sort.Slice(percentVOList, func(i, j int) bool {
return percentVOList[i].Percent > percentVOList[j].Percent
})
heroSetPercentVOS = make([]*v1.HeroSetPercentVO, 0, len(percentVOList))
for _, vo := range percentVOList {
heroSetPercentVOS = append(heroSetPercentVOS, &v1.HeroSetPercentVO{
SetName: vo.SetName,
Percent: vo.Percent,
})
}
// 只保留占比最多的前三种套装类型
if len(heroSetPercentVOS) > 3 {
heroSetPercentVOS = heroSetPercentVOS[:3]
}
// 计算百分比并排序
type artifactPercentVO struct {
ArtifactCode string
Percent float64
}
var artifactPercentVOList []artifactPercentVO
totalArtifact := len(heroSetData.Data)
for code, cnt := range artifactCount {
percent := 0.0
if totalArtifact > 0 {
percent = float64(cnt) * 100.0 / float64(total) / 100.0 // 得到 0.156 而不是 15.6
}
percent = math.Round(percent*1000) / 1000 // 保留三位小数
artifactPercentVOList = append(artifactPercentVOList, artifactPercentVO{
ArtifactCode: code,
Percent: percent,
})
}
sort.Slice(artifactPercentVOList, func(i, j int) bool {
return artifactPercentVOList[i].Percent > artifactPercentVOList[j].Percent
})
// 查询神器详细信息
artifactInfoMap := make(map[string]*entity.EpicArtifactInfo)
if len(artifactPercentVOList) > 0 {
codes := make([]string, 0, len(artifactPercentVOList))
for _, vo := range artifactPercentVOList {
codes = append(codes, vo.ArtifactCode)
}
var artifactInfos []*entity.EpicArtifactInfo
err := dao.EpicArtifactInfo.Ctx(ctx).WhereIn(dao.EpicArtifactInfo.Columns().ArtifactCode, codes).Scan(&artifactInfos)
if err == nil {
for _, info := range artifactInfos {
artifactInfoMap[info.ArtifactCode] = info
}
}
}
heroArtifactPercentVOS = make([]*v1.HeroArtifactPercentVO, 0, 3)
for i, vo := range artifactPercentVOList {
if i >= 3 {
break
}
info := artifactInfoMap[vo.ArtifactCode]
heroArtifactPercentVOS = append(heroArtifactPercentVOS, &v1.HeroArtifactPercentVO{
ArtifactCode: vo.ArtifactCode,
ArtifactName: func() string {
if info != nil {
return info.ArtifactName
} else {
return ""
}
}(),
Rarity: func() string {
if info != nil {
return info.Rarity
} else {
return ""
}
}(),
Role: func() string {
if info != nil {
return info.Role
} else {
return ""
}
}(),
ImageUrl: func() string {
if info != nil {
return info.ImageUrl
} else {
return ""
}
}(),
Percent: vo.Percent,
})
}
// 生成 heroSetShowS取最新500条评分前30%随机取6个
allItems = heroSetData.Data
// 按 CreateDate 降序排序
sort.Slice(allItems, func(i, j int) bool {
t1, err1 := time.Parse("2006-01-02 15:04:05", allItems[i].CreateDate)
t2, err2 := time.Parse("2006-01-02 15:04:05", allItems[j].CreateDate)
if err1 != nil && err2 != nil {
return false
}
if err1 != nil {
return false
}
if err2 != nil {
return true
}
return t1.After(t2)
})
// 取最新600条
maxN := 600
if len(allItems) > maxN {
allItems = allItems[:maxN]
}
// 按 Gs 降序排序
sort.Slice(allItems, func(i, j int) bool {
return allItems[i].Gs > allItems[j].Gs
})
// 取前30%
topN := int(float64(len(allItems)) * 0.3)
if topN < 1 {
topN = len(allItems)
}
if topN > len(allItems) {
topN = len(allItems)
}
topItems := allItems[:topN]
// 随机取6个
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(topItems), func(i, j int) { topItems[i], topItems[j] = topItems[j], topItems[i] })
pickN := 6
if len(topItems) < pickN {
pickN = len(topItems)
}
picked := topItems[:pickN]
// 组装 HeroSetShowVO
heroSetShowS = make([]*v1.HeroSetShowVO, 0, pickN)
for _, item := range picked {
heroSetShowS = append(heroSetShowS, &v1.HeroSetShowVO{
Cp: item.Gs,
Atk: item.Atk,
Hp: item.Hp,
Spd: item.Speed,
Def: item.Def,
Chc: float64(item.Chc),
Chd: float64(item.Chd),
Dac: 0,
Eff: float64(item.Eff),
Efr: float64(item.Efr),
Hds: GetSetNames(item.Sets),
Ctr: item.CreateDate,
ArfName: func() string {
info := artifactInfoMap[item.ArtifactCode]
if info != nil {
return info.ArtifactName
}
return ""
}(),
ArfPic: func() string {
info := artifactInfoMap[item.ArtifactCode]
if info != nil {
return info.ImageUrl
}
return ""
}(),
})
}
heroDetailVO = &v1.HeroDetailVO{
HeroRespSimpleVO: heroRespSimpleVO,
Hero60AttributeVO: hero60AttributeVO,
HeroSetAvgVO: heroSetAvgVO,
HeroSetPercentVOS: heroSetPercentVOS,
HeroArtifactPercentVOS: heroArtifactPercentVOS,
HeroSetShows: heroSetShowS,
}
return heroDetailVO, nil
}
// ClearHeroCache 清理英雄相关缓存
func (l *Logic) ClearHeroCache(ctx context.Context, code string) error {
redis := g.Redis()
// 清理单个英雄缓存
if code != "" {
cacheKey := "hero:" + code
redis.Del(ctx, cacheKey)
}
// 清理英雄列表缓存
redis.Del(ctx, "hero:list")
return nil
}
// 公用套装名称映射和判断
var setTypeNameMap = map[string]string{
"set_speed": "速度",
"set_cri": "暴击",
"set_revenge": "复仇",
"set_counter": "反击",
"set_vampire": "吸血",
"set_immune": "免疫",
"set_cri_dmg": "破灭",
"set_torrent": "激流",
"set_max_hp": "生命",
"set_penetrate": "穿透",
"set_scar": "伤口",
"set_shield": "护盾",
"set_def": "防御",
"set_acc": "命中",
"set_res": "抵抗",
}
func isTwoSet(setType string) bool {
twoSet := map[string]struct{}{
"set_acc": {}, "set_cri": {}, "set_immune": {}, "set_torrent": {},
"set_max_hp": {}, "set_penetrate": {}, "set_def": {}, "set_res": {},
}
_, ok := twoSet[setType]
return ok
}
func isFourSet(setType string) bool {
fourSet := map[string]struct{}{
"set_speed": {}, "set_cri_dmg": {}, "set_revenge": {}, "set_counter": {},
"set_vampire": {}, "set_scar": {}, "set_shield": {},
}
_, ok := fourSet[setType]
return ok
}
// GetSetNames 公用方法:根据套装 map 生成套装中文名
func GetSetNames(sets map[string]int) string {
var fourNames, twoNames []string
for setType, count := range sets {
if isFourSet(setType) && count >= 4 {
if cn, ok := setTypeNameMap[setType]; ok {
fourNames = append(fourNames, cn)
} else {
fourNames = append(fourNames, setType)
}
}
}
for setType, count := range sets {
if isTwoSet(setType) && count >= 2 {
if cn, ok := setTypeNameMap[setType]; ok {
twoNames = append(twoNames, cn)
} else {
twoNames = append(twoNames, setType)
}
}
}
allNames := append(fourNames, twoNames...)
return strings.Join(allNames, ",")
}