- 修改 GetDetailReq 结构体,将 Code 字段改为 HeroCode - 更新 GetDetailRes 结构体,将 Rarity 字段改为 Stars,类型从 string 改为 int -调整 EpicHeroVO 结构体,移除 orm 标签,将 Rarity 改为 Stars- 更新相关控制器和逻辑层代码,以适应上述变更
476 lines
12 KiB
Go
476 lines
12 KiB
Go
package hero
|
||
|
||
import (
|
||
"context"
|
||
v1 "epic/api/hero/v1"
|
||
"epic/internal/dao"
|
||
"epic/internal/model/dto"
|
||
"epic/internal/model/entity"
|
||
"epic/internal/service"
|
||
"epic/utility"
|
||
"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) {
|
||
|
||
//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 // 数据库原始结构
|
||
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 := 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()
|
||
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)
|
||
}
|
||
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: 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, ",")
|
||
}
|