Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
完成运营商实名回调、业务事件观测序列与受控配置装配,同时恢复 UR43 已交付的 packages[].remove 字段及旧响应兼容,统一更新 OpenSpec、OpenAPI 和交付文档。 Constraint: 七月测试环境里程碑不新增或运行自动化测试 Rejected: 以必填 operation_type 替换 packages[].remove | 会破坏已交付前端契约 Confidence: high Scope-risk: broad Directive: 后续修改系列套餐管理接口必须保持 packages[].remove 和 ShopSeriesGrantResponse 兼容 Tested: go run ./cmd/gendocs;go build -buildvcs=false ./...;openspec validate complete-july-iteration-test-release --strict;git diff --check Not-tested: 按本 Change 约定未运行 go test,真实运营商与 Gateway 联调延期
120 lines
4.9 KiB
Go
120 lines
4.9 KiB
Go
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)
|