package cardobservation import ( "context" "strconv" "strings" "github.com/bytedance/sonic" "gorm.io/gorm" cardapp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation" "github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" ) // SeriesEventWriter 将业务成功观测请求写入公共 Outbox。 type SeriesEventWriter struct { outbox *outbox.Repository } // NewSeriesEventWriter 创建业务观测事件 Writer。 func NewSeriesEventWriter(repository *outbox.Repository) *SeriesEventWriter { return &SeriesEventWriter{outbox: repository} } // AppendSeriesRequested 在原业务事务中追加稳定观测请求事件。 func (w *SeriesEventWriter) AppendSeriesRequested(ctx context.Context, tx *gorm.DB, event cardapp.SeriesRequestedEvent) error { if w == nil || w.outbox == nil { return errors.New(errors.CodeInternalError, "业务观测 Outbox Writer 未配置") } if cardapp.IsSeriesTriggerSuppressed(ctx) { return nil } if strings.TrimSpace(event.EventID) == "" || event.ResourceID == 0 || len(event.SyncTypes) == 0 { return errors.New(errors.CodeInvalidParam, "业务观测事件载荷不完整") } if tx == nil { return errors.New(errors.CodeInternalError, "业务观测事件缺少原业务事务") } if event.ResourceType == constants.CardObservationResourceTypeDevice { event.ResourceIDs = nil if err := tx.WithContext(ctx). Model(&model.DeviceSimBinding{}). Where("device_id = ? AND bind_status = ?", event.ResourceID, constants.BindStatusBound). Order("slot_position ASC"). Pluck("iot_card_id", &event.ResourceIDs).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "冻结业务观测设备绑定卡快照失败") } } _, err := w.outbox.AppendIdempotent(ctx, tx, outbox.Envelope{ EventID: event.EventID, EventType: constants.OutboxEventTypeCardSeriesRequested, PayloadVersion: constants.CardSeriesRequestedPayloadVersionV1, AggregateType: event.ResourceType, AggregateID: strconv.FormatUint(uint64(event.ResourceID), 10), ResourceType: event.ResourceType, ResourceID: strconv.FormatUint(uint64(event.ResourceID), 10), BusinessKey: event.EventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID, Payload: event, }) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "写入业务观测 Outbox 事件失败") } return nil } // SeriesRequestedConsumer 消费业务成功事实并触发卡观测序列。 type SeriesRequestedConsumer struct { trigger *cardapp.SeriesTrigger } // NewSeriesRequestedConsumer 创建业务观测序列消费者。 func NewSeriesRequestedConsumer(trigger *cardapp.SeriesTrigger) *SeriesRequestedConsumer { return &SeriesRequestedConsumer{trigger: trigger} } // Consume 将一个业务事件展开为卡观测序列;重复投递由稳定序列键幂等。 func (c *SeriesRequestedConsumer) Consume(ctx context.Context, envelope outbox.DeliveryEnvelope) error { if c == nil || c.trigger == nil { return errors.New(errors.CodeInternalError, "业务观测序列消费者未配置") } if envelope.EventType != constants.OutboxEventTypeCardSeriesRequested || envelope.PayloadVersion != constants.CardSeriesRequestedPayloadVersionV1 { return errors.New(errors.CodeInvalidParam, "业务观测事件类型或版本不受支持") } var event cardapp.SeriesRequestedEvent if err := sonic.Unmarshal(envelope.Payload, &event); err != nil { return errors.Wrap(errors.CodeInvalidParam, err, "业务观测事件载荷无法解析") } if event.EventID != envelope.EventID || event.ResourceID == 0 || len(event.SyncTypes) == 0 { return errors.New(errors.CodeInvalidParam, "业务观测事件载荷不完整") } if event.ResourceType != constants.CardObservationResourceTypeCard && event.ResourceType != constants.CardObservationResourceTypeDevice { return errors.New(errors.CodeInvalidParam, "业务观测事件资源类型不受支持") } cardIDs := []uint{event.ResourceID} if event.ResourceType == constants.CardObservationResourceTypeDevice { cardIDs = event.ResourceIDs } if len(cardIDs) == 0 { return nil } for _, cardID := range cardIDs { for _, syncType := range event.SyncTypes { request := cardapp.SeriesRequest{ Scene: event.Scene, ResourceType: constants.CardObservationResourceTypeCard, ResourceID: strconv.FormatUint(uint64(cardID), 10), SyncType: syncType, ExpectedValue: event.ExpectedValue, Source: event.Source, RequestID: event.RequestID, CorrelationID: event.CorrelationID, } key := event.EventID + ":" + strconv.FormatUint(uint64(cardID), 10) + ":" + syncType if _, _, err := c.trigger.TriggerEvent(ctx, key, request); err != nil { return err } } } return nil } var _ cardapp.SeriesEventWriter = (*SeriesEventWriter)(nil) var _ outbox.EventConsumer = (*SeriesRequestedConsumer)(nil)