package integration import ( "context" "fmt" "strconv" "google.golang.org/protobuf/proto" roomeventsv1 "hyapp.local/api/proto/events/room/v1" "hyapp/pkg/rocketmqx" "hyapp/pkg/roommq" ) // RocketMQOutboxPublisher publishes durable room_outbox facts to a RocketMQ topic. type RocketMQOutboxPublisher struct { producer *rocketmqx.Producer topic string tagMode roommq.EventTagMode } // NewRocketMQOutboxPublisher creates a room_outbox fanout publisher. func NewRocketMQOutboxPublisher(producer *rocketmqx.Producer, topic string, tagMode roommq.EventTagMode) *RocketMQOutboxPublisher { return &RocketMQOutboxPublisher{producer: producer, topic: topic, tagMode: tagMode} } // PublishOutboxEvent implements OutboxPublisher. func (p *RocketMQOutboxPublisher) PublishOutboxEvent(ctx context.Context, envelope *roomeventsv1.EventEnvelope) error { message, err := roomOutboxRocketMessage(p.topic, p.tagMode, envelope) if err != nil { return err } return p.producer.SendSync(ctx, message) } // roomOutboxRocketMessage keeps envelope.event_type canonical in both migration phases. // Phase A publishes the legacy Tag, while explicit Phase B uses the exact event_type; // the body stays byte-compatible so old replay tools and handlers require no protocol fork. func roomOutboxRocketMessage(topic string, tagMode roommq.EventTagMode, envelope *roomeventsv1.EventEnvelope) (rocketmqx.Message, error) { if envelope == nil { return rocketmqx.Message{}, fmt.Errorf("room outbox envelope is required") } eventTag, err := roommq.EventTypeTag(envelope.GetEventType()) if err != nil { return rocketmqx.Message{}, err } publishTag := roommq.TagRoomOutboxEvent switch tagMode { case "", roommq.EventTagModeLegacy: // 空值覆盖旧测试构造和旧配置;只有显式 event_type 才允许进入 Phase B。 case roommq.EventTagModeEventType: publishTag = eventTag default: return rocketmqx.Message{}, fmt.Errorf("unsupported room event tag mode %q", tagMode) } // outbox 历史数据可能含首尾空白;Tag 与消息体必须使用同一规范 event_type, // 但不能原地修改 repository 持有的 envelope,避免 composite publisher 看到副作用。 canonicalEnvelope := proto.Clone(envelope).(*roomeventsv1.EventEnvelope) canonicalEnvelope.EventType = eventTag body, err := roommq.EncodeRoomOutboxMessage(canonicalEnvelope) if err != nil { return rocketmqx.Message{}, err } return rocketmqx.Message{ Topic: topic, Tag: publishTag, Keys: []string{canonicalEnvelope.GetEventId(), canonicalEnvelope.GetRoomId()}, Body: body, Properties: map[string]string{ "message_type": roommq.MessageTypeRoomOutboxEvent, "app_code": canonicalEnvelope.GetAppCode(), "room_id": canonicalEnvelope.GetRoomId(), "event_id": canonicalEnvelope.GetEventId(), "event_type": eventTag, "room_version": strconv.FormatInt(canonicalEnvelope.GetRoomVersion(), 10), "occurred_at_ms": strconv.FormatInt(canonicalEnvelope.GetOccurredAtMs(), 10), "source_database": "hyapp_room", "source_table": "room_outbox", "delivery_version": "1", }, }, nil } // RocketMQRocketLaunchScheduler publishes exact-time rocket wakeup messages. type RocketMQRocketLaunchScheduler struct { producer *rocketmqx.Producer topic string } // NewRocketMQRocketLaunchScheduler creates the delayed launch scheduler. func NewRocketMQRocketLaunchScheduler(producer *rocketmqx.Producer, topic string) *RocketMQRocketLaunchScheduler { return &RocketMQRocketLaunchScheduler{producer: producer, topic: topic} } // ScheduleRoomRocketLaunch implements RoomRocketLaunchScheduler. func (s *RocketMQRocketLaunchScheduler) ScheduleRoomRocketLaunch(ctx context.Context, schedule RoomRocketLaunchSchedule) error { body, err := roommq.EncodeRoomRocketLaunchDueMessage(roommq.RoomRocketLaunchDueMessage{ AppCode: schedule.AppCode, RoomID: schedule.RoomID, RocketID: schedule.RocketID, Level: schedule.Level, LaunchAtMS: schedule.LaunchAtMS, ResetAtMS: schedule.ResetAtMS, CommandID: schedule.CommandID, }) if err != nil { return err } return s.producer.SendSync(ctx, rocketmqx.Message{ Topic: s.topic, Tag: roommq.TagRoomRocketLaunchDue, Keys: []string{schedule.RocketID, schedule.RoomID}, Body: body, DeliveryAtMS: schedule.LaunchAtMS, Properties: map[string]string{ "message_type": roommq.MessageTypeRoomRocketLaunchDue, "app_code": schedule.AppCode, "room_id": schedule.RoomID, "rocket_id": schedule.RocketID, "level": strconv.FormatInt(int64(schedule.Level), 10), "launch_at_ms": strconv.FormatInt(schedule.LaunchAtMS, 10), "reset_at_ms": strconv.FormatInt(schedule.ResetAtMS, 10), }, }) }