143 lines
3.6 KiB
Go

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"
}