feat(hero): 英雄详情页面增加套装和神器统计功能
- 新增英雄详情接口,包含60级属性、套装平均属性、套装占比、神器占比等信息- 优化数据处理逻辑,提高查询效率 - 增加缓存机制,减少数据库访问- 新增数据传输对象HeroSetData和HeroSetItem,用于处理套装数据
This commit is contained in:
@@ -4,11 +4,17 @@ import (
|
||||
"context"
|
||||
v1 "epic/api/hero/v1"
|
||||
"epic/internal/dao"
|
||||
"epic/internal/model/dto"
|
||||
"epic/internal/model/entity"
|
||||
"epic/internal/service"
|
||||
"epic/utility"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -40,9 +46,9 @@ func (l *Logic) GetHeroByCode(ctx context.Context, code string) (*entity.EpicHer
|
||||
// GetHeroList 查询所有英雄信息,并按创建时间倒序排列
|
||||
func (l *Logic) GetHeroList(ctx context.Context) ([]*v1.EpicHeroVO, error) {
|
||||
|
||||
utility.RedisCache.Set(ctx, "epic_artifact_map_key111", "122", 1000*time.Second)
|
||||
utility.RedisCache.Set(ctx, "epic_artifact_map_key222", "6565", 0)
|
||||
fmt.Println(utility.RedisCache.Get(ctx, "NAME"))
|
||||
//utility.RedisCache.Set(ctx, "epic_artifact_map_key111", "122", 1000*time.Second)
|
||||
//utility.RedisCache.Set(ctx, "epic_artifact_map_key222", "6565", 0)
|
||||
//fmt.Println(utility.RedisCache.Get(ctx, "NAME"))
|
||||
|
||||
var (
|
||||
doList []*entity.EpicHeroInfo // 数据库原始结构
|
||||
@@ -78,8 +84,370 @@ func (l *Logic) GetHeroList(ctx context.Context) ([]*v1.EpicHeroVO, error) {
|
||||
}
|
||||
|
||||
func (l *Logic) GetHeroDetailByCode(ctx context.Context, code string) (*v1.HeroDetailVO, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
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 := utility.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小时
|
||||
utility.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()
|
||||
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": "抵抗",
|
||||
}
|
||||
isTwoSet := func(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
|
||||
}
|
||||
isFourSet := func(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
|
||||
}
|
||||
for i := range heroSetData.Data {
|
||||
item := &heroSetData.Data[i]
|
||||
// 1. 解析 SetsName
|
||||
var fourNames, twoNames []string
|
||||
for setType, count := range item.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 item.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...)
|
||||
item.SetsName = strings.Join(allNames, ",")
|
||||
// 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)
|
||||
}
|
||||
percent = math.Round(percent*10) / 10 // 保留一位小数
|
||||
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(totalArtifact)
|
||||
}
|
||||
percent = math.Round(percent*10) / 10
|
||||
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: "",
|
||||
Ctr: "",
|
||||
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 清理英雄相关缓存
|
||||
|
||||
Reference in New Issue
Block a user