2026-05-16 06:13:15 +08:00

210 lines
6.6 KiB
Go

package voiceroomrocket
import (
"context"
"errors"
"math"
"net/http"
"time"
"chatapp3-golang/internal/model"
"chatapp3-golang/internal/utils"
"gorm.io/gorm"
)
// GetStatus 查询房间当前火箭状态,懒初始化当天状态。
func (s *Service) GetStatus(ctx context.Context, user AuthUser, roomID int64) (*StatusResponse, error) {
if roomID <= 0 {
return nil, NewAppError(http.StatusBadRequest, "missing_room_id", "roomId is required")
}
snapshot, err := s.loadConfig(ctx, user.SysOrigin)
if err != nil {
return nil, err
}
if !snapshot.Enabled {
return disabledStatusResponse(snapshot, roomID), nil
}
local := resolveLocation(snapshot.Timezone)
now := time.Now().In(local)
status, err := s.loadOrInitStatus(ctx, nil, snapshot.SysOrigin, roomID, dayKey(now, local), now)
if err != nil {
return nil, err
}
levels, err := s.loadLevelConfigs(ctx, snapshot.SysOrigin, true)
if err != nil {
return nil, err
}
currentLevelConfig, err := s.loadLevelConfig(ctx, snapshot.SysOrigin, status.CurrentLevel)
if err != nil {
return nil, err
}
rewards, err := s.loadRewardConfigs(ctx, snapshot.SysOrigin, status.CurrentLevel)
if err != nil {
return nil, err
}
percent, displayPercent := decimalPercent(status.CurrentEnergy, status.NeedEnergy)
shake := status.NeedEnergy > 0 &&
float64(status.CurrentEnergy)*100/float64(status.NeedEnergy) >= parsePercent(currentLevelConfig.ShakeThresholdPercent)
kings, _ := s.topKingRecords(ctx, snapshot.SysOrigin, roomID, status.DayKey, status.RoundNo, status.CurrentLevel, 3)
countdownSeconds := s.launchCountdownSeconds(ctx, status, time.Now())
return &StatusResponse{
Configured: snapshot.Configured,
SysOrigin: snapshot.SysOrigin,
Enabled: snapshot.Enabled,
Timezone: snapshot.Timezone,
RoomID: roomID,
DayKey: status.DayKey,
RoundNo: status.RoundNo,
DisplayRound: status.RoundNo > 1,
Status: status.Status,
CurrentLevel: status.CurrentLevel,
CurrentEnergy: status.CurrentEnergy,
NeedEnergy: status.NeedEnergy,
EnergyPercent: percent,
DisplayPercent: displayPercent,
LaunchCountdownSeconds: countdownSeconds,
Shake: shake,
PreviewRocketURL: currentLevelConfig.PreviewRocketURL,
RocketIconURL: currentLevelConfig.RocketIconURL,
RocketAnimationURL: currentLevelConfig.RocketAnimationURL,
Levels: buildLevelPayloads(levels),
RewardPreview: rewardPreviewPayload(rewards),
RocketKings: kings,
}, nil
}
func disabledStatusResponse(snapshot configSnapshot, roomID int64) *StatusResponse {
return &StatusResponse{
Configured: snapshot.Configured,
SysOrigin: snapshot.SysOrigin,
Enabled: false,
Timezone: snapshot.Timezone,
RoomID: roomID,
Status: statusDisabled,
Levels: []LevelConfigPayload{},
RewardPreview: RewardPreviewResponse{},
RocketKings: []KingRecord{},
}
}
func (s *Service) loadOrInitStatus(ctx context.Context, tx *gorm.DB, sysOrigin string, roomID int64, day string, now time.Time) (model.VoiceRoomRocketStatus, error) {
db := s.repo.DB.WithContext(ctx)
if tx != nil {
db = tx.WithContext(ctx)
}
var row model.VoiceRoomRocketStatus
err := db.Where("sys_origin = ? AND room_id = ? AND day_key = ?", sysOrigin, roomID, day).First(&row).Error
if err == nil {
return row, nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return model.VoiceRoomRocketStatus{}, err
}
levelConfig, err := s.loadLevelConfig(ctx, sysOrigin, 1)
if err != nil {
return model.VoiceRoomRocketStatus{}, err
}
id, err := utils.NextID()
if err != nil {
return model.VoiceRoomRocketStatus{}, err
}
row = model.VoiceRoomRocketStatus{
ID: id,
SysOrigin: sysOrigin,
RoomID: roomID,
DayKey: day,
RoundNo: 1,
CurrentLevel: 1,
CurrentEnergy: 0,
NeedEnergy: levelConfig.NeedEnergy,
Status: statusCharging,
LevelStartTime: now,
CreateTime: now,
UpdateTime: now,
}
if err := db.Create(&row).Error; err != nil {
if isDuplicateKeyErr(err) {
var existing model.VoiceRoomRocketStatus
if loadErr := db.Where("sys_origin = ? AND room_id = ? AND day_key = ?", sysOrigin, roomID, day).First(&existing).Error; loadErr != nil {
return model.VoiceRoomRocketStatus{}, loadErr
}
return existing, nil
}
return model.VoiceRoomRocketStatus{}, err
}
if err := s.ensureLevelRound(ctx, tx, sysOrigin, roomID, day, 1, 1, levelConfig.NeedEnergy, now); err != nil {
return model.VoiceRoomRocketStatus{}, err
}
return row, nil
}
func (s *Service) launchCountdownSeconds(ctx context.Context, status model.VoiceRoomRocketStatus, now time.Time) int {
if status.Status != statusLaunching {
return 0
}
var round model.VoiceRoomRocketLevelRound
err := s.repo.DB.WithContext(ctx).
Where(
"sys_origin = ? AND room_id = ? AND day_key = ? AND round_no = ? AND level = ?",
status.SysOrigin, status.RoomID, status.DayKey, status.RoundNo, status.CurrentLevel,
).
First(&round).Error
if err != nil || round.ReachTime == nil || round.ReachTime.IsZero() {
return defaultLaunchDelaySeconds
}
due := round.ReachTime.Add(time.Duration(defaultLaunchDelaySeconds) * time.Second)
remaining := due.Sub(now).Seconds()
if remaining <= 0 {
return 0
}
return int(math.Ceil(remaining))
}
func (s *Service) ensureLevelRound(ctx context.Context, tx *gorm.DB, sysOrigin string, roomID int64, day string, roundNo int, level int, needEnergy int64, now time.Time) error {
db := s.repo.DB.WithContext(ctx)
if tx != nil {
db = tx.WithContext(ctx)
}
var row model.VoiceRoomRocketLevelRound
err := db.Where(
"sys_origin = ? AND room_id = ? AND day_key = ? AND round_no = ? AND level = ?",
sysOrigin, roomID, day, roundNo, level,
).First(&row).Error
if err == nil {
return nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
id, err := utils.NextID()
if err != nil {
return err
}
row = model.VoiceRoomRocketLevelRound{
ID: id,
SysOrigin: sysOrigin,
RoomID: roomID,
DayKey: day,
RoundNo: roundNo,
Level: level,
NeedEnergy: needEnergy,
FinalEnergy: 0,
Status: levelRoundStatusCharging,
CreateTime: now,
UpdateTime: now,
}
if err := db.Create(&row).Error; err != nil && !isDuplicateKeyErr(err) {
return err
}
return nil
}
func buildLevelPayloads(rows []model.VoiceRoomRocketLevelConfig) []LevelConfigPayload {
result := make([]LevelConfigPayload, 0, len(rows))
for _, row := range rows {
result = append(result, levelPayload(row))
}
return result
}