收口七月卡状态回调与系列授权兼容契约
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
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 联调延期
This commit is contained in:
119
internal/infrastructure/cardobservation/series_event.go
Normal file
119
internal/infrastructure/cardobservation/series_event.go
Normal file
@@ -0,0 +1,119 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user