package service import ( "context" "log/slog" "time" "hyapp/pkg/appcode" "hyapp/pkg/logx" "hyapp/services/room-service/internal/room/outbox" ) const roomDirectIMPublishTimeout = 2 * time.Second func splitRoomDirectIMRecords(records []outbox.Record, suppressMirror map[string]bool) ([]outbox.Record, []outbox.Record) { if len(records) == 0 { return nil, nil } durable := make([]outbox.Record, 0, len(records)) directIM := make([]outbox.Record, 0) for _, record := range records { if roomDirectIMOnlyEventType(record.EventType) { directIM = append(directIM, record) continue } durable = append(durable, record) if roomDirectIMMirrorEventType(record.EventType) && !suppressMirror[record.EventType] { // 这类事件同时服务 UI 和业务事实消费:MySQL outbox 保证统计、任务、通知等可补偿,直接 IM 只给客户端低延迟展示。 directIM = append(directIM, record) } } return durable, directIM } func roomDirectIMOnlyEventType(eventType string) bool { switch eventType { case "RoomHeatChanged", "RoomRankChanged", "RoomGiftBatchSent", "RoomRocketFuelChanged", "RoomRocketLaunched": return true default: return false } } func roomDirectIMMirrorEventType(eventType string) bool { switch eventType { case "RoomGiftSent", "RoomUserJoined", "RoomUserLeft", "RoomMicChanged", "RoomMicSeatLocked", "RoomClosed", "RoomUserKicked", "RoomUserMuted", "RoomUserUnbanned", "RoomProfileUpdated", "RoomBackgroundChanged", "RoomChatEnabledChanged", "RoomAdminChanged", "RoomRocketIgnited": return true default: return false } } func (s *Service) publishDirectIMRecordsBestEffort(ctx context.Context, records []outbox.Record) { if s == nil || len(records) == 0 { return } if s.roomDisplayPublisher == nil { logx.Warn(ctx, "room_direct_im_publish_skipped", slog.String("node_id", s.nodeID), slog.Int("record_count", len(records)), slog.String("reason", "publisher_not_configured"), ) return } scopedApp := appcode.FromContext(ctx) copied := append([]outbox.Record(nil), records...) for index := range copied { copied[index].AppCode = appcode.Normalize(firstNonEmpty(copied[index].AppCode, scopedApp)) if copied[index].Envelope != nil { copied[index].Envelope.AppCode = copied[index].AppCode } } sortOutboxRecords(copied) go func(records []outbox.Record) { for _, record := range records { if record.Envelope == nil { logx.Warn(ctx, "room_direct_im_publish_skipped", slog.String("node_id", s.nodeID), slog.String("event_id", record.EventID), slog.String("event_type", record.EventType), slog.String("room_id", record.RoomID), slog.String("reason", "nil_envelope"), ) continue } publishCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), roomDirectIMPublishTimeout) err := s.roomDisplayPublisher.PublishOutboxEvent(publishCtx, record.Envelope) cancel() if err != nil { logx.Warn(ctx, "room_direct_im_publish_failed", slog.String("node_id", s.nodeID), slog.String("event_id", record.EventID), slog.String("event_type", record.EventType), slog.String("room_id", record.RoomID), slog.String("error", trimOutboxError(err.Error())), ) } } }(copied) }