package hero import ( "context" v1 "epic/api/hero/v1" "epic/internal/dao" "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 } // 优化:先查 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 } // 写入 Redis 缓存,1小时 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, ",") }