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 } 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{ 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 (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 }