feat(hero): 英雄详情页面增加套装和神器统计功能

- 新增英雄详情接口,包含60级属性、套装平均属性、套装占比、神器占比等信息- 优化数据处理逻辑,提高查询效率
- 增加缓存机制,减少数据库访问- 新增数据传输对象HeroSetData和HeroSetItem,用于处理套装数据
This commit is contained in:
hxt
2025-06-22 22:29:12 +08:00
parent 61cb21fdc7
commit e0cd5d3428
5 changed files with 404 additions and 15 deletions

View File

@@ -1,7 +0,0 @@
ROOT_DIR = $(shell pwd)
NAMESPACE = "default"
DEPLOY_NAME = "template-single"
DOCKER_NAME = "template-single"
include ./hack/hack-cli.mk
include ./hack/hack.mk

View File

@@ -26,5 +26,5 @@ type GetDetailReq struct {
Code string `v:"required" dc:"角色code"`
}
type GetDetailRes struct {
Data *HeroDetailVO `json:"data"`
*HeroDetailVO
}

View File

@@ -39,7 +39,7 @@ func (c *ControllerV1) GetDetail(ctx context.Context, req *v1.GetDetailReq) (res
}
res = &v1.GetDetailRes{
Data: detail,
HeroDetailVO: detail,
}
return res, nil
}

View File

@@ -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 清理英雄相关缓存

View File

@@ -0,0 +1,28 @@
package dto
type HeroSetData struct {
Data []HeroSetItem `json:"data"`
}
type HeroSetItem struct {
ArtifactCode string `json:"artifactCode"`
Atk int `json:"atk"`
Chc int `json:"chc"`
Chd int `json:"chd"`
CreateDate string `json:"createDate"`
Def int `json:"def"`
Eff int `json:"eff"`
Efr int `json:"efr"`
Gs int `json:"gs"`
Hp int `json:"hp"`
Speed int `json:"spd"` // 注意字段名 spd 在 json 中是 speed
UnitCode string `json:"unitCode"`
UnitName string `json:"unitName"`
Sets map[string]int `json:"sets"` // 动态套装类型
SetsName string `json:"setsName"` // 解析后触发的套装效果名称,例如 "速度,生命"
}
type HeroSetGroup struct {
SetImmune int `json:"set_immune"`
SetVampire int `json:"set_vampire"`
}