feat(hero): 英雄详情页面增加套装和神器统计功能
- 新增英雄详情接口,包含60级属性、套装平均属性、套装占比、神器占比等信息- 优化数据处理逻辑,提高查询效率 - 增加缓存机制,减少数据库访问- 新增数据传输对象HeroSetData和HeroSetItem,用于处理套装数据
This commit is contained in:
7
Makefile
7
Makefile
@@ -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
|
|
||||||
@@ -26,5 +26,5 @@ type GetDetailReq struct {
|
|||||||
Code string `v:"required" dc:"角色code"`
|
Code string `v:"required" dc:"角色code"`
|
||||||
}
|
}
|
||||||
type GetDetailRes struct {
|
type GetDetailRes struct {
|
||||||
Data *HeroDetailVO `json:"data"`
|
*HeroDetailVO
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (c *ControllerV1) GetDetail(ctx context.Context, req *v1.GetDetailReq) (res
|
|||||||
}
|
}
|
||||||
|
|
||||||
res = &v1.GetDetailRes{
|
res = &v1.GetDetailRes{
|
||||||
Data: detail,
|
HeroDetailVO: detail,
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
v1 "epic/api/hero/v1"
|
v1 "epic/api/hero/v1"
|
||||||
"epic/internal/dao"
|
"epic/internal/dao"
|
||||||
|
"epic/internal/model/dto"
|
||||||
"epic/internal/model/entity"
|
"epic/internal/model/entity"
|
||||||
"epic/internal/service"
|
"epic/internal/service"
|
||||||
"epic/utility"
|
"epic/utility"
|
||||||
"fmt"
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,9 +46,9 @@ func (l *Logic) GetHeroByCode(ctx context.Context, code string) (*entity.EpicHer
|
|||||||
// GetHeroList 查询所有英雄信息,并按创建时间倒序排列
|
// GetHeroList 查询所有英雄信息,并按创建时间倒序排列
|
||||||
func (l *Logic) GetHeroList(ctx context.Context) ([]*v1.EpicHeroVO, error) {
|
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_key111", "122", 1000*time.Second)
|
||||||
utility.RedisCache.Set(ctx, "epic_artifact_map_key222", "6565", 0)
|
//utility.RedisCache.Set(ctx, "epic_artifact_map_key222", "6565", 0)
|
||||||
fmt.Println(utility.RedisCache.Get(ctx, "NAME"))
|
//fmt.Println(utility.RedisCache.Get(ctx, "NAME"))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
doList []*entity.EpicHeroInfo // 数据库原始结构
|
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) {
|
func (l *Logic) GetHeroDetailByCode(ctx context.Context, code string) (*v1.HeroDetailVO, error) {
|
||||||
//TODO implement me
|
var (
|
||||||
panic("implement me")
|
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 清理英雄相关缓存
|
// ClearHeroCache 清理英雄相关缓存
|
||||||
|
|||||||
28
internal/model/dto/hero_set_dto.go
Normal file
28
internal/model/dto/hero_set_dto.go
Normal 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"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user