// Package roomtime runs room presence outbox consumers and exposes in-room duration compensation. package roomtime import ( "context" "time" "hyapp/pkg/xerr" roomtimedomain "hyapp/services/user-service/internal/domain/roomtime" ) // Repository applies room presence events and compensates abnormal open sessions. type Repository interface { ProcessRoomPresenceEvent(ctx context.Context, event roomtimedomain.RoomPresenceEvent) (roomtimedomain.ApplyResult, error) CompensateOpenSessions(ctx context.Context, options roomtimedomain.CompensationOptions) (roomtimedomain.CompensationResult, error) } // Service owns the user in-room duration read model. type Service struct { repository Repository } // CompensationWorkerOptions controls abnormal open session closure. type CompensationWorkerOptions struct { WorkerID string BatchSize int OpenSessionMaxAge time.Duration } // New creates a room time service with the repository that owns idempotent application. func New(repository Repository) *Service { return &Service{repository: repository} } // ConsumeRoomPresenceEvent applies one MQ-delivered RoomUserJoined/RoomUserLeft/RoomUserKicked fact. func (s *Service) ConsumeRoomPresenceEvent(ctx context.Context, event roomtimedomain.RoomPresenceEvent) (roomtimedomain.ApplyResult, error) { if s == nil || s.repository == nil { return roomtimedomain.ApplyResult{}, xerr.New(xerr.Unavailable, "room time repository is not configured") } switch event.EventType { case roomtimedomain.RoomUserJoinedEventType, roomtimedomain.RoomUserLeftEventType, roomtimedomain.RoomUserKickedEventType: default: return roomtimedomain.ApplyResult{Skipped: true, Reason: "unsupported_event_type"}, nil } return s.repository.ProcessRoomPresenceEvent(ctx, event) } // ProcessOpenSessionCompensationBatch exposes one bounded compensation scan for cron-service. func (s *Service) ProcessOpenSessionCompensationBatch(ctx context.Context, options CompensationWorkerOptions) (roomtimedomain.CompensationResult, error) { if s == nil || s.repository == nil { return roomtimedomain.CompensationResult{}, xerr.New(xerr.Unavailable, "room time repository is not configured") } if options.BatchSize <= 0 { options.BatchSize = 200 } if options.OpenSessionMaxAge <= 0 { // stale worker 2 分钟内会把断线用户判离房;这里只兜 room-service 节点崩溃或 outbox 丢失的极端场景。 options.OpenSessionMaxAge = 24 * time.Hour } return s.repository.CompensateOpenSessions(ctx, roomtimedomain.CompensationOptions{ NowMs: time.Now().UnixMilli(), BatchSize: options.BatchSize, OpenSessionMaxAgeMs: options.OpenSessionMaxAge.Milliseconds(), }) }