package service import ( "context" "log/slog" "time" "hyapp/pkg/appcode" "hyapp/pkg/logx" "hyapp/services/room-service/internal/room/outbox" ) const ( // robotDisplayPublishTimeout 是机器人展示直发 IM 的单条上限;超时或失败只记日志,不影响已提交房间状态。 robotDisplayPublishTimeout = 3 * time.Second ) func (s *Service) publishRobotDisplayRecordsBestEffort(ctx context.Context, records []outbox.Record) { if s == nil || len(records) == 0 { return } if s.robotDisplayPublisher == nil { // 缺少腾讯 IM publisher 时不能阻断房间命令,只把整批展示事件视为 best-effort 投递失败。 logx.Warn(ctx, "robot_display_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, "robot_display_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), robotDisplayPublishTimeout) err := s.robotDisplayPublisher.PublishOutboxEvent(publishCtx, record.Envelope) cancel() if err != nil { // 机器人展示事件允许极小概率丢失;这里只暴露日志,客户端靠房间快照恢复最终状态。 logx.Warn(ctx, "robot_display_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) }