This commit is contained in:
@@ -84,7 +84,7 @@ func (s *Service) RecordCarrierCallbackFailure(ctx context.Context, card *model.
|
||||
}
|
||||
s.auditWriter.WriteCardStateFailure(ctx, StateAudit{
|
||||
ActionCode: constants.AuditActionIotCardRealnameCallbackSynced,
|
||||
Summary: "运营商回调同步 IoT 卡实名状态失败", Card: card, IntegrationID: integrationID,
|
||||
Summary: "运营商回调同步 IoT 卡实名状态失败", Card: card, IntegrationID: integrationID,
|
||||
}, businessErr)
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ func (s *Service) ApplyCardObservation(ctx context.Context, observation domain.R
|
||||
return domain.RealnameDecision{}, errors.New(errors.CodeInternalError, "卡实名观测能力未完整配置")
|
||||
}
|
||||
var decision domain.RealnameDecision
|
||||
var auditedCard *model.IotCard
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var card model.IotCard
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", observation.CardID).First(&card).Error; err != nil {
|
||||
@@ -102,6 +103,7 @@ func (s *Service) ApplyCardObservation(ctx context.Context, observation domain.R
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "锁定IoT卡失败")
|
||||
}
|
||||
auditedCard = &card
|
||||
nextDecision, decisionErr := domain.ApplyRealname(domain.CardRealnameSnapshot{
|
||||
CardID: card.ID, Status: card.RealNameStatus, FirstRealnameAt: card.FirstRealnameAt,
|
||||
ReversalCount: card.RealnameReversalCount, ReversalStartedAt: card.RealnameReversalStartedAt,
|
||||
@@ -185,11 +187,26 @@ func (s *Service) ApplyCardObservation(ctx context.Context, observation domain.R
|
||||
return errors.New(errors.CodeInternalError, "卡状态统一审计能力未配置")
|
||||
}
|
||||
if err := s.auditWriter.WriteCardStateAudit(ctx, tx, StateAudit{
|
||||
ActionCode: constants.AuditActionIotCardRealnameCallbackSynced,
|
||||
Summary: "运营商回调同步 IoT 卡实名状态",
|
||||
Card: &card,
|
||||
ActionCode: constants.AuditActionIotCardRealnameCallbackSynced,
|
||||
Summary: "运营商回调同步 IoT 卡实名状态",
|
||||
Card: &card,
|
||||
IntegrationID: observation.Metadata.ObservationID,
|
||||
BeforeData: map[string]any{"real_name_status": card.RealNameStatus, "first_realname_at": card.FirstRealnameAt},
|
||||
BeforeData: map[string]any{"real_name_status": card.RealNameStatus, "first_realname_at": card.FirstRealnameAt},
|
||||
AfterData: map[string]any{
|
||||
"real_name_status": decision.AfterStatus, "first_realname_at": firstRealnameAfter(card.FirstRealnameAt, observation.Metadata.ObservedAt, decision.FirstVerified),
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if workerObservationAudited(ctx) && decision.StatusChanged {
|
||||
if s.auditWriter == nil {
|
||||
return errors.New(errors.CodeInternalError, "卡状态统一审计能力未配置")
|
||||
}
|
||||
if err := s.auditWriter.WriteCardStateAudit(ctx, tx, StateAudit{
|
||||
ActionCode: constants.AuditActionIotCardWorkerRealnameSynced,
|
||||
Summary: "Worker 同步 IoT 卡实名事实", Card: &card,
|
||||
IntegrationID: observation.Metadata.ObservationID,
|
||||
BeforeData: map[string]any{"real_name_status": card.RealNameStatus, "first_realname_at": card.FirstRealnameAt},
|
||||
AfterData: map[string]any{
|
||||
"real_name_status": decision.AfterStatus, "first_realname_at": firstRealnameAfter(card.FirstRealnameAt, observation.Metadata.ObservedAt, decision.FirstVerified),
|
||||
},
|
||||
@@ -200,6 +217,13 @@ func (s *Service) ApplyCardObservation(ctx context.Context, observation domain.R
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if workerObservationAudited(ctx) && auditedCard != nil && s.auditWriter != nil {
|
||||
s.auditWriter.WriteCardStateFailure(ctx, StateAudit{
|
||||
ActionCode: constants.AuditActionIotCardWorkerRealnameSynced,
|
||||
Summary: "Worker 同步 IoT 卡实名事实失败", Card: auditedCard,
|
||||
IntegrationID: observation.Metadata.ObservationID,
|
||||
}, err)
|
||||
}
|
||||
return domain.RealnameDecision{}, err
|
||||
}
|
||||
if s.cache != nil {
|
||||
@@ -226,6 +250,11 @@ func manualRefreshAuditAction(ctx context.Context) (string, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func workerObservationAudited(ctx context.Context) bool {
|
||||
linkage := auditcontext.From(ctx)
|
||||
return linkage.ActorKind == constants.AuditActorSystemTask && linkage.Source == constants.AuditSourceWorker
|
||||
}
|
||||
|
||||
func realnameChangedEventID(cardID uint, observationID string) string {
|
||||
prefix := "card-realname:"
|
||||
digest := sha256.Sum256([]byte(strconv.FormatUint(uint64(cardID), 10) + ":" + observationID + ":changed"))
|
||||
|
||||
@@ -33,6 +33,7 @@ func (s *Service) ApplyNetworkObservation(ctx context.Context, observation domai
|
||||
return domain.NetworkDecision{}, errors.New(errors.CodeInternalError, "卡网络观测能力未完整配置")
|
||||
}
|
||||
var decision domain.NetworkDecision
|
||||
var auditedCard *model.IotCard
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var card model.IotCard
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", observation.CardID).First(&card).Error; err != nil {
|
||||
@@ -41,6 +42,7 @@ func (s *Service) ApplyNetworkObservation(ctx context.Context, observation domai
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "锁定IoT卡网络事实失败")
|
||||
}
|
||||
auditedCard = &card
|
||||
nextDecision, decisionErr := domain.ApplyNetwork(domain.CardNetworkSnapshot{
|
||||
CardID: card.ID, NetworkStatus: card.NetworkStatus, StopReason: card.StopReason,
|
||||
IsStandalone: card.IsStandalone, EnablePolling: card.EnablePolling,
|
||||
@@ -117,10 +119,46 @@ func (s *Service) ApplyNetworkObservation(ctx context.Context, observation domai
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if workerObservationAudited(ctx) && stateChanged {
|
||||
if s.auditWriter == nil {
|
||||
return errors.New(errors.CodeInternalError, "卡状态统一审计能力未配置")
|
||||
}
|
||||
enablePolling := card.EnablePolling
|
||||
if decision.StopPolling {
|
||||
enablePolling = false
|
||||
}
|
||||
gatewayIMEI := card.GatewayCardIMEI
|
||||
if decision.UpdateIMEI {
|
||||
gatewayIMEI = decision.GatewayIMEI
|
||||
}
|
||||
if err := s.auditWriter.WriteCardStateAudit(ctx, tx, StateAudit{
|
||||
ActionCode: constants.AuditActionIotCardWorkerNetworkSynced,
|
||||
Summary: "Worker 同步 IoT 卡网络事实", Card: &card,
|
||||
IntegrationID: observation.Metadata.ObservationID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": card.NetworkStatus, "stop_reason": card.StopReason,
|
||||
"gateway_extend": card.GatewayExtend, "gateway_card_imei": card.GatewayCardIMEI,
|
||||
"enable_polling": card.EnablePolling,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"network_status": decision.AfterStatus, "stop_reason": decision.StopReason,
|
||||
"gateway_extend": decision.GatewayExtend, "gateway_card_imei": gatewayIMEI,
|
||||
"enable_polling": enablePolling,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if workerObservationAudited(ctx) && auditedCard != nil && s.auditWriter != nil {
|
||||
s.auditWriter.WriteCardStateFailure(ctx, StateAudit{
|
||||
ActionCode: constants.AuditActionIotCardWorkerNetworkSynced,
|
||||
Summary: "Worker 同步 IoT 卡网络事实失败", Card: auditedCard,
|
||||
IntegrationID: observation.Metadata.ObservationID,
|
||||
}, err)
|
||||
}
|
||||
return domain.NetworkDecision{}, err
|
||||
}
|
||||
if s.cache != nil {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
@@ -21,6 +22,7 @@ type SeriesRequest struct {
|
||||
Source string `json:"source"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
CorrelationID string `json:"correlation_id,omitempty"`
|
||||
ParentEventID string `json:"parent_event_id,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceCardsSeriesRequest 描述需要在后台展开设备有效绑定卡的观测请求。
|
||||
@@ -59,6 +61,7 @@ type SeriesTaskPayload struct {
|
||||
Source string `json:"source"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
CorrelationID string `json:"correlation_id,omitempty"`
|
||||
ParentEventID string `json:"parent_event_id,omitempty"`
|
||||
}
|
||||
|
||||
// RunResult 描述一次实际 Gateway 请求及公共观测应用结果。
|
||||
@@ -135,7 +138,7 @@ func (s *SeriesTrigger) trigger(ctx context.Context, request SeriesRequest, cand
|
||||
if s == nil || s.coordinator == nil || s.scheduler == nil || s.logger == nil {
|
||||
return "", false, errors.New(errors.CodeInternalError, "卡观测序列触发能力未完整配置")
|
||||
}
|
||||
request = normalizeSeriesTrace(request)
|
||||
request = normalizeSeriesTrace(ctx, request)
|
||||
if err := validateSeriesRequest(request); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
@@ -171,6 +174,7 @@ func (s *SeriesTrigger) trigger(ctx context.Context, request SeriesRequest, cand
|
||||
Scene: originalRequest.Scene, ResourceType: originalRequest.ResourceType, ResourceID: originalRequest.ResourceID,
|
||||
SyncType: originalRequest.SyncType, ExpectedValue: originalRequest.ExpectedValue, Source: originalRequest.Source,
|
||||
RequestID: originalRequest.RequestID, CorrelationID: originalRequest.CorrelationID,
|
||||
ParentEventID: originalRequest.ParentEventID,
|
||||
}
|
||||
if err := s.scheduler.Enqueue(ctx, payload); err != nil {
|
||||
s.coordinator.ReleaseSchedule(ctx, seriesID, attempt)
|
||||
@@ -303,12 +307,24 @@ func validateSeriesPayload(payload SeriesTaskPayload) error {
|
||||
return validateSeriesRequest(SeriesRequest{
|
||||
Scene: payload.Scene, ResourceType: payload.ResourceType, ResourceID: payload.ResourceID,
|
||||
SyncType: payload.SyncType, Source: payload.Source, RequestID: payload.RequestID, CorrelationID: payload.CorrelationID,
|
||||
ParentEventID: payload.ParentEventID,
|
||||
})
|
||||
}
|
||||
|
||||
func normalizeSeriesTrace(request SeriesRequest) SeriesRequest {
|
||||
func normalizeSeriesTrace(ctx context.Context, request SeriesRequest) SeriesRequest {
|
||||
linkage := auditcontext.From(ctx)
|
||||
request.RequestID = strings.TrimSpace(request.RequestID)
|
||||
request.CorrelationID = strings.TrimSpace(request.CorrelationID)
|
||||
request.ParentEventID = strings.TrimSpace(request.ParentEventID)
|
||||
if request.RequestID == "" {
|
||||
request.RequestID = strings.TrimSpace(linkage.RequestID)
|
||||
}
|
||||
if request.CorrelationID == "" {
|
||||
request.CorrelationID = strings.TrimSpace(linkage.CorrelationID)
|
||||
}
|
||||
if request.ParentEventID == "" {
|
||||
request.ParentEventID = strings.TrimSpace(linkage.ParentEventID)
|
||||
}
|
||||
if request.RequestID == "" && request.CorrelationID == "" {
|
||||
traceID := uuid.NewString()
|
||||
request.RequestID = traceID
|
||||
|
||||
@@ -32,6 +32,7 @@ func (s *Service) ApplyTrafficObservation(ctx context.Context, observation domai
|
||||
return domain.TrafficDecision{}, errors.New(errors.CodeInternalError, "卡流量观测能力未完整配置")
|
||||
}
|
||||
var decision domain.TrafficDecision
|
||||
var auditedCard *model.IotCard
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var card model.IotCard
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", observation.CardID).First(&card).Error; err != nil {
|
||||
@@ -40,6 +41,7 @@ func (s *Service) ApplyTrafficObservation(ctx context.Context, observation domai
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "锁定IoT卡流量事实失败")
|
||||
}
|
||||
auditedCard = &card
|
||||
nextDecision, decisionErr := domain.ApplyTraffic(domain.CardTrafficSnapshot{
|
||||
CardID: card.ID, DataUsageMB: card.DataUsageMB, CurrentMonthUsageMB: card.CurrentMonthUsageMB,
|
||||
CurrentMonthStartDate: card.CurrentMonthStartDate, LastMonthTotalMB: card.LastMonthTotalMB,
|
||||
@@ -99,10 +101,38 @@ func (s *Service) ApplyTrafficObservation(ctx context.Context, observation domai
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if workerObservationAudited(ctx) && stateChanged {
|
||||
if s.auditWriter == nil {
|
||||
return errors.New(errors.CodeInternalError, "卡状态统一审计能力未配置")
|
||||
}
|
||||
if err := s.auditWriter.WriteCardStateAudit(ctx, tx, StateAudit{
|
||||
ActionCode: constants.AuditActionIotCardWorkerTrafficSynced,
|
||||
Summary: "Worker 同步 IoT 卡流量事实", Card: &card,
|
||||
IntegrationID: observation.Metadata.ObservationID,
|
||||
BeforeData: map[string]any{
|
||||
"data_usage_mb": card.DataUsageMB, "current_month_usage_mb": card.CurrentMonthUsageMB,
|
||||
"current_month_start_date": card.CurrentMonthStartDate, "last_month_total_mb": card.LastMonthTotalMB,
|
||||
"last_gateway_reading_mb": card.LastGatewayReadingMB,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"data_usage_mb": decision.DataUsageMB, "current_month_usage_mb": decision.CurrentMonthUsageMB,
|
||||
"current_month_start_date": decision.CurrentMonthStartDate, "last_month_total_mb": decision.LastMonthTotalMB,
|
||||
"last_gateway_reading_mb": decision.LastGatewayReadingMB, "increment_mb": decision.IncrementMB,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if workerObservationAudited(ctx) && auditedCard != nil && s.auditWriter != nil {
|
||||
s.auditWriter.WriteCardStateFailure(ctx, StateAudit{
|
||||
ActionCode: constants.AuditActionIotCardWorkerTrafficSynced,
|
||||
Summary: "Worker 同步 IoT 卡流量事实失败", Card: auditedCard,
|
||||
IntegrationID: observation.Metadata.ObservationID,
|
||||
}, err)
|
||||
}
|
||||
return domain.TrafficDecision{}, err
|
||||
}
|
||||
if s.cache != nil {
|
||||
|
||||
Reference in New Issue
Block a user