Persist live status and migrate legacy live data

- Add user_live_status to store hi score, hi combo count, and clear count per chart
- Expand user_live_goal to persist achieved live_goal_reward_id entries instead of only coarse goal types
- Update live reward handling to refresh live status and sync newly achieved live goals on every clear
- Return live status and achieved goal ids from stored user data in /api/live/status
- Aggregate profile liveCnt from stored user_live_status instead of hardcoded values
- Load complete-rank thresholds from live info for goal evaluation
- Migrate legacy user_live_record data into user_live_status and backfill achievable live goals on startup

Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
2026-06-06 16:25:06 +08:00
parent f8a0f2db13
commit f6466989c6
11 changed files with 731 additions and 86 deletions
+76 -23
View File
@@ -1,34 +1,87 @@
package profile
import (
usermodel "honoka-chan/internal/model/user"
profileapischema "honoka-chan/internal/schema/api/profile"
"honoka-chan/internal/session"
"sort"
"time"
"github.com/gin-gonic/gin"
)
func liveCnt() (res any, err error) {
type profileLiveDifficultyRow struct {
LiveDifficultyID int `xorm:"live_difficulty_id"`
Difficulty int `xorm:"difficulty"`
}
func liveCnt(ctx *gin.Context, targetUserID int) (res any, err error) {
ss := session.Get(ctx)
targetPref, err := getTargetUserPref(ss, targetUserID)
if err != nil {
return nil, err
}
difficultyByLiveID := map[int]int{}
loadDifficultyRows := func(table string) error {
rows := []profileLiveDifficultyRow{}
err := ss.MainEng.Table(table).Alias("live").
Join("LEFT", "live_setting_m setting", "live.live_setting_id = setting.live_setting_id").
Select("live.live_difficulty_id, setting.difficulty").
Find(&rows)
if err != nil {
return err
}
for _, row := range rows {
difficultyByLiveID[row.LiveDifficultyID] = row.Difficulty
}
return nil
}
if err := loadDifficultyRows("normal_live_m"); err != nil {
return nil, err
}
if err := loadDifficultyRows("special_live_m"); err != nil {
return nil, err
}
statusRows := []usermodel.UserLiveStatus{}
err = ss.UserEng.Table(new(usermodel.UserLiveStatus)).
Where("user_id = ?", targetPref.UserID).
Where("clear_cnt > 0").
Cols("live_difficulty_id", "clear_cnt").
Find(&statusRows)
if err != nil {
return nil, err
}
difficultyCounts := map[int]int{}
for _, row := range statusRows {
difficulty, ok := difficultyByLiveID[row.LiveDifficultyID]
if !ok {
continue
}
if difficulty == 5 {
continue
}
difficultyCounts[difficulty]++
}
orderedDifficulties := []int{1, 2, 3, 4, 6}
result := make([]profileapischema.LiveCntData, 0, len(orderedDifficulties))
for _, difficulty := range orderedDifficulties {
result = append(result, profileapischema.LiveCntData{
Difficulty: difficulty,
ClearCnt: difficultyCounts[difficulty],
})
}
sort.Slice(result, func(i, j int) bool {
return result[i].Difficulty < result[j].Difficulty
})
res = profileapischema.LiveCntResp{
Result: []profileapischema.LiveCntData{
{
Difficulty: 1,
ClearCnt: 315,
},
{
Difficulty: 2,
ClearCnt: 310,
},
{
Difficulty: 3,
ClearCnt: 314,
},
{
Difficulty: 4,
ClearCnt: 455,
},
{
Difficulty: 6,
ClearCnt: 233,
},
},
Result: result,
Status: 200,
CommandNum: false,
TimeStamp: time.Now().Unix(),