// Package outbox 定义 room-service 房间外事件的统一封装和状态模型。 package outbox import ( "time" "google.golang.org/protobuf/proto" roomeventsv1 "hyapp.local/api/proto/events/room/v1" "hyapp/pkg/appcode" "hyapp/pkg/idgen" ) const ( // StatusPending 表示事件已经入库,但尚未投递完成。 StatusPending = "pending" // StatusPublished 表示事件已成功投递到外部消费者。 StatusPublished = "published" ) // Record 表达 room_outbox 表的一条逻辑记录。 type Record struct { // AppCode 是事件所属 App,补偿投递和消费者都不能跨 App 合并房间事件。 AppCode string // EventID 是 outbox 事件幂等键,同时会传给腾讯云 IM 作为房间系统事件 event_id。 EventID string // EventType 是事件类型字符串,例如 RoomGiftSent、RoomMicChanged。 EventType string // RoomID 是事件所属房间,外部消费者可按房间分区处理。 RoomID string // Status 表示投递状态,只在 repository/worker 侧变更。 Status string // Envelope 是 protobuf 统一信封,body 内是具体事件消息的序列化结果。 Envelope *roomeventsv1.EventEnvelope // CreatedAt 是事件入库时间,也是待投递扫描的排序依据。 CreatedAt time.Time // RetryCount 记录补偿投递失败次数,便于后续告警和退避策略。 RetryCount int // LastError 保存最近一次投递失败原因。 LastError string } // Build 把具体事件体封装成统一 outbox 记录。 func Build(roomID string, eventType string, roomVersion int64, occurredAt time.Time, body proto.Message) (Record, error) { // 事件体先序列化到信封 body,避免 outbox 表按事件类型拆列。 bodyBytes, err := proto.Marshal(body) if err != nil { return Record{}, err } // EventID 在 room-service 生成,后续同步广播和异步补偿都用同一个 ID 去重。 eventID := idgen.New("evt") return Record{ AppCode: appcode.Default, EventID: eventID, EventType: eventType, RoomID: roomID, Status: StatusPending, Envelope: &roomeventsv1.EventEnvelope{ AppCode: appcode.Default, EventId: eventID, RoomId: roomID, EventType: eventType, RoomVersion: roomVersion, OccurredAtMs: occurredAt.UnixMilli(), Body: bodyBytes, }, CreatedAt: occurredAt, }, nil }