2026-05-25 15:45:57 +08:00

77 lines
3.1 KiB
Go

// Package mictime runs room mic outbox consumers and exposes mic duration queries.
package mictime
import (
"context"
"time"
"hyapp/pkg/xerr"
mictimedomain "hyapp/services/user-service/internal/domain/mictime"
)
// Repository applies room mic events and returns user duration aggregates.
type Repository interface {
ProcessRoomMicEvent(ctx context.Context, event mictimedomain.RoomMicEvent) (mictimedomain.ApplyResult, error)
CompensateOpenSessions(ctx context.Context, options mictimedomain.CompensationOptions) (mictimedomain.CompensationResult, error)
GetLifetimeStats(ctx context.Context, appCode string, userID int64) (mictimedomain.LifetimeStats, error)
}
// Service owns the user mic duration read model.
type Service struct {
repository Repository
}
// CompensationWorkerOptions controls abnormal open session closure.
type CompensationWorkerOptions struct {
WorkerID string
BatchSize int
PendingPublishMaxAge time.Duration
PublishingSessionMaxAge time.Duration
}
// New creates a mic time service with the repository that owns idempotent application.
func New(repository Repository) *Service {
return &Service{repository: repository}
}
// GetLifetimeStats returns user-wide accumulated mic duration.
func (s *Service) GetLifetimeStats(ctx context.Context, appCode string, userID int64) (mictimedomain.LifetimeStats, error) {
if s == nil || s.repository == nil {
return mictimedomain.LifetimeStats{}, xerr.New(xerr.Unavailable, "mic time repository is not configured")
}
return s.repository.GetLifetimeStats(ctx, appCode, userID)
}
// ConsumeRoomMicEvent applies one MQ-delivered RoomMicChanged fact.
func (s *Service) ConsumeRoomMicEvent(ctx context.Context, event mictimedomain.RoomMicEvent) (mictimedomain.ApplyResult, error) {
if s == nil || s.repository == nil {
return mictimedomain.ApplyResult{}, xerr.New(xerr.Unavailable, "mic time repository is not configured")
}
if event.EventType != mictimedomain.RoomMicChangedEventType {
return mictimedomain.ApplyResult{Skipped: true, Reason: "unsupported_event_type"}, nil
}
return s.repository.ProcessRoomMicEvent(ctx, event)
}
// ProcessOpenSessionCompensationBatch exposes one bounded compensation scan for cron-service.
func (s *Service) ProcessOpenSessionCompensationBatch(ctx context.Context, options CompensationWorkerOptions) (mictimedomain.CompensationResult, error) {
if s == nil || s.repository == nil {
return mictimedomain.CompensationResult{}, xerr.New(xerr.Unavailable, "mic time repository is not configured")
}
if options.BatchSize <= 0 {
options.BatchSize = 100
}
if options.PendingPublishMaxAge <= 0 {
options.PendingPublishMaxAge = 2 * time.Minute
}
if options.PublishingSessionMaxAge <= 0 {
options.PublishingSessionMaxAge = 12 * time.Hour
}
return s.repository.CompensateOpenSessions(ctx, mictimedomain.CompensationOptions{
NowMs: time.Now().UnixMilli(),
BatchSize: options.BatchSize,
PendingPublishMaxAgeMs: options.PendingPublishMaxAge.Milliseconds(),
PublishingSessionMaxAgeMs: options.PublishingSessionMaxAge.Milliseconds(),
})
}