收口审计治理与套餐任务进展

Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
2026-08-05 14:30:54 +08:00
parent b3499adfca
commit 5e552d99bc
178 changed files with 16797 additions and 5674 deletions

View File

@@ -46,6 +46,7 @@ type SyncDecisionCommand struct {
Decision string
DecisionSnapshot []byte
Source string
IntegrationIDs []string
}
// SyncDecisionResult 返回本次是否首次记录该标准终态。
@@ -60,6 +61,7 @@ type SyncDecisionService struct {
repositories RepositoryProvider
eventWriter TerminalEventWriter
deliveryWriter DecisionDeliveryWriter
audit AuditWriter
now func() time.Time
}
@@ -79,9 +81,14 @@ func NewSyncDecisionService(
}
}
// SetAuditWriter 注入通用审批统一审计 Writer。
func (s *SyncDecisionService) SetAuditWriter(writer AuditWriter) {
s.audit = writer
}
// Execute 将回调或轮询取得的权威渠道状态原子转换为通用审批终态和可靠业务事件。
func (s *SyncDecisionService) Execute(ctx context.Context, command SyncDecisionCommand) (*SyncDecisionResult, error) {
if s == nil || s.db == nil || s.repositories == nil || s.eventWriter == nil || s.deliveryWriter == nil {
if s == nil || s.db == nil || s.repositories == nil || s.eventWriter == nil || s.deliveryWriter == nil || s.audit == nil {
return nil, errors.New(errors.CodeInternalError, "通用审批决策同步用例未完整配置")
}
if command.InstanceID == 0 || !isSupportedSyncSource(command.Source) {
@@ -98,6 +105,8 @@ func (s *SyncDecisionService) Execute(ctx context.Context, command SyncDecisionC
return err
}
expectedStatus, expectedVersion := instance.Status, instance.Version
beforeStatus := instance.Status
beforeExternalRef := instance.ExternalRef
changed, err := instance.ApplyDecision(command.Decision, command.DecisionSnapshot, s.now().UTC())
if err != nil {
return err
@@ -125,6 +134,20 @@ func (s *SyncDecisionService) Execute(ctx context.Context, command SyncDecisionC
if err := s.eventWriter.Append(ctx, tx, event); err != nil {
return err
}
actorKind, actorID, source := approvalSyncAuditOrigin(command.Source)
afterStatus := instance.Status
if err := s.audit.WriteApproval(ctx, tx, AuditChange{
EventID: event.EventID + ":audit", ActionCode: constants.AuditActionApprovalDecisionSynced,
Summary: "同步审批权威终态", InstanceID: instance.ID,
BusinessType: instance.BusinessType, BusinessID: instance.BusinessID,
SubmitterAccountID: instance.SubmitterAccountID, SubmitterSnapshot: instance.SubmitterSnapshot,
Provider: instance.Provider, BeforeExternalRef: beforeExternalRef, AfterExternalRef: instance.ExternalRef,
CorrelationID: instance.CorrelationID, BeforeStatus: &beforeStatus, AfterStatus: &afterStatus,
ActorKind: actorKind, ActorID: actorID, Source: source, Result: constants.AuditResultSuccess,
Decision: command.Decision, IntegrationIDs: command.IntegrationIDs, OutboxEventID: event.EventID,
}); err != nil {
return err
}
result.FirstTerminal = true
return nil
})
@@ -134,6 +157,17 @@ func (s *SyncDecisionService) Execute(ctx context.Context, command SyncDecisionC
return result, nil
}
func approvalSyncAuditOrigin(source string) (string, string, string) {
switch source {
case constants.ApprovalSyncSourceCallback:
return constants.AuditActorExternalSystem, constants.ApprovalAuditActorWeCom, constants.AuditSourceCallback
case constants.ApprovalSyncSourcePolling:
return constants.AuditActorScheduledJob, constants.ApprovalAuditActorRecoveryJob, constants.AuditSourceScheduler
default:
return constants.AuditActorAccount, "", constants.AuditSourceAdminAPI
}
}
func terminalDecisionEventID(instanceID uint, decision string) string {
return "approval:" + strconv.FormatUint(uint64(instanceID), 10) + ":" + decision
}