package voiceroomrocket import ( "context" "errors" "strconv" "strings" "time" "chatapp3-golang/internal/model" "github.com/redis/go-redis/v9" "gorm.io/gorm" "gorm.io/gorm/clause" ) // ProcessDueLaunches 扫描进度满 10 秒后的火箭发射任务。 func (s *Service) ProcessDueLaunches(ctx context.Context, now time.Time, limit int64) (int, error) { if limit <= 0 { limit = int64(defaultWorkerBatchSize) } processed := 0 if s.repo.Redis != nil { statusIDs, err := s.repo.Redis.ZRangeByScore(ctx, s.launchDueZSetKey(), &redis.ZRangeBy{ Min: "-inf", Max: strconv.FormatInt(now.Unix(), 10), Count: limit, }).Result() if err != nil { return 0, err } for _, rawID := range statusIDs { statusID, parseErr := strconv.ParseInt(strings.TrimSpace(rawID), 10, 64) if parseErr != nil || statusID <= 0 { _ = s.repo.Redis.ZRem(context.Background(), s.launchDueZSetKey(), rawID).Err() continue } done, err := s.processDueLaunchStatus(ctx, statusID, now) if err != nil { return processed, err } if done { if err := s.repo.Redis.ZRem(context.Background(), s.launchDueZSetKey(), rawID).Err(); err != nil { return processed, err } processed++ } } } backfilled, err := s.processStaleLaunchingStatuses(ctx, now, limit) if err != nil { return processed, err } return processed + backfilled, nil } func (s *Service) processStaleLaunchingStatuses(ctx context.Context, now time.Time, limit int64) (int, error) { if limit <= 0 { limit = int64(defaultWorkerBatchSize) } var rows []model.VoiceRoomRocketStatus if err := s.repo.DB.WithContext(ctx). Where("status = ?", statusLaunching). Order("update_time ASC"). Limit(int(limit)). Find(&rows).Error; err != nil { return 0, err } processed := 0 for _, row := range rows { done, err := s.processDueLaunchStatus(ctx, row.ID, now) if err != nil { return processed, err } if done { processed++ } } return processed, nil } func (s *Service) processDueLaunchStatus(ctx context.Context, statusID int64, now time.Time) (bool, error) { var launch model.VoiceRoomRocketLaunch done := false var launched bool err := s.repo.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var statusRow model.VoiceRoomRocketStatus err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("id = ?", statusID). First(&statusRow).Error if errors.Is(err, gorm.ErrRecordNotFound) { done = true return nil } if err != nil { return err } if statusRow.Status != statusLaunching || statusRow.CurrentEnergy < statusRow.NeedEnergy { done = true return nil } var round model.VoiceRoomRocketLevelRound err = tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where( "sys_origin = ? AND room_id = ? AND day_key = ? AND round_no = ? AND level = ?", statusRow.SysOrigin, statusRow.RoomID, statusRow.DayKey, statusRow.RoundNo, statusRow.CurrentLevel, ). First(&round).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil } if err != nil { return err } if round.ReachTime == nil || round.IgniteUserID == nil || *round.IgniteUserID <= 0 { return nil } due := round.ReachTime.Add(time.Duration(defaultLaunchDelaySeconds) * time.Second) if now.Before(due) { return nil } result, err := s.launchLocked(ctx, tx, statusRow, *round.IgniteUserID, now) if err != nil { return err } launch = result.Launch launched = result.Launched done = true return nil }) if err != nil { return false, err } if launched { _ = s.notifyLaunch(context.Background(), launch) } return done, nil } func (s *Service) launchDueZSetKey() string { return "voice_room:rocket:launch_due" }